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

A persistent multi-version map: simplified cursor

上级 4b20cf9b
...@@ -7,11 +7,9 @@ ...@@ -7,11 +7,9 @@
package org.h2.test.store; package org.h2.test.store;
import java.util.ArrayList; import java.util.ArrayList;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.MVMap; import org.h2.dev.store.btree.MVMap;
import org.h2.dev.store.btree.MVStore; 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.dev.store.btree.Page;
import org.h2.util.New; import org.h2.util.New;
...@@ -399,63 +397,6 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -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() { public boolean isQuadraticSplit() {
return quadraticSplit; return quadraticSplit;
} }
...@@ -464,4 +405,8 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -464,4 +405,8 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
this.quadraticSplit = quadraticSplit; this.quadraticSplit = quadraticSplit;
} }
protected int getChildPageCount(Page p) {
return p.getChildPageCount() - 1;
}
} }
...@@ -184,21 +184,21 @@ public class TestMVStore extends TestBase { ...@@ -184,21 +184,21 @@ public class TestMVStore extends TestBase {
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.incrementVersion(); long old = 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++) { for (int i = 90; i < 93; i++) {
assertEquals("Hi", m.remove(i)); assertEquals("Hi", m.remove(i));
} }
assertEquals(null, m.put(100, "Hallo")); assertEquals(null, m.put(100, "Hallo"));
Iterator<Integer> it = m.changeIterator(s.getCurrentVersion()); Iterator<Integer> it = m.changeIterator(old);
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, 87, 88, 89, 100]", list.toString()); assertEquals("[10, 11, 12, 13, 14, 50, 100, 90, 91, 92]", list.toString());
s.close(); s.close();
} }
......
...@@ -6,57 +6,216 @@ ...@@ -6,57 +6,216 @@
*/ */
package org.h2.dev.store.btree; package org.h2.dev.store.btree;
import java.util.Iterator;
/** /**
* A cursor to iterate over all keys in new pages. * A cursor to iterate over all keys in new pages.
* *
* @param <K> the key type * @param <K> the key type
* @param <V> the value 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) { ChangeCursor(MVMap<K, V> map, Page root1, Page root2) {
super(map, root, from); this.map = map;
this.minVersion = minVersion; this.root1 = root1;
this.root2 = root2;
} }
public CursorPos min(Page p, K from) { public K next() {
while (p != null && p.getVersion() >= minVersion) { K c = current;
if (p.isLeaf()) { fetchNext();
return new CursorPos(p, 0, null); 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;
}
} }
for (int i = 0; i < p.getChildPageCount(); i++) { }
if (isChildOld(p, i)) {
private boolean fetchNextKey() {
while (true) {
if (state == 3) {
return false;
}
if (state == 1) {
// read from root1
pos1 = fetchNext(pos1);
if (pos1 == null) {
// reached the end of pos1
state = 2;
pos2 = null;
continue; continue;
} }
CursorPos c = new CursorPos(p, i, null); pos2 = find(root2, current);
push(c); if (pos2 == null) {
p = p.getChildPage(i); // 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; break;
} }
} }
return null; }
}
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;
}
}
} }
public CursorPos visitChild(Page p, int childIndex) { private CursorPos find(Page p, K key) {
if (isChildOld(p, childIndex)) { // 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 null;
} }
return super.visitChild(p, childIndex); 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);
}
} }
private boolean isChildOld(Page p, int childIndex) { @SuppressWarnings("unchecked")
long pos = p.getChildPagePos(childIndex); private CursorPos fetchNext(CursorPos p) {
if (pos == 0) { while (p != null) {
Page c = p.getChildPage(childIndex); if (p.index < p.page.getKeyCount()) {
if (c.getVersion() < minVersion) { current = (K) p.page.getKey(p.index++);
return true; return p;
} }
} else if (map.getStore().getChunk(pos).version < minVersion) { p = p.parent;
return true; 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);
}
}
current = null;
return p;
}
private static CursorPos min(CursorPos p) {
while (true) {
if (p.page.isLeaf()) {
return p;
}
Page c = p.page.getChildPage(0);
p = new CursorPos(c, 0, p);
} }
return false;
} }
} }
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
*/ */
package org.h2.dev.store.btree; package org.h2.dev.store.btree;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
/** /**
...@@ -17,12 +16,11 @@ import java.util.Iterator; ...@@ -17,12 +16,11 @@ import java.util.Iterator;
*/ */
public class Cursor<K, V> implements Iterator<K> { public class Cursor<K, V> implements Iterator<K> {
protected final MVMap<K, V> map; private final MVMap<K, V> map;
protected final Page root; private final K from;
protected final K from; private Page root;
protected ArrayList<CursorPos> parents; private CursorPos pos;
protected CursorPos currentPos; private K current;
protected K current;
Cursor(MVMap<K, V> map, Page root, K from) { Cursor(MVMap<K, V> map, Page root, K from) {
this.map = map; this.map = map;
...@@ -31,33 +29,18 @@ public class Cursor<K, V> implements Iterator<K> { ...@@ -31,33 +29,18 @@ public class Cursor<K, V> implements Iterator<K> {
} }
public K next() { public K next() {
if (!hasNext()) {
return null;
}
K c = current; K c = current;
if (c != null) {
fetchNext(); fetchNext();
} return c;
return c == null ? null : c;
}
/**
* Fetch the next key.
*/
@SuppressWarnings("unchecked")
protected void fetchNext() {
current = (K) map.nextKey(currentPos, this);
} }
public boolean hasNext() { public boolean hasNext() {
if (parents == null) { if (root != null) {
// not initialized yet: fetch the first key // initialize
parents = new ArrayList<CursorPos>(); min(root, from);
currentPos = min(root, from); root = null;
if (currentPos != null) {
fetchNext(); fetchNext();
} }
}
return current != null; return current != null;
} }
...@@ -65,48 +48,43 @@ public class Cursor<K, V> implements Iterator<K> { ...@@ -65,48 +48,43 @@ public class Cursor<K, V> implements Iterator<K> {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
/** private void min(Page p, K from) {
* Add a cursor position to the stack. while (true) {
* if (p.isLeaf()) {
* @param p the cursor position int x = from == null ? 0 : p.binarySearch(from);
*/ if (x < 0) {
public void push(CursorPos p) { x = -x - 1;
parents.add(p);
} }
pos = new CursorPos(p, x, pos);
/** break;
* Remove the latest cursor position from the stack and return it. }
* int x = from == null ? -1 : p.binarySearch(from);
* @return the cursor position, or null if none if (x < 0) {
*/ x = -x - 1;
public CursorPos pop() { } else {
int size = parents.size(); x++;
return size == 0 ? null : parents.remove(size - 1); }
pos = new CursorPos(p, x + 1, pos);
p = p.getChildPage(x);
} }
/**
* 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);
} }
/** @SuppressWarnings("unchecked")
* Visit the first key within this child page. private void fetchNext() {
* while (pos != null) {
* @param p the page if (pos.index < pos.page.getKeyCount()) {
* @param childIndex the child index current = (K) pos.page.getKey(pos.index++);
* @return the cursor position return;
*/ }
public CursorPos visitChild(Page p, int childIndex) { pos = pos.parent;
p = p.getChildPage(childIndex); if (pos == null) {
currentPos = min(p, null); break;
return currentPos; }
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> { ...@@ -137,68 +137,6 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
return (V) binarySearch(root, key); 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. * 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> { ...@@ -443,20 +381,20 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
*/ */
public Iterator<K> keyIterator(K from) { public Iterator<K> keyIterator(K from) {
checkOpen(); checkOpen();
return new RangeCursor<K, V>(root, from); return new Cursor<K, V>(this, 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. * 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 * @return the iterator
*/ */
public Iterator<K> changeIterator(long minVersion) { public Iterator<K> changeIterator(long version) {
checkOpen(); 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() { public Set<Map.Entry<K, V>> entrySet() {
...@@ -469,13 +407,13 @@ public class MVMap<K, V> extends AbstractMap<K, V> { ...@@ -469,13 +407,13 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
public Set<K> keySet() { public Set<K> keySet() {
checkOpen(); checkOpen();
final MVMap<K, V> map = this;
final Page root = this.root; final Page root = this.root;
return new AbstractSet<K>() { return new AbstractSet<K>() {
@Override @Override
public Iterator<K> iterator() { public Iterator<K> iterator() {
return new RangeCursor<K, V>(root, null); return new Cursor<K, V>(map, root, null);
// return new Cursor<K, V>(MVMap.this, root, null);
} }
@Override @Override
...@@ -701,4 +639,8 @@ public class MVMap<K, V> extends AbstractMap<K, V> { ...@@ -701,4 +639,8 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
return root.getVersion(); return root.getVersion();
} }
protected int getChildPageCount(Page p) {
return p.getChildPageCount();
}
} }
...@@ -788,8 +788,25 @@ public class Page { ...@@ -788,8 +788,25 @@ public class Page {
return version; return version;
} }
int getChildPageCount() { public int getChildPageCount() {
return children.length; 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论