提交 49b85b79 authored 作者: Thomas Mueller's avatar Thomas Mueller

A persistent multi-version map: simplified cursor

上级 4b20cf9b
......@@ -7,11 +7,9 @@
package org.h2.test.store;
import java.util.ArrayList;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.MVMap;
import org.h2.dev.store.btree.MVStore;
import org.h2.dev.store.btree.Cursor;
import org.h2.dev.store.btree.CursorPos;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.Page;
import org.h2.util.New;
......@@ -399,63 +397,6 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
}
}
/**
* Go to the first element for the given key.
*
* @param p the current page
* @param cursor the cursor
* @param key the key
* @return the cursor position
*/
protected CursorPos min(Page p, Cursor<K, V> cursor, Object key) {
if (p == null) {
return null;
}
while (true) {
CursorPos c = new CursorPos(p, 0, null);
if (p.isLeaf()) {
return c;
}
cursor.push(c);
p = p.getChildPage(0);
}
}
/**
* Get the next key.
*
* @param p the cursor position
* @param cursor the cursor
* @return the next key
*/
protected Object nextKey(CursorPos p, Cursor<K, V> cursor) {
while (p != null) {
int index = p.index++;
Page x = p.page;
if (index < x.getKeyCount()) {
return x.getKey(index);
}
while (true) {
p = cursor.pop();
if (p == null) {
break;
}
index = ++p.index;
x = p.page;
// this is different from a b-tree:
// we have one less child
if (index < x.getKeyCount()) {
cursor.push(p);
p = cursor.visitChild(x, index);
if (p != null) {
break;
}
}
}
}
return null;
}
public boolean isQuadraticSplit() {
return quadraticSplit;
}
......@@ -464,4 +405,8 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
this.quadraticSplit = quadraticSplit;
}
protected int getChildPageCount(Page p) {
return p.getChildPageCount() - 1;
}
}
......@@ -184,21 +184,21 @@ public class TestMVStore extends TestBase {
for (int i = 20; i < 40; i++) {
assertEquals("Hi", m.put(i, "Hello"));
}
s.incrementVersion();
long old = s.incrementVersion();
for (int i = 10; i < 15; i++) {
m.put(i, "Hallo");
}
m.put(50, "Hallo");
for (int i = 90; i < 100; i++) {
for (int i = 90; i < 93; i++) {
assertEquals("Hi", m.remove(i));
}
assertEquals(null, m.put(100, "Hallo"));
Iterator<Integer> it = m.changeIterator(s.getCurrentVersion());
Iterator<Integer> it = m.changeIterator(old);
ArrayList<Integer> list = New.arrayList();
while (it.hasNext()) {
list.add(it.next());
}
assertEquals("[9, 10, 11, 12, 13, 14, 48, 49, 50, 87, 88, 89, 100]", list.toString());
assertEquals("[10, 11, 12, 13, 14, 50, 100, 90, 91, 92]", list.toString());
s.close();
}
......
......@@ -6,57 +6,216 @@
*/
package org.h2.dev.store.btree;
import java.util.Iterator;
/**
* A cursor to iterate over all keys in new pages.
*
* @param <K> the key type
* @param <V> the value type
*/
public class ChangeCursor<K, V> extends Cursor<K, V> {
public class ChangeCursor<K, V> implements Iterator<K> {
private final MVMap<K, V> map;
private final Page root1, root2;
private final long minVersion;
/**
* The state of this cursor.
* 0: not initialized
* 1: reading from root1
* 2: reading from root2
* 3: closed
*/
private int state;
private CursorPos pos1, pos2;
private K current;
ChangeCursor(MVMap<K, V> map, Page root, K from, long minVersion) {
super(map, root, from);
this.minVersion = minVersion;
ChangeCursor(MVMap<K, V> map, Page root1, Page root2) {
this.map = map;
this.root1 = root1;
this.root2 = root2;
}
public CursorPos min(Page p, K from) {
while (p != null && p.getVersion() >= minVersion) {
if (p.isLeaf()) {
return new CursorPos(p, 0, null);
public K next() {
K c = current;
fetchNext();
return c;
}
public boolean hasNext() {
if (state == 0) {
pos1 = new CursorPos(root1, 0, null);
pos1 = min(pos1);
state = 1;
fetchNext();
}
return current != null;
}
public void remove() {
throw new UnsupportedOperationException();
}
private void fetchNext() {
while (fetchNextKey()) {
if (pos1 == null || pos2 == null) {
break;
}
@SuppressWarnings("unchecked")
V v1 = (V) map.binarySearch(root1, current);
@SuppressWarnings("unchecked")
V v2 = (V) map.binarySearch(root2, current);
if (!v1.equals(v2)) {
break;
}
}
}
private boolean fetchNextKey() {
while (true) {
if (state == 3) {
return false;
}
for (int i = 0; i < p.getChildPageCount(); i++) {
if (isChildOld(p, i)) {
if (state == 1) {
// read from root1
pos1 = fetchNext(pos1);
if (pos1 == null) {
// reached the end of pos1
state = 2;
pos2 = null;
continue;
}
CursorPos c = new CursorPos(p, i, null);
push(c);
p = p.getChildPage(i);
break;
pos2 = find(root2, current);
if (pos2 == null) {
// not found in root2
return true;
}
if (!pos1.page.equals(pos2.page)) {
// the page is different,
// so the entry has possibly changed
return true;
}
while (true) {
pos1 = pos1.parent;
if (pos1 == null) {
// reached end of pos1
state = 2;
pos2 = null;
break;
}
pos2 = pos2.parent;
if (pos2 == null || !pos1.page.equals(pos2.page.getPos())) {
if (pos1.index + 1 < map.getChildPageCount(pos1.page)) {
pos1 = new CursorPos(pos1.page.getChildPage(++pos1.index), 0, pos1);
pos1 = min(pos1);
break;
}
}
}
}
if (state == 2) {
if (pos2 == null) {
// init reading from root2
pos2 = new CursorPos(root2, 0, null);
pos2 = min(pos2);
}
// read from root2
pos2 = fetchNext(pos2);
if (pos2 == null) {
// reached the end of pos2
state = 3;
current = null;
continue;
}
pos1 = find(root1, current);
if (pos1 != null) {
// found a corresponding record
// so it was not deleted
// but now we may need to skip pages
if (!pos1.page.equals(pos2.page)) {
// the page is different
pos1 = null;
continue;
}
while (true) {
pos2 = pos2.parent;
if (pos2 == null) {
// reached end of pos1
state = 3;
current = null;
pos1 = null;
break;
}
pos1 = pos1.parent;
if (pos1 == null || !pos2.page.equals(pos1.page.getPos())) {
if (pos2.index + 1 < map.getChildPageCount(pos2.page)) {
pos2 = new CursorPos(pos2.page.getChildPage(++pos2.index), 0, pos2);
pos2 = min(pos2);
break;
}
}
}
pos1 = null;
continue;
}
// found no corresponding record
// so it was deleted
return true;
}
}
}
private CursorPos find(Page p, K key) {
// TODO combine with RangeCursor.min
// possibly move to MVMap
CursorPos pos = null;
while (true) {
if (p.isLeaf()) {
int x = key == null ? 0 : p.binarySearch(key);
if (x < 0) {
return null;
}
return new CursorPos(p, x, pos);
}
int x = key == null ? -1 : p.binarySearch(key);
if (x < 0) {
x = -x - 1;
} else {
x++;
}
pos = new CursorPos(p, x, pos);
p = p.getChildPage(x);
}
return null;
}
public CursorPos visitChild(Page p, int childIndex) {
if (isChildOld(p, childIndex)) {
return null;
@SuppressWarnings("unchecked")
private CursorPos fetchNext(CursorPos p) {
while (p != null) {
if (p.index < p.page.getKeyCount()) {
current = (K) p.page.getKey(p.index++);
return p;
}
p = p.parent;
if (p == null) {
break;
}
if (p.index + 1 < map.getChildPageCount(p.page)) {
p = new CursorPos(p.page.getChildPage(++p.index), 0, p);
p = min(p);
}
}
return super.visitChild(p, childIndex);
current = null;
return p;
}
private boolean isChildOld(Page p, int childIndex) {
long pos = p.getChildPagePos(childIndex);
if (pos == 0) {
Page c = p.getChildPage(childIndex);
if (c.getVersion() < minVersion) {
return true;
private static CursorPos min(CursorPos p) {
while (true) {
if (p.page.isLeaf()) {
return p;
}
} else if (map.getStore().getChunk(pos).version < minVersion) {
return true;
Page c = p.page.getChildPage(0);
p = new CursorPos(c, 0, p);
}
return false;
}
}
......@@ -6,7 +6,6 @@
*/
package org.h2.dev.store.btree;
import java.util.ArrayList;
import java.util.Iterator;
/**
......@@ -17,12 +16,11 @@ import java.util.Iterator;
*/
public class Cursor<K, V> implements Iterator<K> {
protected final MVMap<K, V> map;
protected final Page root;
protected final K from;
protected ArrayList<CursorPos> parents;
protected CursorPos currentPos;
protected K current;
private final MVMap<K, V> map;
private final K from;
private Page root;
private CursorPos pos;
private K current;
Cursor(MVMap<K, V> map, Page root, K from) {
this.map = map;
......@@ -31,32 +29,17 @@ public class Cursor<K, V> implements Iterator<K> {
}
public K next() {
if (!hasNext()) {
return null;
}
K c = current;
if (c != null) {
fetchNext();
}
return c == null ? null : c;
}
/**
* Fetch the next key.
*/
@SuppressWarnings("unchecked")
protected void fetchNext() {
current = (K) map.nextKey(currentPos, this);
fetchNext();
return c;
}
public boolean hasNext() {
if (parents == null) {
// not initialized yet: fetch the first key
parents = new ArrayList<CursorPos>();
currentPos = min(root, from);
if (currentPos != null) {
fetchNext();
}
if (root != null) {
// initialize
min(root, from);
root = null;
fetchNext();
}
return current != null;
}
......@@ -65,48 +48,43 @@ public class Cursor<K, V> implements Iterator<K> {
throw new UnsupportedOperationException();
}
/**
* Add a cursor position to the stack.
*
* @param p the cursor position
*/
public void push(CursorPos 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() {
int size = parents.size();
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) {
return map.min(p, this, from);
private void min(Page p, K from) {
while (true) {
if (p.isLeaf()) {
int x = from == null ? 0 : p.binarySearch(from);
if (x < 0) {
x = -x - 1;
}
pos = new CursorPos(p, x, pos);
break;
}
int x = from == null ? -1 : p.binarySearch(from);
if (x < 0) {
x = -x - 1;
} else {
x++;
}
pos = new CursorPos(p, x + 1, pos);
p = p.getChildPage(x);
}
}
/**
* 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) {
p = p.getChildPage(childIndex);
currentPos = min(p, null);
return currentPos;
@SuppressWarnings("unchecked")
private void fetchNext() {
while (pos != null) {
if (pos.index < pos.page.getKeyCount()) {
current = (K) pos.page.getKey(pos.index++);
return;
}
pos = pos.parent;
if (pos == null) {
break;
}
if (pos.index < map.getChildPageCount(pos.page)) {
min(pos.page.getChildPage(pos.index++), null);
}
}
current = null;
}
}
......@@ -137,68 +137,6 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
return (V) binarySearch(root, key);
}
/**
* Go to the first element for the given key.
*
* @param p the current page
* @param cursor the cursor
* @param key the key
* @return the cursor position
*/
protected CursorPos min(Page p, Cursor<K, V> cursor, Object key) {
while (true) {
if (p.isLeaf()) {
int x = key == null ? 0 : p.binarySearch(key);
if (x < 0) {
x = -x - 1;
}
return new CursorPos(p, x, null);
}
int x = key == null ? -1 : p.binarySearch(key);
if (x < 0) {
x = -x - 1;
} else {
x++;
}
CursorPos c = new CursorPos(p, x, null);
cursor.push(c);
p = p.getChildPage(x);
}
}
/**
* Get the next key.
*
* @param p the cursor position
* @param cursor the cursor
* @return the next key
*/
protected Object nextKey(CursorPos p, Cursor<K, V> cursor) {
while (p != null) {
int index = p.index++;
Page x = p.page;
if (index < x.getKeyCount()) {
return x.getKey(index);
}
while (true) {
p = cursor.pop();
if (p == null) {
break;
}
index = ++p.index;
x = p.page;
if (index <= x.getKeyCount()) {
cursor.push(p);
p = cursor.visitChild(x, index);
if (p != null) {
break;
}
}
}
}
return null;
}
/**
* Get the value for the given key, or null if not found.
*
......@@ -443,20 +381,20 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
*/
public Iterator<K> keyIterator(K from) {
checkOpen();
return new RangeCursor<K, V>(root, from);
return new Cursor<K, V>(this, root, from);
// return new Cursor<K, V>(this, root, from);
}
/**
* Iterate over all keys in changed pages.
* This does not include deleted deleted pages.
*
* @param minVersion the minimum version
* @param version the old version
* @return the iterator
*/
public Iterator<K> changeIterator(long minVersion) {
public Iterator<K> changeIterator(long version) {
checkOpen();
return new ChangeCursor<K, V>(this, root, null, minVersion);
MVMap<K, V> old = openVersion(version);
return new ChangeCursor<K, V>(this, root, old.root);
}
public Set<Map.Entry<K, V>> entrySet() {
......@@ -469,13 +407,13 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
public Set<K> keySet() {
checkOpen();
final MVMap<K, V> map = this;
final Page root = this.root;
return new AbstractSet<K>() {
@Override
public Iterator<K> iterator() {
return new RangeCursor<K, V>(root, null);
// return new Cursor<K, V>(MVMap.this, root, null);
return new Cursor<K, V>(map, root, null);
}
@Override
......@@ -701,4 +639,8 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
return root.getVersion();
}
protected int getChildPageCount(Page p) {
return p.getChildPageCount();
}
}
......@@ -788,8 +788,25 @@ public class Page {
return version;
}
int getChildPageCount() {
public int getChildPageCount() {
return children.length;
}
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other instanceof Page) {
if (pos != 0 && ((Page) other).pos == pos) {
return true;
}
return this == other;
}
return false;
}
public int hashCode() {
return pos != 0 ? (int) (pos | (pos >>> 32)) : super.hashCode();
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.dev.store.btree;
import java.util.Iterator;
/**
* A cursor to iterate over elements in ascending order.
*
* @param <K> the key type
* @param <V> the value type
*/
public class RangeCursor<K, V> implements Iterator<K> {
protected final K from;
protected Page root;
protected CursorPos pos;
protected K current;
RangeCursor(Page root, K from) {
this.root = root;
this.from = from;
}
public K next() {
K c = current;
fetchNext();
return c;
}
public boolean hasNext() {
if (root != null) {
// initialize
min(root, from);
root = null;
fetchNext();
}
return current != null;
}
public void remove() {
throw new UnsupportedOperationException();
}
private void min(Page p, K from) {
while (true) {
if (p.isLeaf()) {
int x = from == null ? 0 : p.binarySearch(from);
if (x < 0) {
x = -x - 1;
}
pos = new CursorPos(p, x, pos);
break;
}
int x = from == null ? -1 : p.binarySearch(from);
if (x < 0) {
x = -x - 1;
} else {
x++;
}
pos = new CursorPos(p, x + 1, pos);
p = p.getChildPage(x);
}
}
@SuppressWarnings("unchecked")
private void fetchNext() {
while (pos != null) {
if (pos.index < pos.page.getKeyCount()) {
current = (K) pos.page.getKey(pos.index++);
return;
}
pos = pos.parent;
if (pos == null) {
break;
}
if (pos.index < pos.page.getChildPageCount()) {
min(pos.page.getChildPage(pos.index++), null);
}
}
current = null;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论