Commit c1ba9a90 authored by Kirill Smelkov's avatar Kirill Smelkov

go/zodb/btree: Turn it into template

2dba8607 (go/zodb/btree: New package to work with ZODB BTrees (draft))
added btree module with btree.BTree essentially being LOBTree (int64 key
-> object). Since for wendelin.core we also need IOBTree (int32 key ->
object), which is used in ZBlk1

	https://lab.nexedi.com/nexedi/wendelin.core/blob/v0.12-6-g318efce/bigfile/file_zodb.py#L267
	https://lab.nexedi.com/nexedi/wendelin.core/blob/v0.12-6-g318efce/bigfile/file_zodb.py#L374

let's turn btree module into template internally and generate code for
both LOBTree and IOBTree.

For the reference BTree/py takes similar approach with respect to
templating.
parent fcab58ca
// Copyright (c) 2001, 2002 Zope Foundation and Contributors.
// All Rights Reserved.
//
// Copyright (C) 2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This software is subject to the provisions of the Zope Public License,
// Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
// FOR A PARTICULAR PURPOSE
// Package btree provides B⁺ Trees for ZODB.
//
......@@ -19,348 +10,5 @@
// https://github.com/zopefoundation/BTrees
package btree
// See https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/Development.txt#L198
// for BTree & Bucket organization details.
import (
"context"
"fmt"
"reflect"
"sort"
pickle "github.com/kisielk/og-rek"
"lab.nexedi.com/kirr/go123/xerr"
"lab.nexedi.com/kirr/neo/go/zodb"
)
// KEY is the type for BTree keys.
//
// XXX -> template?
type KEY int64
// Bucket is a leaf node of a B⁺ tree.
//
// It mimics ?OBucket from btree/py, with ? being any integer.
type Bucket struct {
zodb.Persistent
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L179:
//
// A Bucket wraps contiguous vectors of keys and values. Keys are unique,
// and stored in sorted order. The 'values' pointer may be NULL if the
// Bucket is used to implement a set. Buckets serving as leafs of BTrees
// are chained together via 'next', so that the entire BTree contents
// can be traversed in sorted order quickly and easily.
next *Bucket // the bucket with the next-larger keys
keys []KEY // 'len' keys, in increasing order
values []interface{} // 'len' corresponding values
}
// _BTreeItem mimics BTreeItem from btree/py.
//
// XXX export for BTree.Children?
type _BTreeItem struct {
key KEY
child interface{} // BTree or Bucket
}
// BTree is a non-leaf node of a B⁺ tree.
//
// It mimics ?OBTree from btree/py, with ? being any integer.
type BTree struct {
zodb.Persistent
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L205:
//
// firstbucket points to the bucket containing the smallest key in
// the BTree. This is found by traversing leftmost child pointers
// (data[0].child) until reaching a Bucket.
firstbucket *Bucket
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L211:
//
// The BTree points to 'len' children, via the "child" fields of the data
// array. There are len-1 keys in the 'key' fields, stored in increasing
// order. data[0].key is unused. For i in 0 .. len-1, all keys reachable
// from data[i].child are >= data[i].key and < data[i+1].key, at the
// endpoints pretending that data[0].key is -∞ and data[len].key is +∞.
data []_BTreeItem
}
// Get searches BTree by key.
//
// It loads intermediate BTree nodes from database on demand as needed.
//
// t need not be activated beforehand for Get to work.
func (t *BTree) Get(ctx context.Context, key KEY) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key)
err = t.PActivate(ctx)
if err != nil {
return nil, false, err
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return nil, false, nil
}
for {
// search i: K(i) ≤ k < K(i+1) ; K(0) = -∞, K(len) = +∞
i := sort.Search(len(t.data), func(i int) bool {
j := i + 1
if j == len(t.data) {
return true // [len].key = +∞
}
return key < t.data[j].key
})
switch child := t.data[i].child.(type) {
case *BTree:
t.PDeactivate()
t = child
err = t.PActivate(ctx)
if err != nil {
return nil, false, err
}
case *Bucket:
t.PDeactivate()
return child.Get(ctx, key)
}
}
}
// Get searches Bucket by key.
//
// TODO Bucket.Get should not get ctx argument and just require that the bucket
// must be already activated. Correspondingly there should be no error returned.
func (b *Bucket) Get(ctx context.Context, key KEY) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "bucket(%s): get %v", b.POid(), key)
err = b.PActivate(ctx)
if err != nil {
return nil, false, err
}
v, ok := b.get(key)
b.PDeactivate()
return v, ok, nil
}
// get searches Bucket by key.
//
// no loading from database is done. The bucket must be already activated.
func (b *Bucket) get(key KEY) (interface{}, bool) {
// search i: K(i-1) < k ≤ K(i) ; K(-1) = -∞, K(len) = +∞
i := sort.Search(len(b.keys), func(i int) bool {
return key <= b.keys[i]
})
if i == len(b.keys) || b.keys[i] != key {
return nil, false // not found
}
return b.values[i], true
}
// TODO Bucket.MinKey
// TODO Bucket.MaxKey
// ---- serialization ----
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BucketTemplate.c#L1195:
//
// For a mapping bucket (self->values is not NULL), a one-tuple or two-tuple.
// The first element is a tuple interleaving keys and values, of length
// 2 * self->len. The second element is the next bucket, present iff next is
// non-NULL:
//
// (
// (keys[0], values[0], keys[1], values[1], ...,
// keys[len-1], values[len-1]),
// <self->next iff non-NULL>
// )
type bucketState Bucket // hide state methods from public API
// DropState implements Stateful to discard bucket state.
func (b *bucketState) DropState() {
b.next = nil
b.keys = nil
b.values = nil
}
// PySetState implements PyStateful to set bucket data from pystate.
func (b *bucketState) PySetState(pystate interface{}) (err error) {
t, ok := pystate.(pickle.Tuple)
if !ok {
return fmt.Errorf("top: expect (...); got %T", pystate)
}
if !(1 <= len(t) && len(t) <= 2) {
return fmt.Errorf("top: expect [1..2](); got [%d]()", len(t))
}
// .next present
if len(t) == 2 {
next, ok := t[1].(*Bucket)
if !ok {
return fmt.Errorf(".next must be Bucket; got %T", t[1])
}
b.next = next
}
// main part
t, ok = t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("data: expect (...); got %T", t[0])
}
if len(t)%2 != 0 {
return fmt.Errorf("data: expect [%%2](); got [%d]()", len(t))
}
// reset arrays just in case
n := len(t) / 2
b.keys = make([]KEY, 0, n)
b.values = make([]interface{}, 0, n)
for i := 0; i < n; i++ {
xk := t[2*i]
v := t[2*i+1]
k, ok := xk.(int64) // XXX use KEY XXX -> Xint64
if !ok {
return fmt.Errorf("data: [%d]: key must be integer; got %T", i, xk)
}
// XXX check keys are sorted?
b.keys = append(b.keys, KEY(k)) // XXX cast
b.values = append(b.values, v)
}
return nil
}
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeTemplate.c#L1087:
//
// For an empty BTree (self->len == 0), None.
//
// For a BTree with one child (self->len == 1), and that child is a bucket,
// and that bucket has a NULL oid, a one-tuple containing a one-tuple
// containing the bucket's state:
//
// (
// (
// child[0].__getstate__(),
// ),
// )
//
// Else a two-tuple. The first element is a tuple interleaving the BTree's
// keys and direct children, of size 2*self->len - 1 (key[0] is unused and
// is not saved). The second element is the firstbucket:
//
// (
// (child[0], key[1], child[1], key[2], child[2], ...,
// key[len-1], child[len-1]),
// self->firstbucket
// )
//
// In the above, key[i] means self->data[i].key, and similarly for child[i].
type btreeState BTree // hide state methods from public API
// DropState implements zodb.Stateful.
func (t *btreeState) DropState() {
t.firstbucket = nil
t.data = nil
}
// PySetState implements zodb.PyStateful to set btree data from pystate.
func (bt *btreeState) PySetState(pystate interface{}) (err error) {
// empty btree
if _, ok := pystate.(pickle.None); ok {
bt.firstbucket = nil
bt.data = nil
return nil
}
t, ok := pystate.(pickle.Tuple)
if !ok {
return fmt.Errorf("top: expect (...); got %T", pystate)
}
if !(1 <= len(t) && len(t) <= 2) {
return fmt.Errorf("top: expect [1..2](); got [%d]()", len(t))
}
// btree with 1 child bucket without oid
if len(t) == 1 {
t, ok := t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("bucket1: expect [1](); got %T", t[0])
}
if len(t) != 1 {
return fmt.Errorf("bucket1: expect [1](); got [%d]()", len(t))
}
bucket := zodb.NewPersistent(reflect.TypeOf(Bucket{}), bt.PJar()).(*Bucket)
err := (*bucketState)(bucket).PySetState(t[0])
if err != nil {
return fmt.Errorf("bucket1: %s", err)
}
bt.firstbucket = bucket
bt.data = []_BTreeItem{{key: 0, child: bucket}}
return nil
}
// regular btree
bt.firstbucket, ok = t[1].(*Bucket)
if !ok {
return fmt.Errorf("first bucket: must be Bucket; got %T", t[1])
}
t, ok = t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("data: expect (...); got %T", t[0])
}
if len(t)%2 == 0 {
return fmt.Errorf("data: expect [!%%2](); got [%d]()", len(t))
}
n := (len(t) + 1) / 2
bt.data = make([]_BTreeItem, 0, n)
for i, idx := 0, 0; i < n; i++ {
key := int64(0)
if i > 0 {
// key[0] is unused and not saved
key, ok = t[idx].(int64) // XXX Xint
if !ok {
return fmt.Errorf("data: [%d]: key must be integer; got %T", i, t[idx])
}
idx++
}
child := t[idx]
idx++
switch child.(type) {
default:
return fmt.Errorf("data: [%d]: child must be BTree|Bucket; got %T", i, child)
case *BTree: // ok
case *Bucket: // ok
}
bt.data = append(bt.data, _BTreeItem{key: KEY(key), child: child})
}
return nil
}
// ---- register classes to ZODB ----
func init() {
t := reflect.TypeOf
zodb.RegisterClass("BTrees.LOBTree.LOBucket", t(Bucket{}), t(bucketState{}))
zodb.RegisterClass("BTrees.LOBTree.LOBTree", t(BTree{}), t(btreeState{}))
}
//go:generate ./gen-btree IO int32 ziobtree.go
//go:generate ./gen-btree LO int64 zlobtree.go
// Copyright (c) 2001, 2002 Zope Foundation and Contributors.
// All Rights Reserved.
//
// Copyright (C) 2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This software is subject to the provisions of the Zope Public License,
// Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
// FOR A PARTICULAR PURPOSE
package btree
// See https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/Development.txt#L198
// for BTree & Bucket organization details.
import (
"context"
"fmt"
"math"
"reflect"
"sort"
pickle "github.com/kisielk/og-rek"
"lab.nexedi.com/kirr/go123/xerr"
"lab.nexedi.com/kirr/neo/go/zodb"
"lab.nexedi.com/kirr/neo/go/zodb/internal/pickletools"
)
// Bucket is a leaf node of a B tree.
//
// It mimics Bucket from btree/py.
type Bucket struct {
zodb.Persistent
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L179:
//
// A Bucket wraps contiguous vectors of keys and values. Keys are unique,
// and stored in sorted order. The 'values' pointer may be NULL if the
// Bucket is used to implement a set. Buckets serving as leafs of BTrees
// are chained together via 'next', so that the entire BTree contents
// can be traversed in sorted order quickly and easily.
next *Bucket // the bucket with the next-larger keys
keys []KEY // 'len' keys, in increasing order
values []interface{} // 'len' corresponding values
}
// _BTreeItem mimics BTreeItem from btree/py.
//
// XXX export for BTree.Children?
type _BTreeItem struct {
key KEY
child interface{} // BTree or Bucket
}
// BTree is a non-leaf node of a B tree.
//
// It mimics BTree from btree/py.
type BTree struct {
zodb.Persistent
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L205:
//
// firstbucket points to the bucket containing the smallest key in
// the BTree. This is found by traversing leftmost child pointers
// (data[0].child) until reaching a Bucket.
firstbucket *Bucket
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L211:
//
// The BTree points to 'len' children, via the "child" fields of the data
// array. There are len-1 keys in the 'key' fields, stored in increasing
// order. data[0].key is unused. For i in 0 .. len-1, all keys reachable
// from data[i].child are >= data[i].key and < data[i+1].key, at the
// endpoints pretending that data[0].key is - and data[len].key is +.
data []_BTreeItem
}
// Get searches BTree by key.
//
// It loads intermediate BTree nodes from database on demand as needed.
//
// t need not be activated beforehand for Get to work.
func (t *BTree) Get(ctx context.Context, key KEY) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key)
err = t.PActivate(ctx)
if err != nil {
return nil, false, err
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return nil, false, nil
}
for {
// search i: K(i) k < K(i+1) ; K(0) = -, K(len) = +
i := sort.Search(len(t.data), func(i int) bool {
j := i + 1
if j == len(t.data) {
return true // [len].key = +
}
return key < t.data[j].key
})
switch child := t.data[i].child.(type) {
case *BTree:
t.PDeactivate()
t = child
err = t.PActivate(ctx)
if err != nil {
return nil, false, err
}
case *Bucket:
t.PDeactivate()
return child.Get(ctx, key)
}
}
}
// Get searches Bucket by key.
//
// TODO Bucket.Get should not get ctx argument and just require that the bucket
// must be already activated. Correspondingly there should be no error returned.
func (b *Bucket) Get(ctx context.Context, key KEY) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "bucket(%s): get %v", b.POid(), key)
err = b.PActivate(ctx)
if err != nil {
return nil, false, err
}
v, ok := b.get(key)
b.PDeactivate()
return v, ok, nil
}
// get searches Bucket by key.
//
// no loading from database is done. The bucket must be already activated.
func (b *Bucket) get(key KEY) (interface{}, bool) {
// search i: K(i-1) < k K(i) ; K(-1) = -, K(len) = +
i := sort.Search(len(b.keys), func(i int) bool {
return key <= b.keys[i]
})
if i == len(b.keys) || b.keys[i] != key {
return nil, false // not found
}
return b.values[i], true
}
// TODO Bucket.MinKey
// TODO Bucket.MaxKey
// ---- serialization ----
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BucketTemplate.c#L1195:
//
// For a mapping bucket (self->values is not NULL), a one-tuple or two-tuple.
// The first element is a tuple interleaving keys and values, of length
// 2 * self->len. The second element is the next bucket, present iff next is
// non-NULL:
//
// (
// (keys[0], values[0], keys[1], values[1], ...,
// keys[len-1], values[len-1]),
// <self->next iff non-NULL>
// )
type bucketState Bucket // hide state methods from public API
// DropState implements Stateful to discard bucket state.
func (b *bucketState) DropState() {
b.next = nil
b.keys = nil
b.values = nil
}
// PySetState implements PyStateful to set bucket data from pystate.
func (b *bucketState) PySetState(pystate interface{}) (err error) {
t, ok := pystate.(pickle.Tuple)
if !ok {
return fmt.Errorf("top: expect (...); got %T", pystate)
}
if !(1 <= len(t) && len(t) <= 2) {
return fmt.Errorf("top: expect [1..2](); got [%d]()", len(t))
}
// .next present
if len(t) == 2 {
next, ok := t[1].(*Bucket)
if !ok {
return fmt.Errorf(".next must be Bucket; got %T", t[1])
}
b.next = next
}
// main part
t, ok = t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("data: expect (...); got %T", t[0])
}
if len(t)%2 != 0 {
return fmt.Errorf("data: expect [%%2](); got [%d]()", len(t))
}
// reset arrays just in case
n := len(t) / 2
b.keys = make([]KEY, 0, n)
b.values = make([]interface{}, 0, n)
for i := 0; i < n; i++ {
xk := t[2*i]
v := t[2*i+1]
k, ok := pickletools.Xint64(xk)
if !ok {
return fmt.Errorf("data: [%d]: key must be integer; got %T", i, xk)
}
kk := KEY(k)
if int64(kk) != k {
return fmt.Errorf("data: [%d]: key overflows %T", i, kk)
}
// XXX check keys are sorted?
b.keys = append(b.keys, kk)
b.values = append(b.values, v)
}
return nil
}
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeTemplate.c#L1087:
//
// For an empty BTree (self->len == 0), None.
//
// For a BTree with one child (self->len == 1), and that child is a bucket,
// and that bucket has a NULL oid, a one-tuple containing a one-tuple
// containing the bucket's state:
//
// (
// (
// child[0].__getstate__(),
// ),
// )
//
// Else a two-tuple. The first element is a tuple interleaving the BTree's
// keys and direct children, of size 2*self->len - 1 (key[0] is unused and
// is not saved). The second element is the firstbucket:
//
// (
// (child[0], key[1], child[1], key[2], child[2], ...,
// key[len-1], child[len-1]),
// self->firstbucket
// )
//
// In the above, key[i] means self->data[i].key, and similarly for child[i].
type btreeState BTree // hide state methods from public API
// DropState implements zodb.Stateful.
func (t *btreeState) DropState() {
t.firstbucket = nil
t.data = nil
}
// PySetState implements zodb.PyStateful to set btree data from pystate.
func (bt *btreeState) PySetState(pystate interface{}) (err error) {
// empty btree
if _, ok := pystate.(pickle.None); ok {
bt.firstbucket = nil
bt.data = nil
return nil
}
t, ok := pystate.(pickle.Tuple)
if !ok {
return fmt.Errorf("top: expect (...); got %T", pystate)
}
if !(1 <= len(t) && len(t) <= 2) {
return fmt.Errorf("top: expect [1..2](); got [%d]()", len(t))
}
// btree with 1 child bucket without oid
if len(t) == 1 {
t, ok := t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("bucket1: expect [1](); got %T", t[0])
}
if len(t) != 1 {
return fmt.Errorf("bucket1: expect [1](); got [%d]()", len(t))
}
bucket := zodb.NewPersistent(reflect.TypeOf(Bucket{}), bt.PJar()).(*Bucket)
err := (*bucketState)(bucket).PySetState(t[0])
if err != nil {
return fmt.Errorf("bucket1: %s", err)
}
bt.firstbucket = bucket
bt.data = []_BTreeItem{{key: 0, child: bucket}}
return nil
}
// regular btree
bt.firstbucket, ok = t[1].(*Bucket)
if !ok {
return fmt.Errorf("first bucket: must be Bucket; got %T", t[1])
}
t, ok = t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("data: expect (...); got %T", t[0])
}
if len(t)%2 == 0 {
return fmt.Errorf("data: expect [!%%2](); got [%d]()", len(t))
}
n := (len(t) + 1) / 2
bt.data = make([]_BTreeItem, 0, n)
for i, idx := 0, 0; i < n; i++ {
key := int64(math.Min<Key>) // KEY(-) (qualifies for )
if i > 0 {
// key[0] is unused and not saved
key, ok = t[idx].(int64) // XXX Xint
if !ok {
return fmt.Errorf("data: [%d]: key must be integer; got %T", i, t[idx])
}
idx++
}
child := t[idx]
idx++
kkey := KEY(key)
if int64(kkey) != key {
return fmt.Errorf("data: [%d]: key overflows %T", i, kkey)
}
switch child.(type) {
default:
return fmt.Errorf("data: [%d]: child must be BTree|Bucket; got %T", i, child)
case *BTree: // ok
case *Bucket: // ok
}
bt.data = append(bt.data, _BTreeItem{key: kkey, child: child})
}
return nil
}
// ---- register classes to ZODB ----
func init() {
t := reflect.TypeOf
zodb.RegisterClass("BTrees.BTree.BTree", t(BTree{}), t(btreeState{}))
zodb.RegisterClass("BTrees.BTree.Bucket", t(Bucket{}), t(bucketState{}))
}
......@@ -33,7 +33,7 @@ import (
// kv is one (key, value) pair.
type kv struct {
key KEY
key int64
value interface{}
}
......@@ -52,7 +52,7 @@ type testEntry struct {
// bmapping represents Get of Bucket or BTree.
type bmapping interface {
Get(context.Context, KEY) (interface{}, bool, error)
Get(context.Context, int64) (interface{}, bool, error)
}
func TestBTree(t *testing.T) {
......@@ -83,18 +83,18 @@ func TestBTree(t *testing.T) {
obj, ok := xobj.(bmapping)
if !ok {
t.Fatalf("%s: got %T; want Bucket|BTree", tt.oid, xobj)
t.Fatalf("%s: got %T; want LOBucket|LOBTree", tt.oid, xobj)
}
want := ""
switch tt.kind {
case kindBucket:
if _, ok = obj.(*Bucket); !ok {
want = "Bucket"
if _, ok = obj.(*LOBucket); !ok {
want = "LOBucket"
}
case kindBTree:
if _, ok = obj.(*BTree); !ok {
want = "BTree"
if _, ok = obj.(*LOBTree); !ok {
want = "LOBTree"
}
default:
panic(0)
......@@ -132,12 +132,12 @@ func TestBTree(t *testing.T) {
if err != nil {
t.Fatal(err)
}
B3, ok := xB3.(*BTree)
B3, ok := xB3.(*LOBTree)
if !ok {
t.Fatalf("B3: %v; got %T; want BTree", B3_oid, xB3)
t.Fatalf("B3: %v; got %T; want LOBTree", B3_oid, xB3)
}
for i := KEY(0); i <= B3_maxkey; i++ {
for i := int64(0); i <= B3_maxkey; i++ {
v, ok, err := B3.Get(ctx, i)
if err != nil {
t.Fatal(err)
......@@ -151,21 +151,21 @@ func TestBTree(t *testing.T) {
}
// verifyFirstBucket verifies that b.firstbucket is correct and returns it.
var verifyFirstBucket func(b *BTree) *Bucket
verifyFirstBucket = func(b *BTree) *Bucket {
var verifyFirstBucket func(b *LOBTree) *LOBucket
verifyFirstBucket = func(b *LOBTree) *LOBucket {
err := b.PActivate(ctx); X(err)
defer b.PDeactivate()
var firstbucket *Bucket
var firstbucket *LOBucket
switch child := b.data[0].child.(type) {
default:
t.Fatalf("btree(%s): child[0] is %T", b.POid(), b.data[0].child)
case *BTree:
case *LOBTree:
firstbucket = verifyFirstBucket(child)
case *Bucket:
case *LOBucket:
firstbucket = child
}
......
#!/bin/bash -e
# btree.go.in -> specialized with concrete types
# gen-btree KIND KEY out
# Copyright (C) 2018 Nexedi SA and Contributors.
# Kirill Smelkov <kirr@nexedi.com>
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
KIND=$1
KEY=$2
out=$3
kind=${KIND,,} # IO -> io
Key=${KEY^}
input=$(dirname $0)/btree.go.in
echo "// Code generated by gen-btree; DO NOT EDIT." >$out
echo >>$out
sed \
-e "s/<kind>/$kind/g" \
-e "s/<KIND>/$KIND/g" \
-e "s/KEY/$KEY/g" \
-e "s/<Key>/$Key/g" \
-e "s/\bBTree\b/${KIND}BTree/g" \
-e "s/\b_BTreeItem\b/_${KIND}BTreeItem/g" \
-e "s/\bBucket\b/${KIND}Bucket/g" \
-e "s/\bbtreeState\b/${kind}btreeState/g" \
-e "s/\bbucketState\b/${kind}bucketState/g" \
$input >>$out
// Code generated by gen-btree; DO NOT EDIT.
// Copyright (c) 2001, 2002 Zope Foundation and Contributors.
// All Rights Reserved.
//
// Copyright (C) 2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This software is subject to the provisions of the Zope Public License,
// Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
// FOR A PARTICULAR PURPOSE
package btree
// See https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/Development.txt#L198
// for IOBTree & IOBucket organization details.
import (
"context"
"fmt"
"math"
"reflect"
"sort"
pickle "github.com/kisielk/og-rek"
"lab.nexedi.com/kirr/go123/xerr"
"lab.nexedi.com/kirr/neo/go/zodb"
"lab.nexedi.com/kirr/neo/go/zodb/internal/pickletools"
)
// IOBucket is a leaf node of a B⁺ tree.
//
// It mimics IOBucket from btree/py.
type IOBucket struct {
zodb.Persistent
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L179:
//
// A IOBucket wraps contiguous vectors of keys and values. Keys are unique,
// and stored in sorted order. The 'values' pointer may be NULL if the
// IOBucket is used to implement a set. Buckets serving as leafs of BTrees
// are chained together via 'next', so that the entire IOBTree contents
// can be traversed in sorted order quickly and easily.
next *IOBucket // the bucket with the next-larger keys
keys []int32 // 'len' keys, in increasing order
values []interface{} // 'len' corresponding values
}
// _IOBTreeItem mimics BTreeItem from btree/py.
//
// XXX export for IOBTree.Children?
type _IOBTreeItem struct {
key int32
child interface{} // IOBTree or IOBucket
}
// IOBTree is a non-leaf node of a B⁺ tree.
//
// It mimics IOBTree from btree/py.
type IOBTree struct {
zodb.Persistent
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L205:
//
// firstbucket points to the bucket containing the smallest key in
// the IOBTree. This is found by traversing leftmost child pointers
// (data[0].child) until reaching a IOBucket.
firstbucket *IOBucket
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L211:
//
// The IOBTree points to 'len' children, via the "child" fields of the data
// array. There are len-1 keys in the 'key' fields, stored in increasing
// order. data[0].key is unused. For i in 0 .. len-1, all keys reachable
// from data[i].child are >= data[i].key and < data[i+1].key, at the
// endpoints pretending that data[0].key is -∞ and data[len].key is +∞.
data []_IOBTreeItem
}
// Get searches IOBTree by key.
//
// It loads intermediate IOBTree nodes from database on demand as needed.
//
// t need not be activated beforehand for Get to work.
func (t *IOBTree) Get(ctx context.Context, key int32) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key)
err = t.PActivate(ctx)
if err != nil {
return nil, false, err
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return nil, false, nil
}
for {
// search i: K(i) ≤ k < K(i+1) ; K(0) = -∞, K(len) = +∞
i := sort.Search(len(t.data), func(i int) bool {
j := i + 1
if j == len(t.data) {
return true // [len].key = +∞
}
return key < t.data[j].key
})
switch child := t.data[i].child.(type) {
case *IOBTree:
t.PDeactivate()
t = child
err = t.PActivate(ctx)
if err != nil {
return nil, false, err
}
case *IOBucket:
t.PDeactivate()
return child.Get(ctx, key)
}
}
}
// Get searches IOBucket by key.
//
// TODO IOBucket.Get should not get ctx argument and just require that the bucket
// must be already activated. Correspondingly there should be no error returned.
func (b *IOBucket) Get(ctx context.Context, key int32) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "bucket(%s): get %v", b.POid(), key)
err = b.PActivate(ctx)
if err != nil {
return nil, false, err
}
v, ok := b.get(key)
b.PDeactivate()
return v, ok, nil
}
// get searches IOBucket by key.
//
// no loading from database is done. The bucket must be already activated.
func (b *IOBucket) get(key int32) (interface{}, bool) {
// search i: K(i-1) < k ≤ K(i) ; K(-1) = -∞, K(len) = +∞
i := sort.Search(len(b.keys), func(i int) bool {
return key <= b.keys[i]
})
if i == len(b.keys) || b.keys[i] != key {
return nil, false // not found
}
return b.values[i], true
}
// TODO IOBucket.MinKey
// TODO IOBucket.MaxKey
// ---- serialization ----
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BucketTemplate.c#L1195:
//
// For a mapping bucket (self->values is not NULL), a one-tuple or two-tuple.
// The first element is a tuple interleaving keys and values, of length
// 2 * self->len. The second element is the next bucket, present iff next is
// non-NULL:
//
// (
// (keys[0], values[0], keys[1], values[1], ...,
// keys[len-1], values[len-1]),
// <self->next iff non-NULL>
// )
type iobucketState IOBucket // hide state methods from public API
// DropState implements Stateful to discard bucket state.
func (b *iobucketState) DropState() {
b.next = nil
b.keys = nil
b.values = nil
}
// PySetState implements PyStateful to set bucket data from pystate.
func (b *iobucketState) PySetState(pystate interface{}) (err error) {
t, ok := pystate.(pickle.Tuple)
if !ok {
return fmt.Errorf("top: expect (...); got %T", pystate)
}
if !(1 <= len(t) && len(t) <= 2) {
return fmt.Errorf("top: expect [1..2](); got [%d]()", len(t))
}
// .next present
if len(t) == 2 {
next, ok := t[1].(*IOBucket)
if !ok {
return fmt.Errorf(".next must be IOBucket; got %T", t[1])
}
b.next = next
}
// main part
t, ok = t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("data: expect (...); got %T", t[0])
}
if len(t)%2 != 0 {
return fmt.Errorf("data: expect [%%2](); got [%d]()", len(t))
}
// reset arrays just in case
n := len(t) / 2
b.keys = make([]int32, 0, n)
b.values = make([]interface{}, 0, n)
for i := 0; i < n; i++ {
xk := t[2*i]
v := t[2*i+1]
k, ok := pickletools.Xint64(xk)
if !ok {
return fmt.Errorf("data: [%d]: key must be integer; got %T", i, xk)
}
kk := int32(k)
if int64(kk) != k {
return fmt.Errorf("data: [%d]: key overflows %T", i, kk)
}
// XXX check keys are sorted?
b.keys = append(b.keys, kk)
b.values = append(b.values, v)
}
return nil
}
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeTemplate.c#L1087:
//
// For an empty IOBTree (self->len == 0), None.
//
// For a IOBTree with one child (self->len == 1), and that child is a bucket,
// and that bucket has a NULL oid, a one-tuple containing a one-tuple
// containing the bucket's state:
//
// (
// (
// child[0].__getstate__(),
// ),
// )
//
// Else a two-tuple. The first element is a tuple interleaving the IOBTree's
// keys and direct children, of size 2*self->len - 1 (key[0] is unused and
// is not saved). The second element is the firstbucket:
//
// (
// (child[0], key[1], child[1], key[2], child[2], ...,
// key[len-1], child[len-1]),
// self->firstbucket
// )
//
// In the above, key[i] means self->data[i].key, and similarly for child[i].
type iobtreeState IOBTree // hide state methods from public API
// DropState implements zodb.Stateful.
func (t *iobtreeState) DropState() {
t.firstbucket = nil
t.data = nil
}
// PySetState implements zodb.PyStateful to set btree data from pystate.
func (bt *iobtreeState) PySetState(pystate interface{}) (err error) {
// empty btree
if _, ok := pystate.(pickle.None); ok {
bt.firstbucket = nil
bt.data = nil
return nil
}
t, ok := pystate.(pickle.Tuple)
if !ok {
return fmt.Errorf("top: expect (...); got %T", pystate)
}
if !(1 <= len(t) && len(t) <= 2) {
return fmt.Errorf("top: expect [1..2](); got [%d]()", len(t))
}
// btree with 1 child bucket without oid
if len(t) == 1 {
t, ok := t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("bucket1: expect [1](); got %T", t[0])
}
if len(t) != 1 {
return fmt.Errorf("bucket1: expect [1](); got [%d]()", len(t))
}
bucket := zodb.NewPersistent(reflect.TypeOf(IOBucket{}), bt.PJar()).(*IOBucket)
err := (*iobucketState)(bucket).PySetState(t[0])
if err != nil {
return fmt.Errorf("bucket1: %s", err)
}
bt.firstbucket = bucket
bt.data = []_IOBTreeItem{{key: 0, child: bucket}}
return nil
}
// regular btree
bt.firstbucket, ok = t[1].(*IOBucket)
if !ok {
return fmt.Errorf("first bucket: must be IOBucket; got %T", t[1])
}
t, ok = t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("data: expect (...); got %T", t[0])
}
if len(t)%2 == 0 {
return fmt.Errorf("data: expect [!%%2](); got [%d]()", len(t))
}
n := (len(t) + 1) / 2
bt.data = make([]_IOBTreeItem, 0, n)
for i, idx := 0, 0; i < n; i++ {
key := int64(math.MinInt32) // int32(-∞) (qualifies for ≤)
if i > 0 {
// key[0] is unused and not saved
key, ok = t[idx].(int64) // XXX Xint
if !ok {
return fmt.Errorf("data: [%d]: key must be integer; got %T", i, t[idx])
}
idx++
}
child := t[idx]
idx++
kkey := int32(key)
if int64(kkey) != key {
return fmt.Errorf("data: [%d]: key overflows %T", i, kkey)
}
switch child.(type) {
default:
return fmt.Errorf("data: [%d]: child must be IOBTree|IOBucket; got %T", i, child)
case *IOBTree: // ok
case *IOBucket: // ok
}
bt.data = append(bt.data, _IOBTreeItem{key: kkey, child: child})
}
return nil
}
// ---- register classes to ZODB ----
func init() {
t := reflect.TypeOf
zodb.RegisterClass("BTrees.IOBTree.IOBTree", t(IOBTree{}), t(iobtreeState{}))
zodb.RegisterClass("BTrees.IOBTree.IOBucket", t(IOBucket{}), t(iobucketState{}))
}
// Code generated by gen-btree; DO NOT EDIT.
// Copyright (c) 2001, 2002 Zope Foundation and Contributors.
// All Rights Reserved.
//
// Copyright (C) 2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This software is subject to the provisions of the Zope Public License,
// Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
// FOR A PARTICULAR PURPOSE
package btree
// See https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/Development.txt#L198
// for LOBTree & LOBucket organization details.
import (
"context"
"fmt"
"math"
"reflect"
"sort"
pickle "github.com/kisielk/og-rek"
"lab.nexedi.com/kirr/go123/xerr"
"lab.nexedi.com/kirr/neo/go/zodb"
"lab.nexedi.com/kirr/neo/go/zodb/internal/pickletools"
)
// LOBucket is a leaf node of a B⁺ tree.
//
// It mimics LOBucket from btree/py.
type LOBucket struct {
zodb.Persistent
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L179:
//
// A LOBucket wraps contiguous vectors of keys and values. Keys are unique,
// and stored in sorted order. The 'values' pointer may be NULL if the
// LOBucket is used to implement a set. Buckets serving as leafs of BTrees
// are chained together via 'next', so that the entire LOBTree contents
// can be traversed in sorted order quickly and easily.
next *LOBucket // the bucket with the next-larger keys
keys []int64 // 'len' keys, in increasing order
values []interface{} // 'len' corresponding values
}
// _LOBTreeItem mimics BTreeItem from btree/py.
//
// XXX export for LOBTree.Children?
type _LOBTreeItem struct {
key int64
child interface{} // LOBTree or LOBucket
}
// LOBTree is a non-leaf node of a B⁺ tree.
//
// It mimics LOBTree from btree/py.
type LOBTree struct {
zodb.Persistent
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L205:
//
// firstbucket points to the bucket containing the smallest key in
// the LOBTree. This is found by traversing leftmost child pointers
// (data[0].child) until reaching a LOBucket.
firstbucket *LOBucket
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeModuleTemplate.c#L211:
//
// The LOBTree points to 'len' children, via the "child" fields of the data
// array. There are len-1 keys in the 'key' fields, stored in increasing
// order. data[0].key is unused. For i in 0 .. len-1, all keys reachable
// from data[i].child are >= data[i].key and < data[i+1].key, at the
// endpoints pretending that data[0].key is -∞ and data[len].key is +∞.
data []_LOBTreeItem
}
// Get searches LOBTree by key.
//
// It loads intermediate LOBTree nodes from database on demand as needed.
//
// t need not be activated beforehand for Get to work.
func (t *LOBTree) Get(ctx context.Context, key int64) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "btree(%s): get %v", t.POid(), key)
err = t.PActivate(ctx)
if err != nil {
return nil, false, err
}
if len(t.data) == 0 {
// empty btree
t.PDeactivate()
return nil, false, nil
}
for {
// search i: K(i) ≤ k < K(i+1) ; K(0) = -∞, K(len) = +∞
i := sort.Search(len(t.data), func(i int) bool {
j := i + 1
if j == len(t.data) {
return true // [len].key = +∞
}
return key < t.data[j].key
})
switch child := t.data[i].child.(type) {
case *LOBTree:
t.PDeactivate()
t = child
err = t.PActivate(ctx)
if err != nil {
return nil, false, err
}
case *LOBucket:
t.PDeactivate()
return child.Get(ctx, key)
}
}
}
// Get searches LOBucket by key.
//
// TODO LOBucket.Get should not get ctx argument and just require that the bucket
// must be already activated. Correspondingly there should be no error returned.
func (b *LOBucket) Get(ctx context.Context, key int64) (_ interface{}, _ bool, err error) {
defer xerr.Contextf(&err, "bucket(%s): get %v", b.POid(), key)
err = b.PActivate(ctx)
if err != nil {
return nil, false, err
}
v, ok := b.get(key)
b.PDeactivate()
return v, ok, nil
}
// get searches LOBucket by key.
//
// no loading from database is done. The bucket must be already activated.
func (b *LOBucket) get(key int64) (interface{}, bool) {
// search i: K(i-1) < k ≤ K(i) ; K(-1) = -∞, K(len) = +∞
i := sort.Search(len(b.keys), func(i int) bool {
return key <= b.keys[i]
})
if i == len(b.keys) || b.keys[i] != key {
return nil, false // not found
}
return b.values[i], true
}
// TODO LOBucket.MinKey
// TODO LOBucket.MaxKey
// ---- serialization ----
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BucketTemplate.c#L1195:
//
// For a mapping bucket (self->values is not NULL), a one-tuple or two-tuple.
// The first element is a tuple interleaving keys and values, of length
// 2 * self->len. The second element is the next bucket, present iff next is
// non-NULL:
//
// (
// (keys[0], values[0], keys[1], values[1], ...,
// keys[len-1], values[len-1]),
// <self->next iff non-NULL>
// )
type lobucketState LOBucket // hide state methods from public API
// DropState implements Stateful to discard bucket state.
func (b *lobucketState) DropState() {
b.next = nil
b.keys = nil
b.values = nil
}
// PySetState implements PyStateful to set bucket data from pystate.
func (b *lobucketState) PySetState(pystate interface{}) (err error) {
t, ok := pystate.(pickle.Tuple)
if !ok {
return fmt.Errorf("top: expect (...); got %T", pystate)
}
if !(1 <= len(t) && len(t) <= 2) {
return fmt.Errorf("top: expect [1..2](); got [%d]()", len(t))
}
// .next present
if len(t) == 2 {
next, ok := t[1].(*LOBucket)
if !ok {
return fmt.Errorf(".next must be LOBucket; got %T", t[1])
}
b.next = next
}
// main part
t, ok = t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("data: expect (...); got %T", t[0])
}
if len(t)%2 != 0 {
return fmt.Errorf("data: expect [%%2](); got [%d]()", len(t))
}
// reset arrays just in case
n := len(t) / 2
b.keys = make([]int64, 0, n)
b.values = make([]interface{}, 0, n)
for i := 0; i < n; i++ {
xk := t[2*i]
v := t[2*i+1]
k, ok := pickletools.Xint64(xk)
if !ok {
return fmt.Errorf("data: [%d]: key must be integer; got %T", i, xk)
}
kk := int64(k)
if int64(kk) != k {
return fmt.Errorf("data: [%d]: key overflows %T", i, kk)
}
// XXX check keys are sorted?
b.keys = append(b.keys, kk)
b.values = append(b.values, v)
}
return nil
}
// https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/BTreeTemplate.c#L1087:
//
// For an empty LOBTree (self->len == 0), None.
//
// For a LOBTree with one child (self->len == 1), and that child is a bucket,
// and that bucket has a NULL oid, a one-tuple containing a one-tuple
// containing the bucket's state:
//
// (
// (
// child[0].__getstate__(),
// ),
// )
//
// Else a two-tuple. The first element is a tuple interleaving the LOBTree's
// keys and direct children, of size 2*self->len - 1 (key[0] is unused and
// is not saved). The second element is the firstbucket:
//
// (
// (child[0], key[1], child[1], key[2], child[2], ...,
// key[len-1], child[len-1]),
// self->firstbucket
// )
//
// In the above, key[i] means self->data[i].key, and similarly for child[i].
type lobtreeState LOBTree // hide state methods from public API
// DropState implements zodb.Stateful.
func (t *lobtreeState) DropState() {
t.firstbucket = nil
t.data = nil
}
// PySetState implements zodb.PyStateful to set btree data from pystate.
func (bt *lobtreeState) PySetState(pystate interface{}) (err error) {
// empty btree
if _, ok := pystate.(pickle.None); ok {
bt.firstbucket = nil
bt.data = nil
return nil
}
t, ok := pystate.(pickle.Tuple)
if !ok {
return fmt.Errorf("top: expect (...); got %T", pystate)
}
if !(1 <= len(t) && len(t) <= 2) {
return fmt.Errorf("top: expect [1..2](); got [%d]()", len(t))
}
// btree with 1 child bucket without oid
if len(t) == 1 {
t, ok := t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("bucket1: expect [1](); got %T", t[0])
}
if len(t) != 1 {
return fmt.Errorf("bucket1: expect [1](); got [%d]()", len(t))
}
bucket := zodb.NewPersistent(reflect.TypeOf(LOBucket{}), bt.PJar()).(*LOBucket)
err := (*lobucketState)(bucket).PySetState(t[0])
if err != nil {
return fmt.Errorf("bucket1: %s", err)
}
bt.firstbucket = bucket
bt.data = []_LOBTreeItem{{key: 0, child: bucket}}
return nil
}
// regular btree
bt.firstbucket, ok = t[1].(*LOBucket)
if !ok {
return fmt.Errorf("first bucket: must be LOBucket; got %T", t[1])
}
t, ok = t[0].(pickle.Tuple)
if !ok {
return fmt.Errorf("data: expect (...); got %T", t[0])
}
if len(t)%2 == 0 {
return fmt.Errorf("data: expect [!%%2](); got [%d]()", len(t))
}
n := (len(t) + 1) / 2
bt.data = make([]_LOBTreeItem, 0, n)
for i, idx := 0, 0; i < n; i++ {
key := int64(math.MinInt64) // int64(-∞) (qualifies for ≤)
if i > 0 {
// key[0] is unused and not saved
key, ok = t[idx].(int64) // XXX Xint
if !ok {
return fmt.Errorf("data: [%d]: key must be integer; got %T", i, t[idx])
}
idx++
}
child := t[idx]
idx++
kkey := int64(key)
if int64(kkey) != key {
return fmt.Errorf("data: [%d]: key overflows %T", i, kkey)
}
switch child.(type) {
default:
return fmt.Errorf("data: [%d]: child must be LOBTree|LOBucket; got %T", i, child)
case *LOBTree: // ok
case *LOBucket: // ok
}
bt.data = append(bt.data, _LOBTreeItem{key: kkey, child: child})
}
return nil
}
// ---- register classes to ZODB ----
func init() {
t := reflect.TypeOf
zodb.RegisterClass("BTrees.LOBTree.LOBTree", t(LOBTree{}), t(lobtreeState{}))
zodb.RegisterClass("BTrees.LOBTree.LOBucket", t(LOBucket{}), t(lobucketState{}))
}
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