Commit 2dc1e2e0 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent 1b3a514b
// Copyright (c) 2001, 2002 Zope Foundation and Contributors.
// All Rights Reserved.
//
// Copyright (C) 2018 Nexedi SA and Contributors. // Copyright (C) 2018 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com> // 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. // Package btree provides B⁺ Trees for ZODB.
// //
...@@ -20,415 +11,13 @@ ...@@ -20,415 +11,13 @@
// //
// A B⁺ tree consists of nodes. Only leaf tree nodes point to data. // A B⁺ tree consists of nodes. Only leaf tree nodes point to data.
// Intermediate tree nodes contains keys and pointer to next-level tree nodes. // Intermediate tree nodes contains keys and pointer to next-level tree nodes.
// B⁺ always have uniform height - that is the path to any leaf node from the // B⁺ tree always have uniform height - that is the path to any leaf node from
// tree root is the same. // the tree root is the same. XXX
// //
// BTree.Get(key) performs point-query. // BTree.Get(key) performs point-query.
// //
// B⁺ Trees // B⁺ Trees
package btree package btree
// See https://github.com/zopefoundation/BTrees/blob/4.5.0-1-gc8bf24e/BTrees/Development.txt#L198 //go:generate ./gen-btree IO int32 ziobtree.go
// for BTree & Bucket organization details. //go:generate ./gen-btree LO int64 zlobtree.go
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"
)
// KEY is the type for BTree keys.
//
// XXX -> template?
type KEY int64
// 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 []Entry
}
// Entry is one BTree node entry.
//
// It contains key and child, who is either BTree or Bucket.
//
// Key limits child's keys - see BTree.Entryv for details.
type Entry struct {
key KEY
child interface{} // BTree or Bucket
}
// 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
}
// BucketEntry is one Bucket node entry.
//
// It contains key and value.
type BucketEntry struct {
key KEY
value interface{}
}
// Key returns BTree entry key.
func (e *Entry) Key() KEY { return e.key }
// Child returns BTree entry child.
func (e *Entry) Child() interface{} { return e.child }
// Entryv returns entries of a BTree node.
//
// Entries keys limit the keys of all children reachable from an entry:
//
// [i].Key ≤ [i].Child.*.Key < [i+1].Key i ∈ [0, len([]))
//
// [0].Key = -∞ ; always returned so
// [len(ev)].Key = +∞ ; should be assumed so
//
//
// Children of all entries are guaranteed to be of the same kind - either all BTree, or all Bucket.
//
// The caller must not modify returned array.
func (t *BTree) Entryv() []Entry {
return t.data
}
// Key returns Bucket entry key.
func (e *BucketEntry) Key() KEY { return e.key }
// Value returns Bucket entry value.
func (e *BucketEntry) Value() interface{} { return e.value }
// Entryv returns entries of a Bucket node.
//
// XXX
func (b *Bucket) Entryv() []BucketEntry {
ev := make([]BucketEntry, len(b.keys))
for i, k := range b.keys {
ev[i] = BucketEntry{k, b.values[i]}
}
return ev
}
// 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 = []Entry{{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([]Entry, 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++
switch child.(type) {
default:
return fmt.Errorf("data: [%d]: child must be BTree|Bucket; got %T", i, child)
// XXX check all children are of the same type
case *BTree: // ok
case *Bucket: // ok
}
bt.data = append(bt.data, Entry{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{}))
// XXX "I" means int32 in ZODB; we reuse int64 for now
// XXX should be caught by RegisterClass as invalid? (typ registered twice)
zodb.RegisterClass("BTrees.IOBTree.IOBucket", t(Bucket{}), t(bucketState{}))
zodb.RegisterClass("BTrees.IOBTree.IOBTree", t(BTree{}), t(btreeState{}))
}
// 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"
)
// BTree is a non-leaf node of a B tree.
//
// It contains []Entry in key order.
//
// 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 []Entry
}
// Entry is one BTree node entry.
//
// It contains key and child, who is either BTree or Bucket.
//
// Key limits child's keys - see BTree.Entryv for details.
type Entry struct {
key KEY
child interface{} // BTree or Bucket
}
// Bucket is a leaf node of a B⁺ tree.
//
// It contains []BucketEntry in ↑ key order.
//
// 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
}
// BucketEntry is one Bucket node entry.
//
// It contains key and value.
type BucketEntry struct {
key KEY
value interface{}
}
// Key returns BTree entry key.
func (e *Entry) Key() KEY { return e.key }
// Child returns BTree entry child.
func (e *Entry) Child() interface{} { return e.child }
// Entryv returns entries of a BTree node.
//
// Entries keys limit the keys of all children reachable from an entry:
//
// [i].Key ≤ [i].Child.*.Key < [i+1].Key i ∈ [0, len([]))
//
// [0].Key = -∞ ; always returned so
// [len(ev)].Key = +∞ ; should be assumed so
//
//
// Children of all entries are guaranteed to be of the same kind - either all BTree, or all Bucket.
//
// The caller must not modify returned array.
func (t *BTree) Entryv() []Entry {
return t.data
}
// Key returns Bucket entry key.
func (e *BucketEntry) Key() KEY { return e.key }
// Value returns Bucket entry value.
func (e *BucketEntry) Value() interface{} { return e.value }
// Entryv returns entries of a Bucket node.
//
// XXX
func (b *Bucket) Entryv() []BucketEntry {
ev := make([]BucketEntry, len(b.keys))
for i, k := range b.keys {
ev[i] = BucketEntry{k, b.values[i]}
}
return ev
}
// 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 = []Entry{{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([]Entry, 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++
switch child.(type) {
default:
return fmt.Errorf("data: [%d]: child must be BTree|Bucket; got %T", i, child)
// XXX check all children are of the same type
case *BTree: // ok
case *Bucket: // ok
}
// XXX check key ↑
bt.data = append(bt.data, Entry{key: KEY(key), 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 ( ...@@ -33,7 +33,7 @@ import (
// kv is one (key, value) pair. // kv is one (key, value) pair.
type kv struct { type kv struct {
key KEY key int64
value interface{} value interface{}
} }
...@@ -52,7 +52,7 @@ type testEntry struct { ...@@ -52,7 +52,7 @@ type testEntry struct {
// bmapping represents Get of Bucket or BTree. // bmapping represents Get of Bucket or BTree.
type bmapping interface { type bmapping interface {
Get(context.Context, KEY) (interface{}, bool, error) Get(context.Context, int64) (interface{}, bool, error)
} }
func TestBTree(t *testing.T) { func TestBTree(t *testing.T) {
...@@ -89,11 +89,11 @@ func TestBTree(t *testing.T) { ...@@ -89,11 +89,11 @@ func TestBTree(t *testing.T) {
want := "" want := ""
switch tt.kind { switch tt.kind {
case kindBucket: case kindBucket:
if _, ok = obj.(*Bucket); !ok { if _, ok = obj.(*LOBucket); !ok {
want = "Bucket" want = "Bucket"
} }
case kindBTree: case kindBTree:
if _, ok = obj.(*BTree); !ok { if _, ok = obj.(*LOBTree); !ok {
want = "BTree" want = "BTree"
} }
default: default:
...@@ -132,12 +132,12 @@ func TestBTree(t *testing.T) { ...@@ -132,12 +132,12 @@ func TestBTree(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
B3, ok := xB3.(*BTree) B3, ok := xB3.(*LOBTree)
if !ok { if !ok {
t.Fatalf("B3: %v; got %T; want BTree", B3_oid, xB3) t.Fatalf("B3: %v; got %T; want BTree", 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) v, ok, err := B3.Get(ctx, i)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
...@@ -151,21 +151,21 @@ func TestBTree(t *testing.T) { ...@@ -151,21 +151,21 @@ func TestBTree(t *testing.T) {
} }
// verifyFirstBucket verifies that b.firstbucket is correct and returns it. // verifyFirstBucket verifies that b.firstbucket is correct and returns it.
var verifyFirstBucket func(b *BTree) *Bucket var verifyFirstBucket func(b *LOBTree) *LOBucket
verifyFirstBucket = func(b *BTree) *Bucket { verifyFirstBucket = func(b *LOBTree) *LOBucket {
err := b.PActivate(ctx); X(err) err := b.PActivate(ctx); X(err)
defer b.PDeactivate() defer b.PDeactivate()
var firstbucket *Bucket var firstbucket *LOBucket
switch child := b.data[0].child.(type) { switch child := b.data[0].child.(type) {
default: default:
t.Fatalf("btree(%s): child[0] is %T", b.POid(), b.data[0].child) t.Fatalf("btree(%s): child[0] is %T", b.POid(), b.data[0].child)
case *BTree: case *LOBTree:
firstbucket = verifyFirstBucket(child) firstbucket = verifyFirstBucket(child)
case *Bucket: case *LOBucket:
firstbucket = child firstbucket = child
} }
......
#!/bin/bash -e
# btree.go.in -> specialized with concrete types
# gen-btree KIND KEY out
# Copyright (C) 2017 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
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/\bBTree\b/${KIND}BTree/g" \
-e "s/\bEntry\b/${KIND}Entry/g" \
-e "s/\bBucket\b/${KIND}Bucket/g" \
-e "s/\bBucketEntry\b/${KIND}BucketEntry/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"
)
// IOBTree is a non-leaf node of a B⁺ tree.
//
// It contains []IOEntry in ↑ key order.
//
// It mimics ?OBTree from btree/py, with ? being any integer.
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 []IOEntry
}
// IOEntry is one IOBTree node entry.
//
// It contains key and child, who is either IOBTree or IOBucket.
//
// Key limits child's keys - see IOBTree.Entryv for details.
type IOEntry struct {
key int32
child interface{} // IOBTree or IOBucket
}
// IOBucket is a leaf node of a B⁺ tree.
//
// It contains []IOBucketEntry in ↑ key order.
//
// It mimics ?OBucket from btree/py, with ? being any integer.
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
}
// IOBucketEntry is one IOBucket node entry.
//
// It contains key and value.
type IOBucketEntry struct {
key int32
value interface{}
}
// Key returns IOBTree entry key.
func (e *IOEntry) Key() int32 { return e.key }
// Child returns IOBTree entry child.
func (e *IOEntry) Child() interface{} { return e.child }
// Entryv returns entries of a IOBTree node.
//
// Entries keys limit the keys of all children reachable from an entry:
//
// [i].Key ≤ [i].Child.*.Key < [i+1].Key i ∈ [0, len([]))
//
// [0].Key = -∞ ; always returned so
// [len(ev)].Key = +∞ ; should be assumed so
//
//
// Children of all entries are guaranteed to be of the same kind - either all IOBTree, or all IOBucket.
//
// The caller must not modify returned array.
func (t *IOBTree) Entryv() []IOEntry {
return t.data
}
// Key returns IOBucket entry key.
func (e *IOBucketEntry) Key() int32 { return e.key }
// Value returns IOBucket entry value.
func (e *IOBucketEntry) Value() interface{} { return e.value }
// Entryv returns entries of a IOBucket node.
//
// XXX
func (b *IOBucket) Entryv() []IOBucketEntry {
ev := make([]IOBucketEntry, len(b.keys))
for i, k := range b.keys {
ev[i] = IOBucketEntry{k, b.values[i]}
}
return ev
}
// 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 := xk.(int64) // XXX use int32 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, int32(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 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 = []IOEntry{{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([]IOEntry, 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++
switch child.(type) {
default:
return fmt.Errorf("data: [%d]: child must be IOBTree|IOBucket; got %T", i, child)
// XXX check all children are of the same type
case *IOBTree: // ok
case *IOBucket: // ok
}
// XXX check key ↑
bt.data = append(bt.data, IOEntry{key: int32(key), 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"
)
// LOBTree is a non-leaf node of a B⁺ tree.
//
// It contains []LOEntry in ↑ key order.
//
// It mimics ?OBTree from btree/py, with ? being any integer.
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 []LOEntry
}
// LOEntry is one LOBTree node entry.
//
// It contains key and child, who is either LOBTree or LOBucket.
//
// Key limits child's keys - see LOBTree.Entryv for details.
type LOEntry struct {
key int64
child interface{} // LOBTree or LOBucket
}
// LOBucket is a leaf node of a B⁺ tree.
//
// It contains []LOBucketEntry in ↑ key order.
//
// It mimics ?OBucket from btree/py, with ? being any integer.
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
}
// LOBucketEntry is one LOBucket node entry.
//
// It contains key and value.
type LOBucketEntry struct {
key int64
value interface{}
}
// Key returns LOBTree entry key.
func (e *LOEntry) Key() int64 { return e.key }
// Child returns LOBTree entry child.
func (e *LOEntry) Child() interface{} { return e.child }
// Entryv returns entries of a LOBTree node.
//
// Entries keys limit the keys of all children reachable from an entry:
//
// [i].Key ≤ [i].Child.*.Key < [i+1].Key i ∈ [0, len([]))
//
// [0].Key = -∞ ; always returned so
// [len(ev)].Key = +∞ ; should be assumed so
//
//
// Children of all entries are guaranteed to be of the same kind - either all LOBTree, or all LOBucket.
//
// The caller must not modify returned array.
func (t *LOBTree) Entryv() []LOEntry {
return t.data
}
// Key returns LOBucket entry key.
func (e *LOBucketEntry) Key() int64 { return e.key }
// Value returns LOBucket entry value.
func (e *LOBucketEntry) Value() interface{} { return e.value }
// Entryv returns entries of a LOBucket node.
//
// XXX
func (b *LOBucket) Entryv() []LOBucketEntry {
ev := make([]LOBucketEntry, len(b.keys))
for i, k := range b.keys {
ev[i] = LOBucketEntry{k, b.values[i]}
}
return ev
}
// 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 := xk.(int64) // XXX use int64 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, int64(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 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 = []LOEntry{{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([]LOEntry, 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++
switch child.(type) {
default:
return fmt.Errorf("data: [%d]: child must be LOBTree|LOBucket; got %T", i, child)
// XXX check all children are of the same type
case *LOBTree: // ok
case *LOBucket: // ok
}
// XXX check key ↑
bt.data = append(bt.data, LOEntry{key: int64(key), 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