提交 2ceb5ec6 authored 作者: Thomas Mueller's avatar Thomas Mueller

A persistent multi-version map (work in progress)

上级 a94da0f4
...@@ -104,6 +104,7 @@ import org.h2.test.server.TestNestedLoop; ...@@ -104,6 +104,7 @@ import org.h2.test.server.TestNestedLoop;
import org.h2.test.server.TestWeb; import org.h2.test.server.TestWeb;
import org.h2.test.server.TestInit; import org.h2.test.server.TestInit;
import org.h2.test.store.TestCacheLIRS; import org.h2.test.store.TestCacheLIRS;
import org.h2.test.store.TestConcurrent;
import org.h2.test.store.TestDataUtils; import org.h2.test.store.TestDataUtils;
import org.h2.test.store.TestMVStore; import org.h2.test.store.TestMVStore;
import org.h2.test.store.TestMVRTree; import org.h2.test.store.TestMVRTree;
...@@ -664,11 +665,12 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -664,11 +665,12 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
} }
private void testUnit() { private void testUnit() {
// store // mv store
new TestMVStore().runTest(this);
new TestCacheLIRS().runTest(this); new TestCacheLIRS().runTest(this);
new TestConcurrent().runTest(this);
new TestDataUtils().runTest(this); new TestDataUtils().runTest(this);
new TestMVRTree().runTest(this); new TestMVRTree().runTest(this);
new TestMVStore().runTest(this);
// unit // unit
new TestAutoReconnect().runTest(this); new TestAutoReconnect().runTest(this);
......
...@@ -35,9 +35,6 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -35,9 +35,6 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public V get(Object key) { public V get(Object key) {
checkOpen(); checkOpen();
if (root == null) {
return null;
}
return (V) get(root, key); return (V) get(root, key);
} }
...@@ -73,13 +70,10 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -73,13 +70,10 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
} }
protected Page getPage(K key) { protected Page getPage(K key) {
if (root == null) {
return null;
}
return getPage(root, key); return getPage(root, key);
} }
protected Page getPage(Page p, Object key) { private Page getPage(Page p, Object key) {
if (!p.isLeaf()) { if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) { for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) { if (contains(p, i, key)) {
...@@ -155,26 +149,26 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -155,26 +149,26 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
return (V) putOrAdd(key, value, false); return (V) putOrAdd(key, value, false);
} }
/**
* Add a given key-value pair. The key should not exist (if it exists, the
* result is undefined).
*
* @param key the key
* @param value the value
*/
public void add(K key, V value) { public void add(K key, V value) {
putOrAdd(key, value, true); putOrAdd(key, value, true);
} }
public Object putOrAdd(K key, V value, boolean alwaysAdd) { private Object putOrAdd(K key, V value, boolean alwaysAdd) {
checkWrite(); checkWrite();
long writeVersion = store.getCurrentVersion(); long writeVersion = store.getCurrentVersion();
Page p = root; Page p = root.copyOnWrite(writeVersion);
Object result; Object result;
if (p == null) { if (alwaysAdd || get(key) == null) {
Object[] keys = { key };
Object[] values = { value };
p = Page.create(this, writeVersion, 1,
keys, values, null, null, null, 1, 0);
result = null;
} else if (alwaysAdd || get(key) == null) {
if (p.getKeyCount() > store.getMaxPageSize()) { if (p.getKeyCount() > store.getMaxPageSize()) {
// only possible if this is the root, else we would have split earlier // only possible if this is the root, else we would have split earlier
// (this requires maxPageSize is fixed) // (this requires maxPageSize is fixed)
p = p.copyOnWrite(writeVersion);
long totalCount = p.getTotalCount(); long totalCount = p.getTotalCount();
Page split = split(p, writeVersion); Page split = split(p, writeVersion);
Object[] keys = { getBounds(p), getBounds(split) }; Object[] keys = { getBounds(p), getBounds(split) };
...@@ -195,7 +189,16 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -195,7 +189,16 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
return result; return result;
} }
protected Object set(Page p, long writeVersion, Object key, Object value) { /**
* Update the value for the given key. The key must exist.
*
* @param p the page
* @param writeVersion the write version
* @param key the key
* @param value the value
* @return the old value
*/
private Object set(Page p, long writeVersion, Object key, Object value) {
if (!p.isLeaf()) { if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) { for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) { if (contains(p, i, key)) {
...@@ -217,7 +220,7 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -217,7 +220,7 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
return null; return null;
} }
protected void add(Page p, long writeVersion, Object key, Object value) { private void add(Page p, long writeVersion, Object key, Object value) {
if (p.isLeaf()) { if (p.isLeaf()) {
p.insertLeaf(p.getKeyCount(), key, value); p.insertLeaf(p.getKeyCount(), key, value);
return; return;
...@@ -379,6 +382,13 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -379,6 +382,13 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
source.remove(sourceIndex); source.remove(sourceIndex);
} }
/**
* Add all node keys (including internal bounds) to the given list.
* This is mainly used to visualize the internal splits.
*
* @param list the list
* @param p the root page
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void addNodeKeys(ArrayList<K> list, Page p) { public void addNodeKeys(ArrayList<K> list, Page p) {
if (p != null && !p.isLeaf()) { if (p != null && !p.isLeaf()) {
...@@ -395,6 +405,7 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -395,6 +405,7 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
* @param p the current page * @param p the current page
* @param cursor the cursor * @param cursor the cursor
* @param key the key * @param key the key
* @return the cursor position
*/ */
protected CursorPos min(Page p, Cursor<K, V> cursor, Object key) { protected CursorPos min(Page p, Cursor<K, V> cursor, Object key) {
if (p == null) { if (p == null) {
......
...@@ -21,7 +21,15 @@ import org.h2.dev.store.btree.DataType; ...@@ -21,7 +21,15 @@ import org.h2.dev.store.btree.DataType;
*/ */
public class SequenceMap<K, V> extends MVMap<K, V> { public class SequenceMap<K, V> extends MVMap<K, V> {
int min = 1, max = 10; /**
* The minimum value.
*/
int min = 1;
/**
* The maximum value.
*/
int max = 10;
SequenceMap(MVStore store, int id, String name, DataType keyType, SequenceMap(MVStore store, int id, String name, DataType keyType,
DataType valueType, long createVersion) { DataType valueType, long createVersion) {
......
...@@ -14,8 +14,8 @@ import java.util.Arrays; ...@@ -14,8 +14,8 @@ import java.util.Arrays;
*/ */
public class SpatialKey { public class SpatialKey {
public long id; private final long id;
private float[] minMax; private final float[] minMax;
/** /**
* Create a new key. * Create a new key.
...@@ -28,22 +28,50 @@ public class SpatialKey { ...@@ -28,22 +28,50 @@ public class SpatialKey {
this.minMax = minMax; this.minMax = minMax;
} }
/**
* Get the minimum value for the given dimension.
*
* @param dim the dimension
* @return the value
*/
public float min(int dim) { public float min(int dim) {
return minMax[dim + dim]; return minMax[dim + dim];
} }
/**
* Set the minimum value for the given dimension.
*
* @param dim the dimension
* @param x the value
*/
public void setMin(int dim, float x) { public void setMin(int dim, float x) {
minMax[dim + dim] = x; minMax[dim + dim] = x;
} }
/**
* Get the maximum value for the given dimension.
*
* @param dim the dimension
* @return the value
*/
public float max(int dim) { public float max(int dim) {
return minMax[dim + dim + 1]; return minMax[dim + dim + 1];
} }
/**
* Set the maximum value for the given dimension.
*
* @param dim the dimension
* @param x the value
*/
public void setMax(int dim, float x) { public void setMax(int dim, float x) {
minMax[dim + dim + 1] = x; minMax[dim + dim + 1] = x;
} }
public long getId() {
return id;
}
public String toString() { public String toString() {
StringBuilder buff = new StringBuilder(); StringBuilder buff = new StringBuilder();
buff.append(id).append(": ("); buff.append(id).append(": (");
......
...@@ -27,20 +27,33 @@ public class SpatialType implements DataType { ...@@ -27,20 +27,33 @@ public class SpatialType implements DataType {
this.dimensions = dimensions; this.dimensions = dimensions;
} }
/**
* Read a value from a string.
*
* @param s the string
* @return the value
*/
public static SpatialType fromString(String s) { public static SpatialType fromString(String s) {
return new SpatialType(Integer.parseInt(s.substring(1))); return new SpatialType(Integer.parseInt(s.substring(1)));
} }
@Override @Override
public int compare(Object a, Object b) { public int compare(Object a, Object b) {
long la = ((SpatialKey) a).id; long la = ((SpatialKey) a).getId();
long lb = ((SpatialKey) b).id; long lb = ((SpatialKey) b).getId();
return la < lb ? -1 : la > lb ? 1 : 0; return la < lb ? -1 : la > lb ? 1 : 0;
} }
/**
* Check whether two spatial values are equal.
*
* @param a the first value
* @param b the second value
* @return true if they are equal
*/
public boolean equals(Object a, Object b) { public boolean equals(Object a, Object b) {
long la = ((SpatialKey) a).id; long la = ((SpatialKey) a).getId();
long lb = ((SpatialKey) b).id; long lb = ((SpatialKey) b).getId();
return la == lb; return la == lb;
} }
...@@ -70,7 +83,7 @@ public class SpatialType implements DataType { ...@@ -70,7 +83,7 @@ public class SpatialType implements DataType {
buff.putFloat(k.max(i)); buff.putFloat(k.max(i));
} }
} }
DataUtils.writeVarLong(buff, k.id); DataUtils.writeVarLong(buff, k.getId());
} }
@Override @Override
...@@ -97,6 +110,13 @@ public class SpatialType implements DataType { ...@@ -97,6 +110,13 @@ public class SpatialType implements DataType {
return "s" + dimensions; return "s" + dimensions;
} }
/**
* Check whether the two objects overlap.
*
* @param objA the first object
* @param objB the second object
* @return true if they overlap
*/
public boolean isOverlap(Object objA, Object objB) { public boolean isOverlap(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA; SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB; SpatialKey b = (SpatialKey) objB;
...@@ -108,6 +128,12 @@ public class SpatialType implements DataType { ...@@ -108,6 +128,12 @@ public class SpatialType implements DataType {
return true; return true;
} }
/**
* Increase the bounds in the given spatial object.
*
* @param bounds the bounds (may be modified)
* @param add the value
*/
public void increaseBounds(Object bounds, Object add) { public void increaseBounds(Object bounds, Object add) {
SpatialKey b = (SpatialKey) bounds; SpatialKey b = (SpatialKey) bounds;
SpatialKey a = (SpatialKey) add; SpatialKey a = (SpatialKey) add;
...@@ -144,7 +170,14 @@ public class SpatialType implements DataType { ...@@ -144,7 +170,14 @@ public class SpatialType implements DataType {
return areaNew - areaOld; return areaNew - areaOld;
} }
public float getCombinedArea(Object objA, Object objB) { /**
* Get the combined area of both objects.
*
* @param objA the first object
* @param objB the second object
* @return the area
*/
float getCombinedArea(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA; SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB; SpatialKey b = (SpatialKey) objB;
float area = 1; float area = 1;
...@@ -193,7 +226,13 @@ public class SpatialType implements DataType { ...@@ -193,7 +226,13 @@ public class SpatialType implements DataType {
return true; return true;
} }
public Object createBoundingBox(Object objA) { /**
* Create a bounding box starting with the given object.
*
* @param objA the object
* @return the bounding box
*/
Object createBoundingBox(Object objA) {
float[] minMax = new float[dimensions * 2]; float[] minMax = new float[dimensions * 2];
SpatialKey a = (SpatialKey) objA; SpatialKey a = (SpatialKey) objA;
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
......
/*
* 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.Map;
import java.util.Random;
import org.h2.dev.store.btree.MVMap;
import org.h2.dev.store.btree.MVStore;
import org.h2.test.TestBase;
import org.h2.util.Task;
/**
* Tests concurrently accessing a tree map store.
*/
public class TestConcurrent extends TestMVStore {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
public void test() throws InterruptedException {
testConcurrentWrite();
testConcurrentRead();
}
/**
* Test what happens on concurrent write. Concurrent write may corrupt the
* map, so that keys and values may become null.
*/
private void testConcurrentWrite() throws InterruptedException {
final MVStore s = openStore(null);
final MVMap<Integer, Integer> m = s.openMap("data", Integer.class, Integer.class);
final int size = 20;
final Random rand = new Random(1);
Task task = new Task() {
public void call() throws Exception {
while (!stop) {
try {
if (rand.nextBoolean()) {
m.put(rand.nextInt(size), 1);
} else {
m.remove(rand.nextInt(size));
}
m.get(rand.nextInt(size));
} catch (NullPointerException e) {
// ignore
}
}
}
};
task.execute();
Thread.sleep(1);
for (int j = 0; j < 10; j++) {
for (int i = 0; i < 10; i++) {
try {
if (rand.nextBoolean()) {
m.put(rand.nextInt(size), 2);
} else {
m.remove(rand.nextInt(size));
}
m.get(rand.nextInt(size));
} catch (NullPointerException e) {
// ignore
}
}
s.incrementVersion();
Thread.sleep(1);
}
task.get();
// verify the structure is still somewhat usable
for (int x : m.keySet()) {
try {
m.get(x);
} catch (NullPointerException e) {
// ignore
}
}
for (int i = 0; i < size; i++) {
try {
m.get(i);
} catch (NullPointerException e) {
// ignore
}
}
s.close();
}
private void testConcurrentRead() throws InterruptedException {
final MVStore s = openStore(null);
final MVMap<Integer, Integer> m = s.openMap("data", Integer.class, Integer.class);
final int size = 3;
int x = (int) s.getCurrentVersion();
for (int i = 0; i < size; i++) {
m.put(i, x);
}
s.incrementVersion();
Task task = new Task() {
public void call() throws Exception {
while (!stop) {
long v = s.getCurrentVersion() - 1;
Map<Integer, Integer> old = m.openVersion(v);
for (int i = 0; i < size; i++) {
Integer x = old.get(i);
if (x == null || (int) v != x) {
Map<Integer, Integer> old2 = m.openVersion(v);
throw new AssertionError(x + "<>" + v + " at " + i + " " + old2);
}
}
}
}
};
task.execute();
Thread.sleep(1);
for (int j = 0; j < 100; j++) {
x = (int) s.getCurrentVersion();
for (int i = 0; i < size; i++) {
m.put(i, x);
}
s.incrementVersion();
Thread.sleep(1);
}
task.get();
s.close();
}
}
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
package org.h2.test.store; package org.h2.test.store;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.HashMap;
import org.h2.dev.store.btree.DataUtils; import org.h2.dev.store.btree.DataUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
...@@ -24,12 +25,31 @@ public class TestDataUtils extends TestBase { ...@@ -24,12 +25,31 @@ public class TestDataUtils extends TestBase {
} }
public void test() throws Exception { public void test() throws Exception {
testMap();
testVarIntVarLong(); testVarIntVarLong();
testCheckValue(); testCheckValue();
testPagePos(); testPagePos();
testEncodeLength(); testEncodeLength();
} }
private void testMap() {
StringBuilder buff = new StringBuilder();
DataUtils.appendMap(buff, "", "");
DataUtils.appendMap(buff, "a", "1");
DataUtils.appendMap(buff, "b", ",");
DataUtils.appendMap(buff, "c", "1,2");
DataUtils.appendMap(buff, "d", "\"test\"");
assertEquals(":,a:1,b:\",\",c:\"1,2\",d:\"\\\"test\\\"\"", buff.toString());
HashMap<String, String> m = DataUtils.parseMap(buff.toString());
assertEquals(5, m.size());
assertEquals("", m.get(""));
assertEquals("1", m.get("a"));
assertEquals(",", m.get("b"));
assertEquals("1,2", m.get("c"));
assertEquals("\"test\"", m.get("d"));
}
private void testVarIntVarLong() { private void testVarIntVarLong() {
ByteBuffer buff = ByteBuffer.allocate(100); ByteBuffer buff = ByteBuffer.allocate(100);
for (long x = 0; x < 1000; x++) { for (long x = 0; x < 1000; x++) {
......
/*
* 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; package org.h2.test.store;
import java.awt.AlphaComposite; import java.awt.AlphaComposite;
...@@ -34,14 +39,14 @@ public class TestMVRTree extends TestMVStore { ...@@ -34,14 +39,14 @@ public class TestMVRTree extends TestMVStore {
} }
public void test() { public void test() {
testRtreeMany(); testMany();
testRtree(); testTree();
testRandomRtree(); testRandom();
testCustomMapType(); testCustomMapType();
} }
private void testRtreeMany() { private void testMany() {
String fileName = getBaseDir() + "/testRtree.h3"; String fileName = getBaseDir() + "/testMany.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s; MVStore s;
s = openStore(fileName); s = openStore(fileName);
...@@ -103,8 +108,8 @@ public class TestMVRTree extends TestMVStore { ...@@ -103,8 +108,8 @@ public class TestMVRTree extends TestMVStore {
// System.out.println("remove: " + (System.currentTimeMillis() - t)); // System.out.println("remove: " + (System.currentTimeMillis() - t));
} }
private void testRtree() { private void testTree() {
String fileName = getBaseDir() + "/testRtree.h3"; String fileName = getBaseDir() + "/testTree.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s; MVStore s;
s = openStore(fileName); s = openStore(fileName);
...@@ -195,8 +200,8 @@ public class TestMVRTree extends TestMVStore { ...@@ -195,8 +200,8 @@ public class TestMVRTree extends TestMVStore {
return rect; return rect;
} }
private void testRandomRtree() { private void testRandom() {
String fileName = getBaseDir() + "/testRtreeRandom.h3"; String fileName = getBaseDir() + "/testRandom.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
MVRTreeMap<SpatialKey, String> m = s.openMap("data", "r", "s2", ""); MVRTreeMap<SpatialKey, String> m = s.openMap("data", "r", "s2", "");
......
...@@ -7,6 +7,7 @@ package org.h2.test.store; ...@@ -7,6 +7,7 @@ package org.h2.test.store;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.TreeMap; import java.util.TreeMap;
import org.h2.dev.store.btree.MVMap; import org.h2.dev.store.btree.MVMap;
...@@ -30,8 +31,10 @@ public class TestMVStore extends TestBase { ...@@ -30,8 +31,10 @@ public class TestMVStore extends TestBase {
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
public void test() { public void test() throws InterruptedException {
testExample();
testIterateOverChanges(); testIterateOverChanges();
testOpenStoreCloseLoop();
testVersion(); testVersion();
testTruncateFile(); testTruncateFile();
testFastDelete(); testFastDelete();
...@@ -49,31 +52,105 @@ public class TestMVStore extends TestBase { ...@@ -49,31 +52,105 @@ public class TestMVStore extends TestBase {
testSimple(); testSimple();
} }
private void testExample() {
String fileName = getBaseDir() + "/testOpenClose.h3";
FileUtils.delete(fileName);
// open the store (in-memory if fileName is null)
MVStore s = MVStore.open(fileName);
// create/get the map "data"
// the String.class, String.class will be optional later
MVMap<String, String> map = s.openMap("data",
String.class, String.class);
// add some data
map.put("1", "Hello");
map.put("2", "World");
// get the current version, for later use
long oldVersion = s.getCurrentVersion();
// from now on, the old version is read-only
s.incrementVersion();
// more changes, in the new version
// changes can be rolled back if required
// changes always go into 'head' (the newest version)
map.put("1", "Hi");
map.remove("2");
// access the old data (before incrementVersion)
MVMap<String, String> oldMap =
map.openVersion(oldVersion);
// store the newest data to disk
s.store();
// print the old version (can be done
// concurrently with further modifications)
// this will print Hello World
// System.out.println(oldMap.get("1"));
// System.out.println(oldMap.get("2"));
oldMap.close();
// print the newest version ("Hi")
// System.out.println(map.get("1"));
// close the store - this doesn't write to disk
s.close();
}
private void testOpenStoreCloseLoop() {
String fileName = getBaseDir() + "/testOpenClose.h3";
FileUtils.delete(fileName);
for (int k = 0; k < 1; k++) {
// long t = System.currentTimeMillis();
for (int j = 0; j < 3; j++) {
MVStore s = openStore(fileName);
Map<String, Integer> m = s.openMap("data", String.class,
Integer.class);
for (int i = 0; i < 3; i++) {
Integer x = m.get("value");
m.put("value", x == null ? 0 : x + 1);
s.store();
}
s.close();
}
// System.out.println("open/close: " + (System.currentTimeMillis() - t));
// System.out.println("size: " + FileUtils.size(fileName));
}
}
private void testIterateOverChanges() { private void testIterateOverChanges() {
String fileName = getBaseDir() + "/testVersion.h3"; String fileName = getBaseDir() + "/testIterate.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
s.setMaxPageSize(6); s.setMaxPageSize(6);
MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 60; i++) { for (int i = 0; i < 100; i++) {
m.put(i, "Hi"); m.put(i, "Hi");
} }
s.commit(); s.incrementVersion();
s.store(); s.store();
for (int i = 20; i < 40; i++) { for (int i = 20; i < 40; i++) {
assertEquals("Hi", m.put(i, "Hello")); assertEquals("Hi", m.put(i, "Hello"));
} }
s.commit(); s.incrementVersion();
for (int i = 10; i < 15; i++) { for (int i = 10; i < 15; i++) {
m.put(i, "Hallo"); m.put(i, "Hallo");
} }
m.put(50, "Hallo"); m.put(50, "Hallo");
for (int i = 90; i < 100; i++) {
assertEquals("Hi", m.remove(i));
}
assertEquals(null, m.put(100, "Hallo"));
Iterator<Integer> it = m.changeIterator(s.getCurrentVersion()); Iterator<Integer> it = m.changeIterator(s.getCurrentVersion());
ArrayList<Integer> list = New.arrayList(); ArrayList<Integer> list = New.arrayList();
while (it.hasNext()) { while (it.hasNext()) {
list.add(it.next()); list.add(it.next());
} }
assertEquals("[9, 10, 11, 12, 13, 14, 48, 49, 50]", list.toString()); assertEquals("[9, 10, 11, 12, 13, 14, 48, 49, 50, 87, 88, 89, 100]", list.toString());
} }
private void testVersion() { private void testVersion() {
...@@ -85,13 +162,14 @@ public class TestMVStore extends TestBase { ...@@ -85,13 +162,14 @@ public class TestMVStore extends TestBase {
s = openStore(fileName); s = openStore(fileName);
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
long first = s.getCurrentVersion(); long first = s.getCurrentVersion();
s.incrementVersion();
m.put("1", "Hello"); m.put("1", "Hello");
m.put("2", "World"); m.put("2", "World");
for (int i = 10; i < 20; i++) { for (int i = 10; i < 20; i++) {
m.put("" + i, "data"); m.put("" + i, "data");
} }
s.commit();
long old = s.getCurrentVersion(); long old = s.getCurrentVersion();
s.incrementVersion();
m.put("1", "Hallo"); m.put("1", "Hallo");
m.put("2", "Welt"); m.put("2", "Welt");
MVMap<String, String> mFirst; MVMap<String, String> mFirst;
...@@ -192,7 +270,7 @@ public class TestMVStore extends TestBase { ...@@ -192,7 +270,7 @@ public class TestMVStore extends TestBase {
assertTrue(s.hasUnsavedChanges()); assertTrue(s.hasUnsavedChanges());
MVMap<String, String> m0 = s.openMap("data0", String.class, String.class); MVMap<String, String> m0 = s.openMap("data0", String.class, String.class);
m.put("1", "Hello"); m.put("1", "Hello");
assertEquals(1, s.commit()); assertEquals(1, s.incrementVersion());
s.rollbackTo(1); s.rollbackTo(1);
assertEquals("Hello", m.get("1")); assertEquals("Hello", m.get("1"));
long v2 = s.store(); long v2 = s.store();
...@@ -235,7 +313,7 @@ public class TestMVStore extends TestBase { ...@@ -235,7 +313,7 @@ public class TestMVStore extends TestBase {
assertEquals("Hello", m.get("1")); assertEquals("Hello", m.get("1"));
assertFalse(m0.isReadOnly()); assertFalse(m0.isReadOnly());
m.put("1", "Hallo"); m.put("1", "Hallo");
s.commit(); s.incrementVersion();
assertEquals(4, s.getCurrentVersion()); assertEquals(4, s.getCurrentVersion());
long v4 = s.store(); long v4 = s.store();
assertEquals(4, v4); assertEquals(4, v4);
...@@ -282,7 +360,7 @@ public class TestMVStore extends TestBase { ...@@ -282,7 +360,7 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
m2.put("" + i, "Test"); m2.put("" + i, "Test");
} }
long v1 = s.commit(); long v1 = s.incrementVersion();
assertEquals(1, v1); assertEquals(1, v1);
assertEquals(2, s.getCurrentVersion()); assertEquals(2, s.getCurrentVersion());
MVMap<String, String> m1 = s.openMap("data1", String.class, String.class); MVMap<String, String> m1 = s.openMap("data1", String.class, String.class);
...@@ -667,6 +745,12 @@ public class TestMVStore extends TestBase { ...@@ -667,6 +745,12 @@ public class TestMVStore extends TestBase {
s.close(); s.close();
} }
/**
* Open a store for the given file name, using a small page size.
*
* @param fileName the file name (null for in-memory)
* @return the store
*/
protected static MVStore openStore(String fileName) { protected static MVStore openStore(String fileName) {
MVStore store = MVStore.open(fileName, new TestMapFactory()); MVStore store = MVStore.open(fileName, new TestMapFactory());
store.setMaxPageSize(10); store.setMaxPageSize(10);
......
...@@ -47,6 +47,10 @@ public class FilePathCache extends FilePathWrapper { ...@@ -47,6 +47,10 @@ public class FilePathCache extends FilePathWrapper {
this.size = base.size(); this.size = base.size();
} }
protected void implCloseChannel() throws IOException {
base.close();
}
public FileChannel position(long newPosition) throws IOException { public FileChannel position(long newPosition) throws IOException {
this.pos = newPosition; this.pos = newPosition;
return this; return this;
......
...@@ -15,7 +15,7 @@ import java.util.Map; ...@@ -15,7 +15,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
* A scan resistent cache. It is meant to cache objects that are relatively * A scan resistant cache. It is meant to cache objects that are relatively
* costly to acquire, for example file content. * costly to acquire, for example file content.
* <p> * <p>
* This implementation is not multi-threading save. Null keys or null values are * This implementation is not multi-threading save. Null keys or null values are
...@@ -237,6 +237,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> implements Map<K, V> { ...@@ -237,6 +237,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> implements Map<K, V> {
* *
* @param key the key (may not be null) * @param key the key (may not be null)
* @param value the value (may not be null) * @param value the value (may not be null)
* @return the old value, or null if there is no resident entry
*/ */
public V put(K key, V value) { public V put(K key, V value) {
return put(key, value, averageMemory); return put(key, value, averageMemory);
...@@ -250,6 +251,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> implements Map<K, V> { ...@@ -250,6 +251,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> implements Map<K, V> {
* @param key the key (may not be null) * @param key the key (may not be null)
* @param value the value (may not be null) * @param value the value (may not be null)
* @param memory the memory used for the given entry * @param memory the memory used for the given entry
* @return the old value, or null if there is no resident entry
*/ */
public V put(K key, V value, int memory) { public V put(K key, V value, int memory) {
if (value == null) { if (value == null) {
...@@ -484,6 +486,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> implements Map<K, V> { ...@@ -484,6 +486,7 @@ public class CacheLIRS<K, V> extends AbstractMap<K, V> implements Map<K, V> {
/** /**
* Check whether there is a resident entry for the given key. * Check whether there is a resident entry for the given key.
* *
* @param key the key (may not be null)
* @return true if there is a resident entry * @return true if there is a resident entry
*/ */
public boolean containsKey(Object key) { public boolean containsKey(Object key) {
......
...@@ -6,9 +6,7 @@ ...@@ -6,9 +6,7 @@
*/ */
package org.h2.dev.store.btree; package org.h2.dev.store.btree;
import java.io.ByteArrayInputStream; import java.util.HashMap;
import java.io.IOException;
import java.util.Properties;
/** /**
* A chunk of data, containing one or multiple pages. * A chunk of data, containing one or multiple pages.
...@@ -23,7 +21,7 @@ import java.util.Properties; ...@@ -23,7 +21,7 @@ import java.util.Properties;
* 8 bytes: metaRootPos * 8 bytes: metaRootPos
* [ Page ] * * [ Page ] *
*/ */
class Chunk { public class Chunk {
/** /**
* The chunk id. * The chunk id.
...@@ -65,7 +63,7 @@ class Chunk { ...@@ -65,7 +63,7 @@ class Chunk {
*/ */
long version; long version;
Chunk(int id) { public Chunk(int id) {
this.id = id; this.id = id;
} }
...@@ -75,22 +73,17 @@ class Chunk { ...@@ -75,22 +73,17 @@ class Chunk {
* @param s the string * @param s the string
* @return the block * @return the block
*/ */
static Chunk fromString(String s) { public static Chunk fromString(String s) {
Properties prop = new Properties(); HashMap<String, String> map = DataUtils.parseMap(s);
try { int id = Integer.parseInt(map.get("id"));
prop.load(new ByteArrayInputStream(s.getBytes("UTF-8"))); Chunk c = new Chunk(id);
int id = Integer.parseInt(prop.get("id").toString()); c.start = Long.parseLong(map.get("start"));
Chunk c = new Chunk(id); c.length = Long.parseLong(map.get("length"));
c.start = Long.parseLong(prop.get("start").toString()); c.entryCount = Integer.parseInt(map.get("entryCount"));
c.length = Long.parseLong(prop.get("length").toString()); c.liveCount = Integer.parseInt(map.get("liveCount"));
c.entryCount = Integer.parseInt(prop.get("entryCount").toString()); c.metaRootPos = Long.parseLong(map.get("metaRoot"));
c.liveCount = Integer.parseInt(prop.get("liveCount").toString()); c.version = Long.parseLong(map.get("version"));
c.metaRootPos = Long.parseLong(prop.get("metaRoot").toString()); return c;
c.version = Long.parseLong(prop.get("version").toString());
return c;
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
public int getFillRate() { public int getFillRate() {
...@@ -107,13 +100,13 @@ class Chunk { ...@@ -107,13 +100,13 @@ class Chunk {
public String toString() { public String toString() {
return return
"id:" + id + "\n" + "id:" + id + "," +
"start:" + start + "\n" + "start:" + start + "," +
"length:" + length + "\n" + "length:" + length + "," +
"entryCount:" + entryCount + "\n" + "entryCount:" + entryCount + "," +
"liveCount:" + liveCount + "\n" + "liveCount:" + liveCount + "," +
"metaRoot:" + metaRootPos + "\n" + "metaRoot:" + metaRootPos + "," +
"version:" + version + "\n"; "version:" + version;
} }
} }
......
...@@ -26,6 +26,12 @@ public class Cursor<K, V> implements Iterator<K> { ...@@ -26,6 +26,12 @@ public class Cursor<K, V> implements Iterator<K> {
this.map = map; this.map = map;
} }
/**
* Fetch the first key.
*
* @param root the root page
* @param from the key, or null
*/
void start(Page root, K from) { void start(Page root, K from) {
currentPos = min(root, from); currentPos = min(root, from);
if (currentPos != null) { if (currentPos != null) {
...@@ -41,6 +47,9 @@ public class Cursor<K, V> implements Iterator<K> { ...@@ -41,6 +47,9 @@ public class Cursor<K, V> implements Iterator<K> {
return c == null ? null : c; return c == null ? null : c;
} }
/**
* Fetch the next key.
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected void fetchNext() { protected void fetchNext() {
current = (K) map.nextKey(currentPos, this); current = (K) map.nextKey(currentPos, this);
...@@ -54,19 +63,43 @@ public class Cursor<K, V> implements Iterator<K> { ...@@ -54,19 +63,43 @@ public class Cursor<K, V> implements Iterator<K> {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/**
* Add a cursor position to the stack.
*
* @param p the cursor position
*/
public void push(CursorPos p) { public void push(CursorPos p) {
parents.add(p); parents.add(p);
} }
/**
* Remove the latest cursor position from the stack and return it.
*
* @return the cursor position, or null if none
*/
public CursorPos pop() { public CursorPos pop() {
int size = parents.size(); int size = parents.size();
return size == 0 ? null : parents.remove(size - 1); return size == 0 ? null : parents.remove(size - 1);
} }
/**
* Visit the first key that is greater or equal the given key.
*
* @param p the page
* @param from the key, or null
* @return the cursor position
*/
public CursorPos min(Page p, K from) { public CursorPos min(Page p, K from) {
return map.min(p, this, from); return map.min(p, this, from);
} }
/**
* Visit the first key within this child page.
*
* @param p the page
* @param childIndex the child index
* @return the cursor position
*/
public CursorPos visitChild(Page p, int childIndex) { public CursorPos visitChild(Page p, int childIndex) {
p = p.getChildPage(childIndex); p = p.getChildPage(childIndex);
currentPos = min(p, null); currentPos = min(p, null);
......
...@@ -9,6 +9,8 @@ package org.h2.dev.store.btree; ...@@ -9,6 +9,8 @@ package org.h2.dev.store.btree;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.HashMap;
import org.h2.util.New;
/** /**
* Utility methods * Utility methods
...@@ -195,7 +197,14 @@ public class DataUtils { ...@@ -195,7 +197,14 @@ public class DataUtils {
} }
} }
static void readFully(FileChannel file, ByteBuffer buff) throws IOException { /**
* Read from a file channel until the target buffer is full, or end-of-file
* has been reached.
*
* @param file the file channel
* @param buff the target buffer
*/
public static void readFully(FileChannel file, ByteBuffer buff) throws IOException {
do { do {
int len = file.read(buff); int len = file.read(buff);
if (len < 0) { if (len < 0) {
...@@ -302,4 +311,70 @@ public class DataUtils { ...@@ -302,4 +311,70 @@ public class DataUtils {
return (short) ((x >> 16) ^ x); return (short) ((x >> 16) ^ x);
} }
/**
* Append a key-value pair to the string buffer. Keys may not contain a
* colon. Values that contain a comma or a double quote are enclosed in
* double quotes, with special characters escaped using a backslash.
*
* @param buff the target buffer
* @param key the key
* @param value the value
*/
public static void appendMap(StringBuilder buff, String key, Object value) {
if (buff.length() > 0) {
buff.append(',');
}
buff.append(key).append(':');
String v = value.toString();
if (v.indexOf(',') < 0 && v.indexOf('\"') < 0) {
buff.append(value);
} else {
buff.append('\"');
for (int i = 0, size = v.length(); i < size; i++) {
char c = v.charAt(i);
if (c == '\"') {
buff.append('\\');
}
buff.append(c);
}
buff.append('\"');
}
}
/**
* Parse a key-value pair list.
*
* @param s the list
* @return the map
*/
public static HashMap<String, String> parseMap(String s) {
HashMap<String, String> map = New.hashMap();
for (int i = 0, size = s.length(); i < size;) {
int startKey = i;
i = s.indexOf(':', i);
String key = s.substring(startKey, i++);
StringBuilder buff = new StringBuilder();
while (i < size) {
char c = s.charAt(i++);
if (c == ',') {
break;
} else if (c == '\"') {
while (i < size) {
c = s.charAt(i++);
if (c == '\\') {
i++;
} else if (c == '\"') {
break;
}
buff.append(c);
}
} else {
buff.append(c);
}
}
map.put(key, buff.toString());
}
return map;
}
} }
...@@ -23,9 +23,9 @@ public interface MapFactory { ...@@ -23,9 +23,9 @@ public interface MapFactory {
* @param createVersion when the map was created * @param createVersion when the map was created
* @return the map * @return the map
*/ */
<K, V> MVMap<K, V> buildMap( <K, V> MVMap<K, V> buildMap(
String mapType, MVStore store, int id, String name, String mapType, MVStore store, int id, String name,
DataType keyType, DataType valueType, long createVersion); DataType keyType, DataType valueType, long createVersion);
/** /**
* Parse the data type. * Parse the data type.
......
...@@ -15,18 +15,26 @@ import org.h2.compress.Compressor; ...@@ -15,18 +15,26 @@ import org.h2.compress.Compressor;
/** /**
* A page (a node or a leaf). * A page (a node or a leaf).
* <p> * <p>
* For b-tree 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
* child at the same index. * the child at the same index.
* <p> * <p>
* File format: page length (including length): int check value: short map id: * File format:
* varInt number of keys: varInt type: byte (0: leaf, 1: node; +2: compressed) * page length (including length): int
* compressed: bytes saved (varInt) keys leaf: values (one for each key) node: * check value: short
* children (1 more than keys) * map id: varInt
* number of keys: varInt
* type: byte (0: leaf, 1: node; +2: compressed)
* compressed: bytes saved (varInt)
* keys
* leaf: values (one for each key)
* node: children (1 more than keys)
*/ */
public class Page { public class Page {
private static final int SHARED_KEYS = 1, SHARED_VALUES = 2, SHARED_CHILDREN = 4, SHARED_COUNTS = 8; private static final int SHARED_KEYS = 1, SHARED_VALUES = 2, SHARED_CHILDREN = 4, SHARED_COUNTS = 8;
private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
private final MVMap<?, ?> map; private final MVMap<?, ?> map;
private final long version; private final long version;
private long pos; private long pos;
...@@ -54,14 +62,32 @@ public class Page { ...@@ -54,14 +62,32 @@ public class Page {
this.version = version; this.version = version;
} }
/**
* Create a new, empty page.
*
* @param map the map
* @param version the version
* @return the new page
*/
public static Page createEmpty(MVMap<?, ?> map, long version) {
return create(map, version, 0,
EMPTY_OBJECT_ARRAY, EMPTY_OBJECT_ARRAY,
null, null, null, 0, 0);
}
/** /**
* Create a new page. The arrays are not cloned. * Create a new page. The arrays are not cloned.
* *
* @param map the map * @param map the map
* @param version the version * @param version the version
* @param keyCount the number of keys
* @param keys the keys * @param keys the keys
* @param values the values * @param values the values
* @param children the children * @param children the children
* @param childrenPages the children pages
* @param counts the children counts
* @param totalCount the total number of keys
* @param sharedFlags which arrays are shared
* @return the page * @return the page
*/ */
public static Page create(MVMap<?, ?> map, long version, public static Page create(MVMap<?, ?> map, long version,
...@@ -84,9 +110,10 @@ public class Page { ...@@ -84,9 +110,10 @@ public class Page {
/** /**
* Read a page. * Read a page.
* *
* @param file the file
* @param map the map * @param map the map
* @param filePos the position in the file
* @param pos the page position * @param pos the page position
* @param buff the source buffer
* @return the page * @return the page
*/ */
static Page read(FileChannel file, MVMap<?, ?> map, static Page read(FileChannel file, MVMap<?, ?> map,
...@@ -114,27 +141,61 @@ public class Page { ...@@ -114,27 +141,61 @@ public class Page {
return p; return p;
} }
/**
* Get the key at the given index.
*
* @param index the index
* @return the key
*/
public Object getKey(int index) { public Object getKey(int index) {
return keys[index]; return keys[index];
} }
/**
* Get the child page at the given index.
*
* @param index the index
* @return the child page
*/
public Page getChildPage(int index) { public Page getChildPage(int index) {
Page p = childrenPages[index]; Page p = childrenPages[index];
return p != null ? p : map.readPage(children[index]); return p != null ? p : map.readPage(children[index]);
} }
/**
* Get the position of the child page at the given index.
*
* @param index the index
* @return the position
*/
long getChildPagePos(int index) { long getChildPagePos(int index) {
return children[index]; return children[index];
} }
public Object getValue(int x) { /**
return values[x]; * Get the value at the given index.
*
* @param index the index
* @return the value
*/
public Object getValue(int index) {
return values[index];
} }
/**
* Get the number of keys in this page.
*
* @return the number of keys
*/
public int getKeyCount() { public int getKeyCount() {
return keyCount; return keyCount;
} }
/**
* Check whether this is a leaf page.
*
* @return true if it is a leaf
*/
public boolean isLeaf() { public boolean isLeaf() {
return children == null; return children == null;
} }
...@@ -169,6 +230,13 @@ public class Page { ...@@ -169,6 +230,13 @@ public class Page {
return buff.toString(); return buff.toString();
} }
/**
* Create a copy of this page, if the write version is higher than the
* current version.
*
* @param writeVersion the write version
* @return a page with the given write version
*/
public Page copyOnWrite(long writeVersion) { public Page copyOnWrite(long writeVersion) {
if (version == writeVersion) { if (version == writeVersion) {
return this; return this;
...@@ -198,8 +266,9 @@ public class Page { ...@@ -198,8 +266,9 @@ public class Page {
if (x < 0 || x > high) { if (x < 0 || x > high) {
x = (low + high) >>> 1; x = (low + high) >>> 1;
} }
Object[] k = keys;
while (low <= high) { while (low <= high) {
int compare = map.compare(key, keys[x]); int compare = map.compare(key, k[x]);
if (compare > 0) { if (compare > 0) {
low = x + 1; low = x + 1;
} else if (compare < 0) { } else if (compare < 0) {
...@@ -229,7 +298,13 @@ public class Page { ...@@ -229,7 +298,13 @@ public class Page {
// return -(low + 1); // return -(low + 1);
} }
public Page split(int at) { /**
* Split the page. This modifies the current page.
*
* @param at the split index
* @return the page with the entries after the split index
*/
Page split(int at) {
return isLeaf() ? splitLeaf(at) : splitNode(at); return isLeaf() ? splitLeaf(at) : splitNode(at);
} }
...@@ -299,6 +374,11 @@ public class Page { ...@@ -299,6 +374,11 @@ public class Page {
return newPage; return newPage;
} }
/**
* Get the total number of key-value pairs, including child pages.
*
* @return the number of key-value pairs
*/
public long getTotalCount() { public long getTotalCount() {
if (MVStore.ASSERT) { if (MVStore.ASSERT) {
long check = 0; long check = 0;
...@@ -317,6 +397,12 @@ public class Page { ...@@ -317,6 +397,12 @@ public class Page {
return totalCount; return totalCount;
} }
/**
* Replace the child page.
*
* @param index the index
* @param c the new child page
*/
public void setChild(int index, Page c) { public void setChild(int index, Page c) {
if (c != childrenPages[index] || c.getPos() != children[index]) { if (c != childrenPages[index] || c.getPos() != children[index]) {
if ((sharedFlags & SHARED_CHILDREN) != 0) { if ((sharedFlags & SHARED_CHILDREN) != 0) {
...@@ -338,6 +424,12 @@ public class Page { ...@@ -338,6 +424,12 @@ public class Page {
} }
} }
/**
* Replace the key.
*
* @param index the index
* @param key the new key
*/
public void setKey(int index, Object key) { public void setKey(int index, Object key) {
if ((sharedFlags & SHARED_KEYS) != 0) { if ((sharedFlags & SHARED_KEYS) != 0) {
keys = Arrays.copyOf(keys, keys.length); keys = Arrays.copyOf(keys, keys.length);
...@@ -346,6 +438,13 @@ public class Page { ...@@ -346,6 +438,13 @@ public class Page {
keys[index] = key; keys[index] = key;
} }
/**
* Replace the value.
*
* @param index the index
* @param value the new value
* @return the old value
*/
public Object setValue(int index, Object value) { public Object setValue(int index, Object value) {
Object old = values[index]; Object old = values[index];
if ((sharedFlags & SHARED_VALUES) != 0) { if ((sharedFlags & SHARED_VALUES) != 0) {
...@@ -379,6 +478,13 @@ public class Page { ...@@ -379,6 +478,13 @@ public class Page {
map.getStore().removePage(pos); map.getStore().removePage(pos);
} }
/**
* Insert a key-value pair into this leaf.
*
* @param index the index
* @param key the key
* @param value the value
*/
public void insertLeaf(int index, Object key, Object value) { public void insertLeaf(int index, Object key, Object value) {
if (((sharedFlags & SHARED_KEYS) == 0) && keys.length > keyCount + 1) { if (((sharedFlags & SHARED_KEYS) == 0) && keys.length > keyCount + 1) {
if (index < keyCount) { if (index < keyCount) {
...@@ -401,6 +507,13 @@ public class Page { ...@@ -401,6 +507,13 @@ public class Page {
totalCount++; totalCount++;
} }
/**
* Insert a child into this node.
*
* @param index the index
* @param key the key
* @param childPage the child page
*/
public void insertNode(int index, Object key, Page childPage) { public void insertNode(int index, Object key, Page childPage) {
Object[] newKeys = new Object[keyCount + 1]; Object[] newKeys = new Object[keyCount + 1];
...@@ -428,6 +541,11 @@ public class Page { ...@@ -428,6 +541,11 @@ public class Page {
totalCount += childPage.getTotalCount(); totalCount += childPage.getTotalCount();
} }
/**
* Remove the key and value (or child) at the given index.
*
* @param index the index
*/
public void remove(int index) { public void remove(int index) {
int keyIndex = index >= keyCount ? index - 1 : index; int keyIndex = index >= keyCount ? index - 1 : index;
if ((sharedFlags & SHARED_KEYS) == 0 && keys.length > keyCount - 4) { if ((sharedFlags & SHARED_KEYS) == 0 && keys.length > keyCount - 4) {
...@@ -478,41 +596,6 @@ public class Page { ...@@ -478,41 +596,6 @@ public class Page {
} }
} }
// public void remove(int index) {
// Object[] newKeys = new Object[keyCount - 1];
// int keyIndex = index >= keyCount ? index - 1 : index;
// DataUtils.copyExcept(keys, newKeys, keyCount, keyIndex);
// keys = newKeys;
// sharedFlags &= ~SHARED_KEYS;
// if (values != null) {
// Object[] newValues = new Object[keyCount - 1];
// DataUtils.copyExcept(values, newValues, keyCount, index);
// values = newValues;
// sharedFlags &= ~SHARED_VALUES;
// totalCount--;
// }
// keyCount--;
// if (children != null) {
// long countOffset = counts[index];
//
// long[] newChildren = new long[children.length - 1];
// DataUtils.copyExcept(children, newChildren, children.length, index);
// children = newChildren;
//
// Page[] newChildrenPages = new Page[childrenPages.length - 1];
// DataUtils.copyExcept(childrenPages, newChildrenPages, childrenPages.length, index);
// childrenPages = newChildrenPages;
//
// long[] newCounts = new long[counts.length - 1];
// DataUtils.copyExcept(counts, newCounts,
// counts.length, index);
// counts = newCounts;
//
// sharedFlags &= ~(SHARED_CHILDREN | SHARED_COUNTS);
// totalCount -= countOffset;
// }
// }
private void read(ByteBuffer buff, int chunkId, int offset, int maxLength) { private void read(ByteBuffer buff, int chunkId, int offset, int maxLength) {
int start = buff.position(); int start = buff.position();
int pageLength = buff.getInt(); int pageLength = buff.getInt();
...@@ -701,10 +784,6 @@ public class Page { ...@@ -701,10 +784,6 @@ public class Page {
return count; return count;
} }
public long getCounts(int index) {
return counts[index];
}
long getVersion() { long getVersion() {
return version; return version;
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论