Commit 20e8f37b authored by Kirill Smelkov's avatar Kirill Smelkov

go/zodb/btree: Change V<op> family to also provide visited node key coverage on visit callback

WCFS needs to know key coverage for every visited node.

WARNING: this is API change.
parent 8e90f62f
...@@ -177,34 +177,54 @@ func (t *BTree) Get(ctx context.Context, key KEY) (_ interface{}, _ bool, err er ...@@ -177,34 +177,54 @@ func (t *BTree) Get(ctx context.Context, key KEY) (_ interface{}, _ bool, err er
// VGet is like Get but also calls visit while traversing the tree. // VGet is like Get but also calls visit while traversing the tree.
// //
// Visit is called with node being activated. // Visit is called with node being activated.
func (t *BTree) VGet(ctx context.Context, key KEY, visit func(node Node)) (_ interface{}, _ bool, err error) { func (t *BTree) VGet(ctx context.Context, key KEY, visit func(node Node, keycov KeyRange)) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key) defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key)
err = t.PActivate(ctx) err = t.PActivate(ctx)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
keycov := KeyRange{Lo: _KeyMin, Hi_: _KeyMax}
if visit != nil { if visit != nil {
visit(t) visit(t, keycov)
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return nil, false, nil
} }
for { for {
l := len(t.data)
if l == 0 {
// empty btree (top, or in leaf)
t.PDeactivate()
return nil, false, nil
}
// search i: K(i) ≤ k < K(i+1) ; K(0) = -∞, K(len) = +∞ // search i: K(i) ≤ k < K(i+1) ; K(0) = -∞, K(len) = +∞
i := sort.Search(len(t.data), func(i int) bool { i := sort.Search(l, func(i int) bool {
j := i + 1 j := i + 1
if j == len(t.data) { if j == len(t.data) {
return true // [len].key = +∞ return true // [len].key = +∞
} }
return key < t.data[j].key return key < t.data[j].key
}) })
// i < l
child := t.data[i].child child := t.data[i].child
// shorten global keycov by local [lo,hi) for this child
lo := _KeyMin
if i > 0 {
lo = t.data[i].key
}
i++
hi_ := _KeyMax
if i < l {
hi_ = t.data[i].key
}
if hi_ != _KeyMax {
hi_--
}
keycov.Lo = kmax(keycov.Lo, lo)
keycov.Hi_ = kmin(keycov.Hi_, hi_)
t.PDeactivate() t.PDeactivate()
err = child.PActivate(ctx) err = child.PActivate(ctx)
if err != nil { if err != nil {
...@@ -212,7 +232,7 @@ func (t *BTree) VGet(ctx context.Context, key KEY, visit func(node Node)) (_ int ...@@ -212,7 +232,7 @@ func (t *BTree) VGet(ctx context.Context, key KEY, visit func(node Node)) (_ int
} }
if visit != nil { if visit != nil {
visit(child) visit(child, keycov)
} }
switch child := child.(type) { switch child := child.(type) {
...@@ -255,26 +275,40 @@ func (t *BTree) MinKey(ctx context.Context) (_ KEY, ok bool, err error) { ...@@ -255,26 +275,40 @@ func (t *BTree) MinKey(ctx context.Context) (_ KEY, ok bool, err error) {
// VMinKey is like MinKey but also calls visit while traversing the tree. // VMinKey is like MinKey but also calls visit while traversing the tree.
// //
// Visit is called with node being activated. // Visit is called with node being activated.
func (t *BTree) VMinKey(ctx context.Context, visit func(node Node)) (_ KEY, ok bool, err error) { func (t *BTree) VMinKey(ctx context.Context, visit func(node Node, keycov KeyRange)) (_ KEY, ok bool, err error) {
defer xerr.Contextf(&err, "btree(%s): minkey", t.POid()) defer xerr.Contextf(&err, "btree(%s): minkey", t.POid())
err = t.PActivate(ctx) err = t.PActivate(ctx)
if err != nil { if err != nil {
return 0, false, err return 0, false, err
} }
keycov := KeyRange{Lo: _KeyMin, Hi_: _KeyMax}
if visit != nil { if visit != nil {
visit(t) visit(t, keycov)
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return 0, false, nil
} }
// NOTE -> can also use t.firstbucket // NOTE -> can also use t.firstbucket
for { for {
l := len(t.data)
if l == 0 {
// empty btree (top, or in leaf)
t.PDeactivate()
return 0, false, nil
}
child := t.data[0].child child := t.data[0].child
// shorten global keycov by local hi) for this child
hi_ := _KeyMax
if 1 < l {
hi_ = t.data[1].key
}
if hi_ != _KeyMax {
hi_--
}
// keycov.Lo stays -∞
keycov.Hi_ = kmin(keycov.Hi_, hi_)
t.PDeactivate() t.PDeactivate()
err = child.PActivate(ctx) err = child.PActivate(ctx)
if err != nil { if err != nil {
...@@ -282,7 +316,7 @@ func (t *BTree) VMinKey(ctx context.Context, visit func(node Node)) (_ KEY, ok b ...@@ -282,7 +316,7 @@ func (t *BTree) VMinKey(ctx context.Context, visit func(node Node)) (_ KEY, ok b
} }
if visit != nil { if visit != nil {
visit(child) visit(child, keycov)
} }
switch child := child.(type) { switch child := child.(type) {
...@@ -308,26 +342,36 @@ func (t *BTree) MaxKey(ctx context.Context) (_ KEY, _ bool, err error) { ...@@ -308,26 +342,36 @@ func (t *BTree) MaxKey(ctx context.Context) (_ KEY, _ bool, err error) {
// VMaxKey is like MaxKey but also calls visit while traversing the tree. // VMaxKey is like MaxKey but also calls visit while traversing the tree.
// //
// Visit is called with node being activated. // Visit is called with node being activated.
func (t *BTree) VMaxKey(ctx context.Context, visit func(node Node)) (_ KEY, _ bool, err error) { func (t *BTree) VMaxKey(ctx context.Context, visit func(node Node, keycov KeyRange)) (_ KEY, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): maxkey", t.POid()) defer xerr.Contextf(&err, "btree(%s): maxkey", t.POid())
err = t.PActivate(ctx) err = t.PActivate(ctx)
if err != nil { if err != nil {
return 0, false, err return 0, false, err
} }
keycov := KeyRange{Lo: _KeyMin, Hi_: _KeyMax}
if visit != nil { if visit != nil {
visit(t) visit(t, keycov)
}
l := len(t.data)
if l == 0 {
// empty btree
t.PDeactivate()
return 0, false, nil
} }
for { for {
l := len(t.data)
if l == 0 {
// empty btree (top, or in leaf)
t.PDeactivate()
return 0, false, nil
}
child := t.data[l-1].child child := t.data[l-1].child
// shorten global keycov by local [lo for this chile
lo := _KeyMin
if l-1 > 0 {
lo = t.data[l-1].key
}
keycov.Lo = kmax(keycov.Lo, lo)
// keycov.Hi_ stays ∞
t.PDeactivate() t.PDeactivate()
err = child.PActivate(ctx) err = child.PActivate(ctx)
if err != nil { if err != nil {
...@@ -335,7 +379,7 @@ func (t *BTree) VMaxKey(ctx context.Context, visit func(node Node)) (_ KEY, _ bo ...@@ -335,7 +379,7 @@ func (t *BTree) VMaxKey(ctx context.Context, visit func(node Node)) (_ KEY, _ bo
} }
if visit != nil { if visit != nil {
visit(child) visit(child, keycov)
} }
switch child := child.(type) { switch child := child.(type) {
...@@ -678,6 +722,22 @@ func (r KeyRange) String() string { ...@@ -678,6 +722,22 @@ func (r KeyRange) String() string {
} }
func kmin(a, b KEY) KEY {
if a < b {
return a
} else {
return b
}
}
func kmax(a, b KEY) KEY {
if a > b {
return a
} else {
return b
}
}
// kstr formats key as string. // kstr formats key as string.
func kstr(k KEY) string { func kstr(k KEY) string {
if k == _KeyMin { if k == _KeyMin {
......
...@@ -23,6 +23,7 @@ package btree ...@@ -23,6 +23,7 @@ package btree
import ( import (
"context" "context"
"reflect"
"testing" "testing"
"lab.nexedi.com/kirr/go123/exc" "lab.nexedi.com/kirr/go123/exc"
...@@ -98,6 +99,12 @@ func (b *bucketWrap) MaxKey(ctx context.Context) (k int64, ok bool, err error) { ...@@ -98,6 +99,12 @@ func (b *bucketWrap) MaxKey(ctx context.Context) (k int64, ok bool, err error) {
return return
} }
// tVisit is information about one visit call.
type tVisit struct {
node zodb.Oid
keycov LKeyRange
}
func TestBTree(t *testing.T) { func TestBTree(t *testing.T) {
X := exc.Raiseif X := exc.Raiseif
ctx := context.Background() ctx := context.Background()
...@@ -263,4 +270,39 @@ func TestBTree(t *testing.T) { ...@@ -263,4 +270,39 @@ func TestBTree(t *testing.T) {
// XXX verify FirstBucket / Next ? // XXX verify FirstBucket / Next ?
verifyFirstBucket(B3) verifyFirstBucket(B3)
// verify nodes/keycov visited through VGet/V{Min,Max}Key
xBv, err := conn.Get(ctx, Bv_oid); X(err)
Bv, ok := xBv.(*LOBTree)
if !ok {
t.Fatalf("Bv: %v; got %T; want LOBTree", Bv_oid, xBv)
}
for k, visitOK := range Bvdict {
visit := []tVisit{}
_, _, err := Bv.VGet(ctx, k, func(node LONode, keycov LKeyRange) {
visit = append(visit, tVisit{node.POid(), keycov})
}); X(err)
if !reflect.DeepEqual(visit, visitOK) {
t.Errorf("VGet(%d): visit:\nhave: %v\nwant: %v", k, visit, visitOK)
}
}
visitMinOK := Bvdict[Bv_kmin]
visitMaxOK := Bvdict[Bv_kmax]
visitMin := []tVisit{}
visitMax := []tVisit{}
_, _, err = Bv.VMinKey(ctx, func(node LONode, keycov LKeyRange) {
visitMin = append(visitMin, tVisit{node.POid(), keycov})
}); X(err)
_, _, err = Bv.VMaxKey(ctx, func(node LONode, keycov LKeyRange) {
visitMax = append(visitMax, tVisit{node.POid(), keycov})
}); X(err)
if !reflect.DeepEqual(visitMin, visitMinOK) {
t.Errorf("VMinKey(): visit:\nhave: %v\nwant: %v", visitMin, visitMinOK)
}
if !reflect.DeepEqual(visitMax, visitMaxOK) {
t.Errorf("VMaxKey(): visit:\nhave: %v\nwant: %v", visitMax, visitMaxOK)
}
} }
...@@ -51,5 +51,7 @@ sed \ ...@@ -51,5 +51,7 @@ sed \
-e "s/\b_KeyMin\b/_${KEYKIND}KeyMin/g" \ -e "s/\b_KeyMin\b/_${KEYKIND}KeyMin/g" \
-e "s/\b_KeyMax\b/_${KEYKIND}KeyMax/g" \ -e "s/\b_KeyMax\b/_${KEYKIND}KeyMax/g" \
-e "s/\bKeyRange\b/${KEYKIND}KeyRange/g" \ -e "s/\bKeyRange\b/${KEYKIND}KeyRange/g" \
-e "s/\bkmin\b/${keykind}kmin/g" \
-e "s/\bkmax\b/${keykind}kmax/g" \
-e "s/\bkstr\b/${keykind}kstr/g" \ -e "s/\bkstr\b/${keykind}kstr/g" \
$input >>$out $input >>$out
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
from ZODB.DB import DB from ZODB.DB import DB
from BTrees.LOBTree import LOBucket, LOBTree from BTrees.LOBTree import LOBucket, LOBTree
from BTrees.check import check as bcheck
from ZODB.utils import u64 from ZODB.utils import u64
from zodbtools.test.gen_testdata import run_with_zodb4py2_compat from zodbtools.test.gen_testdata import run_with_zodb4py2_compat
import os, os.path, transaction import os, os.path, transaction
...@@ -52,8 +53,32 @@ def main2(): ...@@ -52,8 +53,32 @@ def main2():
root['B3'] = B3 = LOBTree(dict([(_, _) for _ in range(10000)])) root['B3'] = B3 = LOBTree(dict([(_, _) for _ in range(10000)]))
# T4/T2-T/B1-B2-T7,9/B5-B8-B10 (to verify VGet->visit)
# TODO use xbtree.py:Restructure after gimport works through modules and
# xbtree.py is moved from wcfs to zodb/go.
v1 = LOBucket([(1,"a")])
v2 = LOBucket([(2,"b")])
v5 = LOBucket([(5,"c")])
v8 = LOBucket([(8,"d")])
v9 = LOBucket([(9,"e")])
T2, T79, T, T4 = LOBTree(), LOBTree(), LOBTree(), LOBTree()
T2.__setstate__ (((v1, 2, v2), v1)) # (child, key, child, ...), firstbucket
T79.__setstate__(((v5, 7, v8, 9, v9), v5))
T.__setstate__ (((T79,), v5))
T4.__setstate__ (((T2, 4, T), v1))
root['Bv'] = Bv = T4
transaction.commit() transaction.commit()
bcheck(Bv)
assert Bv[1] == "a"
assert Bv[2] == "b"
assert Bv[5] == "c"
assert Bv[8] == "d"
assert Bv[9] == "e"
with open("ztestdata_expect_test.go", "w") as f: with open("ztestdata_expect_test.go", "w") as f:
def emit(v): def emit(v):
print >>f, v print >>f, v
...@@ -83,6 +108,28 @@ def main2(): ...@@ -83,6 +108,28 @@ def main2():
emit("\nconst B3_oid = %s" % u64(B3._p_oid)) emit("\nconst B3_oid = %s" % u64(B3._p_oid))
emit("const B3_maxkey = %d" % B3.maxKey()) emit("const B3_maxkey = %d" % B3.maxKey())
emit("\nconst Bv_oid = %s" % u64(Bv._p_oid))
emit("const Bv_kmin = %d" % Bv.minKey())
emit("const Bv_kmax = %d" % Bv.maxKey())
emit("var Bvdict = map[int64][]tVisit{")
noo = "_LKeyMin"
oo = "_LKeyMax"
def emitVisit(key, *visitv): # visitv = [](node, lo,hi)
vstr = []
for node, lo,hi in visitv:
if isinstance(hi, str):
hi_ = hi # oo or noo
else:
hi_ = hi-1
vstr.append("{%d, LKeyRange{%s, %s}}" % (u64(node._p_oid), lo, hi_))
emit("\t%d: []tVisit{%s}," % (key, ", ".join(vstr)))
emitVisit(1, (T4, noo,oo), (T2, noo,4), (v1, noo,2))
emitVisit(2, (T4, noo,oo), (T2, noo,4), (v2, 2,4))
emitVisit(5, (T4, noo,oo), (T, 4,oo), (T79, 4,oo), (v5, 4,7))
emitVisit(8, (T4, noo,oo), (T, 4,oo), (T79, 4,oo), (v8, 7,9))
emitVisit(9, (T4, noo,oo), (T, 4,oo), (T79, 4,oo), (v9, 9,oo))
emit("}")
conn.close() conn.close()
db.close() db.close()
......
...@@ -179,34 +179,54 @@ func (t *IOBTree) Get(ctx context.Context, key int32) (_ interface{}, _ bool, er ...@@ -179,34 +179,54 @@ func (t *IOBTree) Get(ctx context.Context, key int32) (_ interface{}, _ bool, er
// VGet is like Get but also calls visit while traversing the tree. // VGet is like Get but also calls visit while traversing the tree.
// //
// Visit is called with node being activated. // Visit is called with node being activated.
func (t *IOBTree) VGet(ctx context.Context, key int32, visit func(node IONode)) (_ interface{}, _ bool, err error) { func (t *IOBTree) VGet(ctx context.Context, key int32, visit func(node IONode, keycov IKeyRange)) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key) defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key)
err = t.PActivate(ctx) err = t.PActivate(ctx)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
keycov := IKeyRange{Lo: _IKeyMin, Hi_: _IKeyMax}
if visit != nil { if visit != nil {
visit(t) visit(t, keycov)
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return nil, false, nil
} }
for { for {
l := len(t.data)
if l == 0 {
// empty btree (top, or in leaf)
t.PDeactivate()
return nil, false, nil
}
// search i: K(i) ≤ k < K(i+1) ; K(0) = -∞, K(len) = +∞ // search i: K(i) ≤ k < K(i+1) ; K(0) = -∞, K(len) = +∞
i := sort.Search(len(t.data), func(i int) bool { i := sort.Search(l, func(i int) bool {
j := i + 1 j := i + 1
if j == len(t.data) { if j == len(t.data) {
return true // [len].key = +∞ return true // [len].key = +∞
} }
return key < t.data[j].key return key < t.data[j].key
}) })
// i < l
child := t.data[i].child child := t.data[i].child
// shorten global keycov by local [lo,hi) for this child
lo := _IKeyMin
if i > 0 {
lo = t.data[i].key
}
i++
hi_ := _IKeyMax
if i < l {
hi_ = t.data[i].key
}
if hi_ != _IKeyMax {
hi_--
}
keycov.Lo = ikmax(keycov.Lo, lo)
keycov.Hi_ = ikmin(keycov.Hi_, hi_)
t.PDeactivate() t.PDeactivate()
err = child.PActivate(ctx) err = child.PActivate(ctx)
if err != nil { if err != nil {
...@@ -214,7 +234,7 @@ func (t *IOBTree) VGet(ctx context.Context, key int32, visit func(node IONode)) ...@@ -214,7 +234,7 @@ func (t *IOBTree) VGet(ctx context.Context, key int32, visit func(node IONode))
} }
if visit != nil { if visit != nil {
visit(child) visit(child, keycov)
} }
switch child := child.(type) { switch child := child.(type) {
...@@ -257,26 +277,40 @@ func (t *IOBTree) MinKey(ctx context.Context) (_ int32, ok bool, err error) { ...@@ -257,26 +277,40 @@ func (t *IOBTree) MinKey(ctx context.Context) (_ int32, ok bool, err error) {
// VMinKey is like MinKey but also calls visit while traversing the tree. // VMinKey is like MinKey but also calls visit while traversing the tree.
// //
// Visit is called with node being activated. // Visit is called with node being activated.
func (t *IOBTree) VMinKey(ctx context.Context, visit func(node IONode)) (_ int32, ok bool, err error) { func (t *IOBTree) VMinKey(ctx context.Context, visit func(node IONode, keycov IKeyRange)) (_ int32, ok bool, err error) {
defer xerr.Contextf(&err, "btree(%s): minkey", t.POid()) defer xerr.Contextf(&err, "btree(%s): minkey", t.POid())
err = t.PActivate(ctx) err = t.PActivate(ctx)
if err != nil { if err != nil {
return 0, false, err return 0, false, err
} }
keycov := IKeyRange{Lo: _IKeyMin, Hi_: _IKeyMax}
if visit != nil { if visit != nil {
visit(t) visit(t, keycov)
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return 0, false, nil
} }
// NOTE -> can also use t.firstbucket // NOTE -> can also use t.firstbucket
for { for {
l := len(t.data)
if l == 0 {
// empty btree (top, or in leaf)
t.PDeactivate()
return 0, false, nil
}
child := t.data[0].child child := t.data[0].child
// shorten global keycov by local hi) for this child
hi_ := _IKeyMax
if 1 < l {
hi_ = t.data[1].key
}
if hi_ != _IKeyMax {
hi_--
}
// keycov.Lo stays -∞
keycov.Hi_ = ikmin(keycov.Hi_, hi_)
t.PDeactivate() t.PDeactivate()
err = child.PActivate(ctx) err = child.PActivate(ctx)
if err != nil { if err != nil {
...@@ -284,7 +318,7 @@ func (t *IOBTree) VMinKey(ctx context.Context, visit func(node IONode)) (_ int32 ...@@ -284,7 +318,7 @@ func (t *IOBTree) VMinKey(ctx context.Context, visit func(node IONode)) (_ int32
} }
if visit != nil { if visit != nil {
visit(child) visit(child, keycov)
} }
switch child := child.(type) { switch child := child.(type) {
...@@ -310,26 +344,36 @@ func (t *IOBTree) MaxKey(ctx context.Context) (_ int32, _ bool, err error) { ...@@ -310,26 +344,36 @@ func (t *IOBTree) MaxKey(ctx context.Context) (_ int32, _ bool, err error) {
// VMaxKey is like MaxKey but also calls visit while traversing the tree. // VMaxKey is like MaxKey but also calls visit while traversing the tree.
// //
// Visit is called with node being activated. // Visit is called with node being activated.
func (t *IOBTree) VMaxKey(ctx context.Context, visit func(node IONode)) (_ int32, _ bool, err error) { func (t *IOBTree) VMaxKey(ctx context.Context, visit func(node IONode, keycov IKeyRange)) (_ int32, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): maxkey", t.POid()) defer xerr.Contextf(&err, "btree(%s): maxkey", t.POid())
err = t.PActivate(ctx) err = t.PActivate(ctx)
if err != nil { if err != nil {
return 0, false, err return 0, false, err
} }
keycov := IKeyRange{Lo: _IKeyMin, Hi_: _IKeyMax}
if visit != nil { if visit != nil {
visit(t) visit(t, keycov)
}
l := len(t.data)
if l == 0 {
// empty btree
t.PDeactivate()
return 0, false, nil
} }
for { for {
l := len(t.data)
if l == 0 {
// empty btree (top, or in leaf)
t.PDeactivate()
return 0, false, nil
}
child := t.data[l-1].child child := t.data[l-1].child
// shorten global keycov by local [lo for this chile
lo := _IKeyMin
if l-1 > 0 {
lo = t.data[l-1].key
}
keycov.Lo = ikmax(keycov.Lo, lo)
// keycov.Hi_ stays ∞
t.PDeactivate() t.PDeactivate()
err = child.PActivate(ctx) err = child.PActivate(ctx)
if err != nil { if err != nil {
...@@ -337,7 +381,7 @@ func (t *IOBTree) VMaxKey(ctx context.Context, visit func(node IONode)) (_ int32 ...@@ -337,7 +381,7 @@ func (t *IOBTree) VMaxKey(ctx context.Context, visit func(node IONode)) (_ int32
} }
if visit != nil { if visit != nil {
visit(child) visit(child, keycov)
} }
switch child := child.(type) { switch child := child.(type) {
...@@ -680,6 +724,22 @@ func (r IKeyRange) String() string { ...@@ -680,6 +724,22 @@ func (r IKeyRange) String() string {
} }
func ikmin(a, b int32) int32 {
if a < b {
return a
} else {
return b
}
}
func ikmax(a, b int32) int32 {
if a > b {
return a
} else {
return b
}
}
// ikstr formats key as string. // ikstr formats key as string.
func ikstr(k int32) string { func ikstr(k int32) string {
if k == _IKeyMin { if k == _IKeyMin {
......
...@@ -179,34 +179,54 @@ func (t *LOBTree) Get(ctx context.Context, key int64) (_ interface{}, _ bool, er ...@@ -179,34 +179,54 @@ func (t *LOBTree) Get(ctx context.Context, key int64) (_ interface{}, _ bool, er
// VGet is like Get but also calls visit while traversing the tree. // VGet is like Get but also calls visit while traversing the tree.
// //
// Visit is called with node being activated. // Visit is called with node being activated.
func (t *LOBTree) VGet(ctx context.Context, key int64, visit func(node LONode)) (_ interface{}, _ bool, err error) { func (t *LOBTree) VGet(ctx context.Context, key int64, visit func(node LONode, keycov LKeyRange)) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key) defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key)
err = t.PActivate(ctx) err = t.PActivate(ctx)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
keycov := LKeyRange{Lo: _LKeyMin, Hi_: _LKeyMax}
if visit != nil { if visit != nil {
visit(t) visit(t, keycov)
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return nil, false, nil
} }
for { for {
l := len(t.data)
if l == 0 {
// empty btree (top, or in leaf)
t.PDeactivate()
return nil, false, nil
}
// search i: K(i) ≤ k < K(i+1) ; K(0) = -∞, K(len) = +∞ // search i: K(i) ≤ k < K(i+1) ; K(0) = -∞, K(len) = +∞
i := sort.Search(len(t.data), func(i int) bool { i := sort.Search(l, func(i int) bool {
j := i + 1 j := i + 1
if j == len(t.data) { if j == len(t.data) {
return true // [len].key = +∞ return true // [len].key = +∞
} }
return key < t.data[j].key return key < t.data[j].key
}) })
// i < l
child := t.data[i].child child := t.data[i].child
// shorten global keycov by local [lo,hi) for this child
lo := _LKeyMin
if i > 0 {
lo = t.data[i].key
}
i++
hi_ := _LKeyMax
if i < l {
hi_ = t.data[i].key
}
if hi_ != _LKeyMax {
hi_--
}
keycov.Lo = lkmax(keycov.Lo, lo)
keycov.Hi_ = lkmin(keycov.Hi_, hi_)
t.PDeactivate() t.PDeactivate()
err = child.PActivate(ctx) err = child.PActivate(ctx)
if err != nil { if err != nil {
...@@ -214,7 +234,7 @@ func (t *LOBTree) VGet(ctx context.Context, key int64, visit func(node LONode)) ...@@ -214,7 +234,7 @@ func (t *LOBTree) VGet(ctx context.Context, key int64, visit func(node LONode))
} }
if visit != nil { if visit != nil {
visit(child) visit(child, keycov)
} }
switch child := child.(type) { switch child := child.(type) {
...@@ -257,26 +277,40 @@ func (t *LOBTree) MinKey(ctx context.Context) (_ int64, ok bool, err error) { ...@@ -257,26 +277,40 @@ func (t *LOBTree) MinKey(ctx context.Context) (_ int64, ok bool, err error) {
// VMinKey is like MinKey but also calls visit while traversing the tree. // VMinKey is like MinKey but also calls visit while traversing the tree.
// //
// Visit is called with node being activated. // Visit is called with node being activated.
func (t *LOBTree) VMinKey(ctx context.Context, visit func(node LONode)) (_ int64, ok bool, err error) { func (t *LOBTree) VMinKey(ctx context.Context, visit func(node LONode, keycov LKeyRange)) (_ int64, ok bool, err error) {
defer xerr.Contextf(&err, "btree(%s): minkey", t.POid()) defer xerr.Contextf(&err, "btree(%s): minkey", t.POid())
err = t.PActivate(ctx) err = t.PActivate(ctx)
if err != nil { if err != nil {
return 0, false, err return 0, false, err
} }
keycov := LKeyRange{Lo: _LKeyMin, Hi_: _LKeyMax}
if visit != nil { if visit != nil {
visit(t) visit(t, keycov)
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return 0, false, nil
} }
// NOTE -> can also use t.firstbucket // NOTE -> can also use t.firstbucket
for { for {
l := len(t.data)
if l == 0 {
// empty btree (top, or in leaf)
t.PDeactivate()
return 0, false, nil
}
child := t.data[0].child child := t.data[0].child
// shorten global keycov by local hi) for this child
hi_ := _LKeyMax
if 1 < l {
hi_ = t.data[1].key
}
if hi_ != _LKeyMax {
hi_--
}
// keycov.Lo stays -∞
keycov.Hi_ = lkmin(keycov.Hi_, hi_)
t.PDeactivate() t.PDeactivate()
err = child.PActivate(ctx) err = child.PActivate(ctx)
if err != nil { if err != nil {
...@@ -284,7 +318,7 @@ func (t *LOBTree) VMinKey(ctx context.Context, visit func(node LONode)) (_ int64 ...@@ -284,7 +318,7 @@ func (t *LOBTree) VMinKey(ctx context.Context, visit func(node LONode)) (_ int64
} }
if visit != nil { if visit != nil {
visit(child) visit(child, keycov)
} }
switch child := child.(type) { switch child := child.(type) {
...@@ -310,26 +344,36 @@ func (t *LOBTree) MaxKey(ctx context.Context) (_ int64, _ bool, err error) { ...@@ -310,26 +344,36 @@ func (t *LOBTree) MaxKey(ctx context.Context) (_ int64, _ bool, err error) {
// VMaxKey is like MaxKey but also calls visit while traversing the tree. // VMaxKey is like MaxKey but also calls visit while traversing the tree.
// //
// Visit is called with node being activated. // Visit is called with node being activated.
func (t *LOBTree) VMaxKey(ctx context.Context, visit func(node LONode)) (_ int64, _ bool, err error) { func (t *LOBTree) VMaxKey(ctx context.Context, visit func(node LONode, keycov LKeyRange)) (_ int64, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): maxkey", t.POid()) defer xerr.Contextf(&err, "btree(%s): maxkey", t.POid())
err = t.PActivate(ctx) err = t.PActivate(ctx)
if err != nil { if err != nil {
return 0, false, err return 0, false, err
} }
keycov := LKeyRange{Lo: _LKeyMin, Hi_: _LKeyMax}
if visit != nil { if visit != nil {
visit(t) visit(t, keycov)
}
l := len(t.data)
if l == 0 {
// empty btree
t.PDeactivate()
return 0, false, nil
} }
for { for {
l := len(t.data)
if l == 0 {
// empty btree (top, or in leaf)
t.PDeactivate()
return 0, false, nil
}
child := t.data[l-1].child child := t.data[l-1].child
// shorten global keycov by local [lo for this chile
lo := _LKeyMin
if l-1 > 0 {
lo = t.data[l-1].key
}
keycov.Lo = lkmax(keycov.Lo, lo)
// keycov.Hi_ stays ∞
t.PDeactivate() t.PDeactivate()
err = child.PActivate(ctx) err = child.PActivate(ctx)
if err != nil { if err != nil {
...@@ -337,7 +381,7 @@ func (t *LOBTree) VMaxKey(ctx context.Context, visit func(node LONode)) (_ int64 ...@@ -337,7 +381,7 @@ func (t *LOBTree) VMaxKey(ctx context.Context, visit func(node LONode)) (_ int64
} }
if visit != nil { if visit != nil {
visit(child) visit(child, keycov)
} }
switch child := child.(type) { switch child := child.(type) {
...@@ -680,6 +724,22 @@ func (r LKeyRange) String() string { ...@@ -680,6 +724,22 @@ func (r LKeyRange) String() string {
} }
func lkmin(a, b int64) int64 {
if a < b {
return a
} else {
return b
}
}
func lkmax(a, b int64) int64 {
if a > b {
return a
} else {
return b
}
}
// lkstr formats key as string. // lkstr formats key as string.
func lkstr(k int64) string { func lkstr(k int64) string {
if k == _LKeyMin { if k == _LKeyMin {
......
...@@ -3,13 +3,24 @@ package btree ...@@ -3,13 +3,24 @@ package btree
var smallTestv = [...]testEntry{ var smallTestv = [...]testEntry{
testEntry{oid: 6, kind: kindBucket, itemv: []kv{}}, testEntry{oid: 7, kind: kindBucket, itemv: []kv{}},
testEntry{oid: 3, kind: kindBucket, itemv: []kv{{10, int64(17)}, }}, testEntry{oid: 4, kind: kindBucket, itemv: []kv{{10, int64(17)}, }},
testEntry{oid: 1, kind: kindBucket, itemv: []kv{{15, int64(1)}, {23, "hello"}, }}, testEntry{oid: 1, kind: kindBucket, itemv: []kv{{15, int64(1)}, {23, "hello"}, }},
testEntry{oid: 2, kind: kindBTree, itemv: []kv{}}, testEntry{oid: 3, kind: kindBTree, itemv: []kv{}},
testEntry{oid: 7, kind: kindBTree, itemv: []kv{{5, int64(4)}, }}, testEntry{oid: 8, kind: kindBTree, itemv: []kv{{5, int64(4)}, }},
testEntry{oid: 4, kind: kindBTree, itemv: []kv{{7, int64(3)}, {9, "world"}, }}, testEntry{oid: 5, kind: kindBTree, itemv: []kv{{7, int64(3)}, {9, "world"}, }},
} }
const B3_oid = 5 const B3_oid = 6
const B3_maxkey = 9999 const B3_maxkey = 9999
const Bv_oid = 2
const Bv_kmin = 1
const Bv_kmax = 9
var Bvdict = map[int64][]tVisit{
1: []tVisit{{2, LKeyRange{_LKeyMin, _LKeyMax}}, {342, LKeyRange{_LKeyMin, 3}}, {344, LKeyRange{_LKeyMin, 1}}},
2: []tVisit{{2, LKeyRange{_LKeyMin, _LKeyMax}}, {342, LKeyRange{_LKeyMin, 3}}, {349, LKeyRange{2, 3}}},
5: []tVisit{{2, LKeyRange{_LKeyMin, _LKeyMax}}, {343, LKeyRange{4, _LKeyMax}}, {345, LKeyRange{4, _LKeyMax}}, {346, LKeyRange{4, 6}}},
8: []tVisit{{2, LKeyRange{_LKeyMin, _LKeyMax}}, {343, LKeyRange{4, _LKeyMax}}, {345, LKeyRange{4, _LKeyMax}}, {347, LKeyRange{7, 8}}},
9: []tVisit{{2, LKeyRange{_LKeyMin, _LKeyMax}}, {343, LKeyRange{4, _LKeyMax}}, {345, LKeyRange{4, _LKeyMax}}, {348, LKeyRange{9, _LKeyMax}}},
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment