upgrade buntdb and dependencies

This commit is contained in:
Shivaram Lingamneni 2023-01-15 08:26:32 -05:00
parent b2087977d0
commit 3f5de80afd
12 changed files with 2078 additions and 419 deletions

6
go.mod
View File

@ -17,7 +17,7 @@ require (
github.com/onsi/ginkgo v1.12.0 // indirect
github.com/onsi/gomega v1.9.0 // indirect
github.com/stretchr/testify v1.4.0 // indirect
github.com/tidwall/buntdb v1.2.9
github.com/tidwall/buntdb v1.2.10
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208
github.com/xdg-go/scram v1.0.2
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
@ -28,8 +28,8 @@ require (
require github.com/gofrs/flock v0.8.1
require (
github.com/tidwall/btree v1.1.0 // indirect
github.com/tidwall/gjson v1.12.1 // indirect
github.com/tidwall/btree v1.4.2 // indirect
github.com/tidwall/gjson v1.14.3 // indirect
github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect

6
go.sum
View File

@ -49,14 +49,20 @@ github.com/tidwall/btree v0.6.1 h1:75VVgBeviiDO+3g4U+7+BaNBNhNINxB0ULPT3fs9pMY=
github.com/tidwall/btree v0.6.1/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
github.com/tidwall/btree v1.1.0 h1:5P+9WU8ui5uhmcg3SoPyTwoI0mVyZ1nps7YQzTZFkYM=
github.com/tidwall/btree v1.1.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
github.com/tidwall/btree v1.4.2 h1:PpkaieETJMUxYNADsjgtNRcERX7mGc/GP2zp/r5FM3g=
github.com/tidwall/btree v1.4.2/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
github.com/tidwall/buntdb v1.2.7 h1:SIyObKAymzLyGhDeIhVk2Yc1/EwfCC75Uyu77CHlVoA=
github.com/tidwall/buntdb v1.2.7/go.mod h1:b6KvZM27x/8JLI5hgRhRu60pa3q0Tz9c50TyD46OHUM=
github.com/tidwall/buntdb v1.2.9 h1:XVz684P7X6HCTrdr385yDZWB1zt/n20ZNG3M1iGyFm4=
github.com/tidwall/buntdb v1.2.9/go.mod h1:IwyGSvvDg6hnKSIhtdZ0AqhCZGH8ukdtCAzaP8fI1X4=
github.com/tidwall/buntdb v1.2.10 h1:U/ebfkmYPBnyiNZIirUiWFcxA/mgzjbKlyPynFsPtyM=
github.com/tidwall/buntdb v1.2.10/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
github.com/tidwall/gjson v1.10.2 h1:APbLGOM0rrEkd8WBw9C24nllro4ajFuJu0Sc9hRz8Bo=
github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.1.3 h1:z9YwQAMUxVSBde3b7Sl8Da37rffgNfZ6Fq6h9t6KdXE=
github.com/tidwall/grect v0.1.3/go.mod h1:8GMjwh3gPZVpLBI/jDz9uslCe0dpxRpWDdtN0lWAS/E=
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=

View File

@ -2,26 +2,351 @@
[![GoDoc](https://godoc.org/github.com/tidwall/btree?status.svg)](https://godoc.org/github.com/tidwall/btree)
An [efficient](#performance) [B-tree](https://en.wikipedia.org/wiki/B-tree) implementation in Go.
*Check out the [generics branch](https://github.com/tidwall/btree/tree/generics) if you want to try out btree with generic support for Go 1.18+*
An efficient [B-tree](https://en.wikipedia.org/wiki/B-tree) implementation in Go.
## Features
- `Copy()` method with copy-on-write support.
- Support for [Generics](#generics) (Go 1.18+).
- `Map` and `Set` types for ordered key-value maps and sets,
- Fast bulk loading for pre-ordered data using the `Load()` method.
- All operations are thread-safe.
- `Copy()` method with copy-on-write support.
- Thread-safe operations.
- [Path hinting](PATH_HINT.md) optimization for operations with nearby keys.
## Installing
## Using
To start using btree, install Go and run `go get`:
To start using this package, install Go and run:
```sh
$ go get -u github.com/tidwall/btree
$ go get github.com/tidwall/btree
```
## Usage
## B-tree types
This package includes the following types of B-trees:
- [`btree.Map`](#btreemap):
A fast B-tree for storing ordered key value pairs.
Go 1.18+
- [`btree.Set`](#btreeset):
Like `Map`, but only for storing keys.
Go 1.18+
- [`btree.BTreeG`](#btreegeneric):
A feature-rich B-tree for storing data using a custom comparator.
Go 1.18+
- [`btree.BTree`](#btreebtree):
Like `BTreeG` but uses the `interface{}` type for data. Backwards compatible.
Go 1.16+
### btree.Map
```go
// Basic
Set(key, value) // insert or replace an item
Get(key, value) // get an existing item
Delete(key) // delete an item
Len() // return the number of items in the map
// Iteration
Scan(iter) // scan items in ascending order
Reverse(iter) // scan items in descending order
Ascend(key, iter) // scan items in ascending order that are >= to key
Descend(key, iter) // scan items in descending order that are <= to key.
Iter() // returns a read-only iterator for for-loops.
// Array-like operations
GetAt(index) // returns the item at index
DeleteAt(index) // deletes the item at index
// Bulk-loading
Load(key, value) // load presorted items into tree
```
#### Example
```go
package main
import (
"fmt"
"github.com/tidwall/btree"
)
func main() {
// create a map
var users btree.Map[string, string]
// add some users
users.Set("user:4", "Andrea")
users.Set("user:6", "Andy")
users.Set("user:2", "Andy")
users.Set("user:1", "Jane")
users.Set("user:5", "Janet")
users.Set("user:3", "Steve")
// Iterate over the maps and print each user
users.Scan(func(key, value string) bool {
fmt.Printf("%s %s\n", key, value)
return true
})
fmt.Printf("\n")
// Delete a couple
users.Delete("user:5")
users.Delete("user:1")
// print the map again
users.Scan(func(key, value string) bool {
fmt.Printf("%s %s\n", key, value)
return true
})
fmt.Printf("\n")
// Output:
// user:1 Jane
// user:2 Andy
// user:3 Steve
// user:4 Andrea
// user:5 Janet
// user:6 Andy
//
// user:2 Andy
// user:3 Steve
// user:4 Andrea
// user:6 Andy
}
```
### btree.Set
```go
// Basic
Insert(key) // insert an item
Contains(key) // test if item exists
Delete(key) // delete an item
Len() // return the number of items in the set
// Iteration
Scan(iter) // scan items in ascending order
Reverse(iter) // scan items in descending order
Ascend(key, iter) // scan items in ascending order that are >= to key
Descend(key, iter) // scan items in descending order that are <= to key.
Iter() // returns a read-only iterator for for-loops.
// Array-like operations
GetAt(index) // returns the item at index
DeleteAt(index) // deletes the item at index
// Bulk-loading
Load(key) // load presorted item into tree
```
#### Example
```go
package main
import (
"fmt"
"github.com/tidwall/btree"
)
func main() {
// create a set
var names btree.Set[string]
// add some names
names.Insert("Jane")
names.Insert("Andrea")
names.Insert("Steve")
names.Insert("Andy")
names.Insert("Janet")
names.Insert("Andy")
// Iterate over the maps and print each user
names.Scan(func(key string) bool {
fmt.Printf("%s\n", key)
return true
})
fmt.Printf("\n")
// Delete a couple
names.Delete("Steve")
names.Delete("Andy")
// print the map again
names.Scan(func(key string) bool {
fmt.Printf("%s\n", key)
return true
})
fmt.Printf("\n")
// Output:
// Andrea
// Andy
// Jane
// Janet
// Steve
//
// Andrea
// Jane
// Janet
}
```
### btree.BTreeG
```go
// Basic
Set(item) // insert or replace an item
Get(item) // get an existing item
Delete(item) // delete an item
Len() // return the number of items in the btree
// Iteration
Scan(iter) // scan items in ascending order
Reverse(iter) // scan items in descending order
Ascend(key, iter) // scan items in ascending order that are >= to key
Descend(key, iter) // scan items in descending order that are <= to key.
Iter() // returns a read-only iterator for for-loops.
// Array-like operations
GetAt(index) // returns the item at index
DeleteAt(index) // deletes the item at index
// Bulk-loading
Load(item) // load presorted items into tree
// Path hinting
SetHint(item, *hint) // insert or replace an existing item
GetHint(item, *hint) // get an existing item
DeleteHint(item, *hint) // delete an item
// Copy-on-write
Copy() // copy the btree
```
#### Example
```go
package main
import (
"fmt"
"github.com/tidwall/btree"
)
type Item struct {
Key, Val string
}
// byKeys is a comparison function that compares item keys and returns true
// when a is less than b.
func byKeys(a, b Item) bool {
return a.Key < b.Key
}
// byVals is a comparison function that compares item values and returns true
// when a is less than b.
func byVals(a, b Item) bool {
if a.Val < b.Val {
return true
}
if a.Val > b.Val {
return false
}
// Both vals are equal so we should fall though
// and let the key comparison take over.
return byKeys(a, b)
}
func main() {
// Create a tree for keys and a tree for values.
// The "keys" tree will be sorted on the Keys field.
// The "values" tree will be sorted on the Values field.
keys := btree.NewBTreeG[Item](byKeys)
vals := btree.NewBTreeG[Item](byVals)
// Create some items.
users := []Item{
Item{Key: "user:1", Val: "Jane"},
Item{Key: "user:2", Val: "Andy"},
Item{Key: "user:3", Val: "Steve"},
Item{Key: "user:4", Val: "Andrea"},
Item{Key: "user:5", Val: "Janet"},
Item{Key: "user:6", Val: "Andy"},
}
// Insert each user into both trees
for _, user := range users {
keys.Set(user)
vals.Set(user)
}
// Iterate over each user in the key tree
keys.Scan(func(item Item) bool {
fmt.Printf("%s %s\n", item.Key, item.Val)
return true
})
fmt.Printf("\n")
// Iterate over each user in the val tree
vals.Scan(func(item Item) bool {
fmt.Printf("%s %s\n", item.Key, item.Val)
return true
})
// Output:
// user:1 Jane
// user:2 Andy
// user:3 Steve
// user:4 Andrea
// user:5 Janet
// user:6 Andy
//
// user:4 Andrea
// user:2 Andy
// user:6 Andy
// user:1 Jane
// user:5 Janet
// user:3 Steve
}
```
### btree.BTree
```go
// Basic
Set(item) // insert or replace an item
Get(item) // get an existing item
Delete(item) // delete an item
Len() // return the number of items in the btree
// Iteration
Scan(iter) // scan items in ascending order
Reverse(iter) // scan items in descending order
Ascend(key, iter) // scan items in ascending order that are >= to key
Descend(key, iter) // scan items in descending order that are <= to key.
Iter() // returns a read-only iterator for for-loops.
// Array-like operations
GetAt(index) // returns the item at index
DeleteAt(index) // deletes the item at index
// Bulk-loading
Load(item) // load presorted items into tree
// Path hinting
SetHint(item, *hint) // insert or replace an existing item
GetHint(item, *hint) // get an existing item
DeleteHint(item, *hint) // delete an item
// Copy-on-write
Copy() // copy the btree
```
#### Example
```go
package main
@ -113,92 +438,9 @@ func main() {
}
```
## Operations
### Basic
```
Get(item) # get an existing item
Set(item) # insert or replace an existing item
Delete(item) # delete an item
Len() # return the number of items in the btree
```
### Iteration
```
Ascend(pivot, iter) # scan items in ascending order starting at pivot.
Descend(pivot, iter) # scan items in descending order starting at pivot.
Iter() # returns a read-only iterator for for-loops.
```
### Queues
```
Min() # return the first item in the btree
Max() # return the last item in the btree
PopMin() # remove and return the first item in the btree
PopMax() # remove and return the last item in the btree
```
### Bulk loading
```
Load(item) # load presorted items into tree
```
### Path hints
```
SetHint(item, *hint) # insert or replace an existing item
GetHint(item, *hint) # get an existing item
DeleteHint(item, *hint) # delete an item
```
### Array-like operations
```
GetAt(index) # returns the value at index
DeleteAt(index) # deletes the item at index
```
## Performance
This implementation was designed with performance in mind.
The following benchmarks were run on my 2019 Macbook Pro (2.4 GHz 8-Core Intel Core i9) using Go 1.17.3. The items are simple 8-byte ints.
- `google`: The [google/btree](https://github.com/google/btree) package
- `tidwall`: The [tidwall/btree](https://github.com/tidwall/btree) package
- `go-arr`: Just a simple Go array
```
** sequential set **
google: set-seq 1,000,000 ops in 178ms, 5,618,049/sec, 177 ns/op, 39.0 MB, 40 bytes/op
tidwall: set-seq 1,000,000 ops in 156ms, 6,389,837/sec, 156 ns/op, 23.5 MB, 24 bytes/op
tidwall: set-seq-hint 1,000,000 ops in 78ms, 12,895,355/sec, 77 ns/op, 23.5 MB, 24 bytes/op
tidwall: load-seq 1,000,000 ops in 53ms, 18,937,400/sec, 52 ns/op, 23.5 MB, 24 bytes/op
go-arr: append 1,000,000 ops in 78ms, 12,843,432/sec, 77 ns/op
** random set **
google: set-rand 1,000,000 ops in 555ms, 1,803,133/sec, 554 ns/op, 29.7 MB, 31 bytes/op
tidwall: set-rand 1,000,000 ops in 545ms, 1,835,818/sec, 544 ns/op, 29.6 MB, 31 bytes/op
tidwall: set-rand-hint 1,000,000 ops in 670ms, 1,493,473/sec, 669 ns/op, 29.6 MB, 31 bytes/op
tidwall: set-again 1,000,000 ops in 681ms, 1,469,038/sec, 680 ns/op
tidwall: set-after-copy 1,000,000 ops in 670ms, 1,493,230/sec, 669 ns/op
tidwall: load-rand 1,000,000 ops in 569ms, 1,756,187/sec, 569 ns/op, 29.6 MB, 31 bytes/op
** sequential get **
google: get-seq 1,000,000 ops in 165ms, 6,048,307/sec, 165 ns/op
tidwall: get-seq 1,000,000 ops in 144ms, 6,940,120/sec, 144 ns/op
tidwall: get-seq-hint 1,000,000 ops in 78ms, 12,815,243/sec, 78 ns/op
** random get **
google: get-rand 1,000,000 ops in 701ms, 1,427,507/sec, 700 ns/op
tidwall: get-rand 1,000,000 ops in 679ms, 1,473,531/sec, 678 ns/op
tidwall: get-rand-hint 1,000,000 ops in 824ms, 1,213,805/sec, 823 ns/op
```
*You can find the benchmark utility at [tidwall/btree-benchmark](https://github.com/tidwall/btree-benchmark)*
See [tidwall/btree-benchmark](https://github.com/tidwall/btree-benchmark) for benchmark numbers.
## Contact

View File

@ -3,25 +3,17 @@
// license that can be found in the LICENSE file.
package btree
import btree "github.com/tidwall/btree/internal"
type BTree struct {
base *btree.BTree
base *BTreeG[any]
}
// PathHint is a utility type used with the *Hint() functions. Hints provide
// faster operations for clustered keys.
type PathHint = btree.PathHint
// New returns a new BTree
func New(less func(a, b interface{}) bool) *BTree {
func New(less func(a, b any) bool) *BTree {
if less == nil {
panic("nil less")
}
return &BTree{
base: btree.NewOptions(btree.Options{
Context: less,
}),
base: NewBTreeG(less),
}
}
@ -30,31 +22,33 @@ func New(less func(a, b interface{}) bool) *BTree {
//
// This is useful for when you do not need the BTree to manage the locking,
// but would rather do it yourself.
func NewNonConcurrent(less func(a, b interface{}) bool) *BTree {
func NewNonConcurrent(less func(a, b any) bool) *BTree {
if less == nil {
panic("nil less")
}
return &BTree{
base: btree.NewOptions(btree.Options{
Context: less,
NoLocks: true,
}),
base: NewBTreeGOptions(less,
Options{
NoLocks: true,
}),
}
}
// Less is a convenience function that performs a comparison of two items
// using the same "less" function provided to New.
func (tr *BTree) Less(a, b interface{}) bool {
func (tr *BTree) Less(a, b any) bool {
return tr.base.Less(a, b)
}
// Set or replace a value for a key
func (tr *BTree) Set(item interface{}) interface{} {
// Returns the value for the replaced item or nil if the key was not found.
func (tr *BTree) Set(item any) (prev any) {
return tr.SetHint(item, nil)
}
// SetHint sets or replace a value for a key using a path hint
func (tr *BTree) SetHint(item interface{}, hint *PathHint) (prev interface{}) {
// Returns the value for the replaced item or nil if the key was not found.
func (tr *BTree) SetHint(item any, hint *PathHint) (prev any) {
if item == nil {
panic("nil item")
}
@ -65,13 +59,15 @@ func (tr *BTree) SetHint(item interface{}, hint *PathHint) (prev interface{}) {
return v
}
// Get a value for key
func (tr *BTree) Get(key interface{}) interface{} {
// Get a value for key.
// Returns nil if the key was not found.
func (tr *BTree) Get(key any) any {
return tr.GetHint(key, nil)
}
// GetHint gets a value for key using a path hint
func (tr *BTree) GetHint(key interface{}, hint *PathHint) interface{} {
// GetHint gets a value for key using a path hint.
// Returns nil if the item was not found.
func (tr *BTree) GetHint(key any, hint *PathHint) (value any) {
if key == nil {
return nil
}
@ -87,13 +83,15 @@ func (tr *BTree) Len() int {
return tr.base.Len()
}
// Delete a value for a key
func (tr *BTree) Delete(key interface{}) interface{} {
// Delete an item for a key.
// Returns the deleted value or nil if the key was not found.
func (tr *BTree) Delete(key any) (prev any) {
return tr.DeleteHint(key, nil)
}
// DeleteHint deletes a value for a key using a path hint
func (tr *BTree) DeleteHint(key interface{}, hint *PathHint) interface{} {
// Returns the deleted value or nil if the key was not found.
func (tr *BTree) DeleteHint(key any, hint *PathHint) (prev any) {
if key == nil {
return nil
}
@ -107,7 +105,7 @@ func (tr *BTree) DeleteHint(key interface{}, hint *PathHint) interface{} {
// Ascend the tree within the range [pivot, last]
// Pass nil for pivot to scan all item in ascending order
// Return false to stop iterating
func (tr *BTree) Ascend(pivot interface{}, iter func(item interface{}) bool) {
func (tr *BTree) Ascend(pivot any, iter func(item any) bool) {
if pivot == nil {
tr.base.Scan(iter)
} else {
@ -118,7 +116,7 @@ func (tr *BTree) Ascend(pivot interface{}, iter func(item interface{}) bool) {
// Descend the tree within the range [pivot, first]
// Pass nil for pivot to scan all item in descending order
// Return false to stop iterating
func (tr *BTree) Descend(pivot interface{}, iter func(item interface{}) bool) {
func (tr *BTree) Descend(pivot any, iter func(item any) bool) {
if pivot == nil {
tr.base.Reverse(iter)
} else {
@ -127,7 +125,9 @@ func (tr *BTree) Descend(pivot interface{}, iter func(item interface{}) bool) {
}
// Load is for bulk loading pre-sorted items
func (tr *BTree) Load(item interface{}) interface{} {
// If the load replaces and existing item then the value for the replaced item
// is returned.
func (tr *BTree) Load(item any) (prev any) {
if item == nil {
panic("nil item")
}
@ -140,7 +140,7 @@ func (tr *BTree) Load(item interface{}) interface{} {
// Min returns the minimum item in tree.
// Returns nil if the tree has no items.
func (tr *BTree) Min() interface{} {
func (tr *BTree) Min() any {
v, ok := tr.base.Min()
if !ok {
return nil
@ -150,7 +150,7 @@ func (tr *BTree) Min() interface{} {
// Max returns the maximum item in tree.
// Returns nil if the tree has no items.
func (tr *BTree) Max() interface{} {
func (tr *BTree) Max() any {
v, ok := tr.base.Max()
if !ok {
return nil
@ -160,7 +160,7 @@ func (tr *BTree) Max() interface{} {
// PopMin removes the minimum item in tree and returns it.
// Returns nil if the tree has no items.
func (tr *BTree) PopMin() interface{} {
func (tr *BTree) PopMin() any {
v, ok := tr.base.PopMin()
if !ok {
return nil
@ -168,9 +168,9 @@ func (tr *BTree) PopMin() interface{} {
return v
}
// PopMax removes the minimum item in tree and returns it.
// PopMax removes the maximum item in tree and returns it.
// Returns nil if the tree has no items.
func (tr *BTree) PopMax() interface{} {
func (tr *BTree) PopMax() any {
v, ok := tr.base.PopMax()
if !ok {
return nil
@ -180,7 +180,7 @@ func (tr *BTree) PopMax() interface{} {
// GetAt returns the value at index.
// Return nil if the tree is empty or the index is out of bounds.
func (tr *BTree) GetAt(index int) interface{} {
func (tr *BTree) GetAt(index int) any {
v, ok := tr.base.GetAt(index)
if !ok {
return nil
@ -190,7 +190,7 @@ func (tr *BTree) GetAt(index int) interface{} {
// DeleteAt deletes the item at index.
// Return nil if the tree is empty or the index is out of bounds.
func (tr *BTree) DeleteAt(index int) interface{} {
func (tr *BTree) DeleteAt(index int) any {
v, ok := tr.base.DeleteAt(index)
if !ok {
return nil
@ -206,8 +206,8 @@ func (tr *BTree) Height() int {
// Walk iterates over all items in tree, in order.
// The items param will contain one or more items.
func (tr *BTree) Walk(iter func(items []interface{})) {
tr.base.Walk(func(items []interface{}) bool {
func (tr *BTree) Walk(iter func(items []any)) {
tr.base.Walk(func(items []any) bool {
iter(items)
return true
})
@ -220,7 +220,7 @@ func (tr *BTree) Copy() *BTree {
}
type Iter struct {
base btree.Iter
base GenericIter[any]
}
// Iter returns a read-only iterator.
@ -231,7 +231,7 @@ func (tr *BTree) Iter() Iter {
// Seek to item greater-or-equal-to key.
// Returns false if there was no item found.
func (iter *Iter) Seek(key interface{}) bool {
func (iter *Iter) Seek(key any) bool {
return iter.base.Seek(key)
}
@ -268,6 +268,6 @@ func (iter *Iter) Prev() bool {
}
// Item returns the current iterator item.
func (iter *Iter) Item() interface{} {
func (iter *Iter) Item() any {
return iter.base.Item()
}

View File

@ -1,178 +1,109 @@
// Copyright 2020 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be
// found in the LICENSE file at https://github.com/tidwall/btree/LICENSE
///////////////////////////////////////////////////////////////////////////////
// BEGIN PARAMS
///////////////////////////////////////////////////////////////////////////////
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package btree
import "sync"
import (
"sync"
"sync/atomic"
)
// degree is the B-Tree degree, which is equal to maximum number of children
// pre node times two.
// The default is 128, which means each node can have 255 items and 256 child
// nodes.
const degree = 128
const (
degree = 128
maxItems = degree*2 - 1 // max items per node. max children is +1
minItems = maxItems / 2
)
// kind is the item type.
// It's important to use the equal symbol, which tells Go to create an alias of
// the type, rather than creating an entirely new type.
type kind = interface{}
// contextKind is the kind of context that can be passed to NewOptions and the
// less function
type contextKind = interface{}
// less returns true if A is less than B.
// The value of context will be whatever was passed to NewOptions through the
// Options.Context field, otherwise nil if the field was not set.
func less(a, b kind, context contextKind) bool {
return context.(func(a, b contextKind) bool)(a, b)
}
// BTree aliases
// These are aliases to the local bTree types and functions, which are exported
// to allow for public use at a package level.
// Rename them if desired, or comment them out to make the library private.
type BTree = bTree
type Options = bOptions
type PathHint = bPathHint
type Iter = bIter
func New(less func(a, b kind) bool) *bTree { return bNew() }
func NewOptions(opts bOptions) *bTree { return bNewOptions(opts) }
// The functions below, which begin with "test*", are required by the
// btree_test.go file. If you choose not use include the btree_test.go file in
// your project then these functions may be omitted.
// testCustomSeed can be used to generate a custom random seed for testing.
// Returning false will use time.Now().UnixNano()
func testCustomSeed() (seed int64, ok bool) {
return 0, false
}
// testMakeItem must return a valid item for testing.
// It's required that the returned item maintains equal order as the
// provided int, such that:
// testMakeItem(0) < testMakeItem(1) < testMakeItem(2) < testMakeItem(10)
func testMakeItem(x int) (item kind) {
return x
}
// testNewBTree must return an operational btree for testing.
func testNewBTree() *bTree {
return bNewOptions(bOptions{
Context: func(a, b contextKind) bool {
if a == nil {
return b != nil
} else if b == nil {
return false
}
return a.(int) < b.(int)
},
})
}
///////////////////////////////////////////////////////////////////////////////
// END PARAMS
///////////////////////////////////////////////////////////////////////////////
// Do not edit code below this line.
const maxItems = degree*2 - 1 // max items per node. max children is +1
const minItems = maxItems / 2
type bTree struct {
type BTreeG[T any] struct {
mu *sync.RWMutex
cow *cow
root *node
cow uint64
root *node[T]
count int
ctx contextKind
locks bool
empty kind
less func(a, b T) bool
empty T
}
type node struct {
cow *cow
type node[T any] struct {
cow uint64
count int
items []kind
children *[]*node
items []T
children *[]*node[T]
}
type cow struct {
_ int // cannot be an empty struct
}
func (tr *bTree) newNode(leaf bool) *node {
n := &node{cow: tr.cow}
if !leaf {
n.children = new([]*node)
}
return n
}
// leaf returns true if the node is a leaf.
func (n *node) leaf() bool {
return n.children == nil
}
var gcow uint64
// PathHint is a utility type used with the *Hint() functions. Hints provide
// faster operations for clustered keys.
type bPathHint struct {
type PathHint struct {
used [8]bool
path [8]uint8
}
type bOptions struct {
// Options for passing to New when creating a new BTree.
type Options struct {
NoLocks bool
Context contextKind
}
// New returns a new BTree
func bNew() *bTree {
return bNewOptions(bOptions{})
func NewBTreeG[T any](less func(a, b T) bool) *BTreeG[T] {
return NewBTreeGOptions(less, Options{})
}
func bNewOptions(opts bOptions) *bTree {
tr := new(bTree)
tr.cow = new(cow)
func NewBTreeGOptions[T any](less func(a, b T) bool, opts Options) *BTreeG[T] {
tr := new(BTreeG[T])
tr.cow = atomic.AddUint64(&gcow, 1)
tr.mu = new(sync.RWMutex)
tr.ctx = opts.Context
tr.less = less
tr.locks = !opts.NoLocks
return tr
}
// Less is a convenience function that performs a comparison of two items
// using the same "less" function provided to New.
func (tr *bTree) Less(a, b kind) bool {
return less(a, b, tr.ctx)
func (tr *BTreeG[T]) Less(a, b T) bool {
return tr.less(a, b)
}
func (tr *bTree) find(n *node, key kind,
hint *bPathHint, depth int,
func (tr *BTreeG[T]) newNode(leaf bool) *node[T] {
n := &node[T]{cow: tr.cow}
if !leaf {
n.children = new([]*node[T])
}
return n
}
// leaf returns true if the node is a leaf.
func (n *node[T]) leaf() bool {
return n.children == nil
}
func (tr *BTreeG[T]) bsearch(n *node[T], key T) (index int, found bool) {
low, high := 0, len(n.items)
for low < high {
h := int(uint(low+high) >> 1)
if !tr.less(key, n.items[h]) {
low = h + 1
} else {
high = h
}
}
if low > 0 && !tr.less(n.items[low-1], key) {
return low - 1, true
}
return low, false
}
func (tr *BTreeG[T]) find(n *node[T], key T, hint *PathHint, depth int,
) (index int, found bool) {
if hint == nil {
// fast path for no hinting
low := 0
high := len(n.items)
for low < high {
mid := (low + high) / 2
if !tr.Less(key, n.items[mid]) {
low = mid + 1
} else {
high = mid
}
}
if low > 0 && !tr.Less(n.items[low-1], key) {
return low - 1, true
}
return low, false
return tr.bsearch(n, key)
}
return tr.hintsearch(n, key, hint, depth)
}
// Try using hint.
func (tr *BTreeG[T]) hintsearch(n *node[T], key T, hint *PathHint, depth int,
) (index int, found bool) {
// Best case finds the exact match, updates the hint and returns.
// Worst case, updates the low and high bounds to binary search between.
low := 0
@ -247,17 +178,21 @@ path_match:
}
// SetHint sets or replace a value for a key using a path hint
func (tr *bTree) SetHint(item kind, hint *bPathHint) (prev kind, replaced bool) {
if tr.lock() {
defer tr.unlock()
func (tr *BTreeG[T]) SetHint(item T, hint *PathHint) (prev T, replaced bool) {
if tr.locks {
tr.mu.Lock()
prev, replaced = tr.setHint(item, hint)
tr.mu.Unlock()
} else {
prev, replaced = tr.setHint(item, hint)
}
return tr.setHint(item, hint)
return prev, replaced
}
func (tr *bTree) setHint(item kind, hint *bPathHint) (prev kind, replaced bool) {
func (tr *BTreeG[T]) setHint(item T, hint *PathHint) (prev T, replaced bool) {
if tr.root == nil {
tr.root = tr.newNode(true)
tr.root.items = append([]kind{}, item)
tr.root.items = append([]T{}, item)
tr.root.count = 1
tr.count = 1
return tr.empty, false
@ -267,9 +202,9 @@ func (tr *bTree) setHint(item kind, hint *bPathHint) (prev kind, replaced bool)
left := tr.cowLoad(&tr.root)
right, median := tr.nodeSplit(left)
tr.root = tr.newNode(false)
*tr.root.children = make([]*node, 0, maxItems+1)
*tr.root.children = append([]*node{}, left, right)
tr.root.items = append([]kind{}, median)
*tr.root.children = make([]*node[T], 0, maxItems+1)
*tr.root.children = append([]*node[T]{}, left, right)
tr.root.items = append([]T{}, median)
tr.root.updateCount()
return tr.setHint(item, hint)
}
@ -281,39 +216,61 @@ func (tr *bTree) setHint(item kind, hint *bPathHint) (prev kind, replaced bool)
}
// Set or replace a value for a key
func (tr *bTree) Set(item kind) (kind, bool) {
func (tr *BTreeG[T]) Set(item T) (T, bool) {
return tr.SetHint(item, nil)
}
func (tr *bTree) nodeSplit(n *node) (right *node, median kind) {
func (tr *BTreeG[T]) nodeSplit(n *node[T]) (right *node[T], median T) {
i := maxItems / 2
median = n.items[i]
// left node
left := tr.newNode(n.leaf())
left.items = make([]kind, len(n.items[:i]), maxItems/2)
copy(left.items, n.items[:i])
if !n.leaf() {
*left.children = make([]*node, len((*n.children)[:i+1]), maxItems+1)
copy(*left.children, (*n.children)[:i+1])
}
left.updateCount()
const sliceItems = true
// right node
right = tr.newNode(n.leaf())
right.items = make([]kind, len(n.items[i+1:]), maxItems/2)
copy(right.items, n.items[i+1:])
if !n.leaf() {
*right.children = make([]*node, len((*n.children)[i+1:]), maxItems+1)
copy(*right.children, (*n.children)[i+1:])
if sliceItems {
right.items = n.items[i+1:]
if !n.leaf() {
*right.children = (*n.children)[i+1:]
}
} else {
right.items = make([]T, len(n.items[i+1:]), maxItems/2)
copy(right.items, n.items[i+1:])
if !n.leaf() {
*right.children =
make([]*node[T], len((*n.children)[i+1:]), maxItems+1)
copy(*right.children, (*n.children)[i+1:])
}
}
right.updateCount()
*n = *left
// left node
if sliceItems {
n.items[i] = tr.empty
n.items = n.items[:i:i]
if !n.leaf() {
*n.children = (*n.children)[: i+1 : i+1]
}
} else {
for j := i; j < len(n.items); j++ {
n.items[j] = tr.empty
}
if !n.leaf() {
for j := i + 1; j < len((*n.children)); j++ {
(*n.children)[j] = nil
}
}
n.items = n.items[:i]
if !n.leaf() {
*n.children = (*n.children)[:i+1]
}
}
n.updateCount()
return right, median
}
func (n *node) updateCount() {
func (n *node[T]) updateCount() {
n.count = len(n.items)
if !n.leaf() {
for i := 0; i < len(*n.children); i++ {
@ -326,33 +283,42 @@ func (n *node) updateCount() {
// called outside of heavy copy-on-write situations. Marking it "noinline"
// allows for the parent cowLoad to be inlined.
// go:noinline
func (tr *bTree) copy(n *node) *node {
n2 := new(node)
func (tr *BTreeG[T]) copy(n *node[T]) *node[T] {
n2 := new(node[T])
n2.cow = tr.cow
n2.count = n.count
n2.items = make([]kind, len(n.items), cap(n.items))
n2.items = make([]T, len(n.items), cap(n.items))
copy(n2.items, n.items)
if !n.leaf() {
n2.children = new([]*node)
*n2.children = make([]*node, len(*n.children), maxItems+1)
n2.children = new([]*node[T])
*n2.children = make([]*node[T], len(*n.children), maxItems+1)
copy(*n2.children, *n.children)
}
return n2
}
// cowLoad loads the provided node and, if needed, performs a copy-on-write.
func (tr *bTree) cowLoad(cn **node) *node {
func (tr *BTreeG[T]) cowLoad(cn **node[T]) *node[T] {
if (*cn).cow != tr.cow {
*cn = tr.copy(*cn)
}
return *cn
}
func (tr *bTree) nodeSet(cn **node, item kind,
hint *bPathHint, depth int,
) (prev kind, replaced bool, split bool) {
n := tr.cowLoad(cn)
i, found := tr.find(n, item, hint, depth)
func (tr *BTreeG[T]) nodeSet(cn **node[T], item T,
hint *PathHint, depth int,
) (prev T, replaced bool, split bool) {
if (*cn).cow != tr.cow {
*cn = tr.copy(*cn)
}
n := *cn
var i int
var found bool
if hint == nil {
i, found = tr.bsearch(n, item)
} else {
i, found = tr.hintsearch(n, item, hint, depth)
}
if found {
prev = n.items[i]
n.items[i] = item
@ -388,7 +354,7 @@ func (tr *bTree) nodeSet(cn **node, item kind,
return prev, replaced, false
}
func (tr *bTree) Scan(iter func(item kind) bool) {
func (tr *BTreeG[T]) Scan(iter func(item T) bool) {
if tr.rlock() {
defer tr.runlock()
}
@ -398,7 +364,7 @@ func (tr *bTree) Scan(iter func(item kind) bool) {
tr.root.scan(iter)
}
func (n *node) scan(iter func(item kind) bool) bool {
func (n *node[T]) scan(iter func(item T) bool) bool {
if n.leaf() {
for i := 0; i < len(n.items); i++ {
if !iter(n.items[i]) {
@ -419,15 +385,36 @@ func (n *node) scan(iter func(item kind) bool) bool {
}
// Get a value for key
func (tr *bTree) Get(key kind) (kind, bool) {
return tr.GetHint(key, nil)
func (tr *BTreeG[T]) Get(key T) (T, bool) {
if tr.locks {
return tr.GetHint(key, nil)
}
if tr.root == nil {
return tr.empty, false
}
n := tr.root
for {
i, found := tr.bsearch(n, key)
if found {
return n.items[i], true
}
if n.children == nil {
return tr.empty, false
}
n = (*n.children)[i]
}
}
// GetHint gets a value for key using a path hint
func (tr *bTree) GetHint(key kind, hint *bPathHint) (kind, bool) {
func (tr *BTreeG[T]) GetHint(key T, hint *PathHint) (value T, ok bool) {
if tr.rlock() {
defer tr.runlock()
}
return tr.getHint(key, hint)
}
// GetHint gets a value for key using a path hint
func (tr *BTreeG[T]) getHint(key T, hint *PathHint) (T, bool) {
if tr.root == nil {
return tr.empty, false
}
@ -447,24 +434,27 @@ func (tr *bTree) GetHint(key kind, hint *bPathHint) (kind, bool) {
}
// Len returns the number of items in the tree
func (tr *bTree) Len() int {
func (tr *BTreeG[T]) Len() int {
return tr.count
}
// Delete a value for a key
func (tr *bTree) Delete(key kind) (kind, bool) {
// Delete a value for a key and returns the deleted value.
// Returns false if there was no value by that key found.
func (tr *BTreeG[T]) Delete(key T) (T, bool) {
return tr.DeleteHint(key, nil)
}
// DeleteHint deletes a value for a key using a path hint
func (tr *bTree) DeleteHint(key kind, hint *bPathHint) (kind, bool) {
// DeleteHint deletes a value for a key using a path hint and returns the
// deleted value.
// Returns false if there was no value by that key found.
func (tr *BTreeG[T]) DeleteHint(key T, hint *PathHint) (T, bool) {
if tr.lock() {
defer tr.unlock()
}
return tr.deleteHint(key, hint)
}
func (tr *bTree) deleteHint(key kind, hint *bPathHint) (kind, bool) {
func (tr *BTreeG[T]) deleteHint(key T, hint *PathHint) (T, bool) {
if tr.root == nil {
return tr.empty, false
}
@ -482,9 +472,9 @@ func (tr *bTree) deleteHint(key kind, hint *bPathHint) (kind, bool) {
return prev, true
}
func (tr *bTree) delete(cn **node, max bool, key kind,
hint *bPathHint, depth int,
) (kind, bool) {
func (tr *BTreeG[T]) delete(cn **node[T], max bool, key T,
hint *PathHint, depth int,
) (T, bool) {
n := tr.cowLoad(cn)
var i int
var found bool
@ -506,7 +496,7 @@ func (tr *bTree) delete(cn **node, max bool, key kind,
return tr.empty, false
}
var prev kind
var prev T
var deleted bool
if found {
if max {
@ -529,13 +519,12 @@ func (tr *bTree) delete(cn **node, max bool, key kind,
tr.nodeRebalance(n, i)
}
return prev, true
}
// nodeRebalance rebalances the child nodes following a delete operation.
// Provide the index of the child node with the number of items that fell
// below minItems.
func (tr *bTree) nodeRebalance(n *node, i int) {
func (tr *BTreeG[T]) nodeRebalance(n *node[T], i int) {
if i == len(n.items) {
i--
}
@ -618,7 +607,7 @@ func (tr *bTree) nodeRebalance(n *node, i int) {
// Ascend the tree within the range [pivot, last]
// Pass nil for pivot to scan all item in ascending order
// Return false to stop iterating
func (tr *bTree) Ascend(pivot kind, iter func(item kind) bool) {
func (tr *BTreeG[T]) Ascend(pivot T, iter func(item T) bool) {
if tr.rlock() {
defer tr.runlock()
}
@ -630,8 +619,8 @@ func (tr *bTree) Ascend(pivot kind, iter func(item kind) bool) {
// The return value of this function determines whether we should keep iterating
// upon this functions return.
func (tr *bTree) ascend(n *node, pivot kind,
hint *bPathHint, depth int, iter func(item kind) bool,
func (tr *BTreeG[T]) ascend(n *node[T], pivot T,
hint *PathHint, depth int, iter func(item T) bool,
) bool {
i, found := tr.find(n, pivot, hint, depth)
if !found {
@ -658,7 +647,7 @@ func (tr *bTree) ascend(n *node, pivot kind,
return true
}
func (tr *bTree) Reverse(iter func(item kind) bool) {
func (tr *BTreeG[T]) Reverse(iter func(item T) bool) {
if tr.rlock() {
defer tr.runlock()
}
@ -668,7 +657,7 @@ func (tr *bTree) Reverse(iter func(item kind) bool) {
tr.root.reverse(iter)
}
func (n *node) reverse(iter func(item kind) bool) bool {
func (n *node[T]) reverse(iter func(item T) bool) bool {
if n.leaf() {
for i := len(n.items) - 1; i >= 0; i-- {
if !iter(n.items[i]) {
@ -694,7 +683,7 @@ func (n *node) reverse(iter func(item kind) bool) bool {
// Descend the tree within the range [pivot, first]
// Pass nil for pivot to scan all item in descending order
// Return false to stop iterating
func (tr *bTree) Descend(pivot kind, iter func(item kind) bool) {
func (tr *BTreeG[T]) Descend(pivot T, iter func(item T) bool) {
if tr.rlock() {
defer tr.runlock()
}
@ -704,8 +693,8 @@ func (tr *bTree) Descend(pivot kind, iter func(item kind) bool) {
tr.descend(tr.root, pivot, nil, 0, iter)
}
func (tr *bTree) descend(n *node, pivot kind,
hint *bPathHint, depth int, iter func(item kind) bool,
func (tr *BTreeG[T]) descend(n *node[T], pivot T,
hint *PathHint, depth int, iter func(item T) bool,
) bool {
i, found := tr.find(n, pivot, hint, depth)
if !found {
@ -730,7 +719,7 @@ func (tr *bTree) descend(n *node, pivot kind,
}
// Load is for bulk loading pre-sorted items
func (tr *bTree) Load(item kind) (kind, bool) {
func (tr *BTreeG[T]) Load(item T) (T, bool) {
if tr.lock() {
defer tr.unlock()
}
@ -765,8 +754,8 @@ func (tr *bTree) Load(item kind) (kind, bool) {
}
// Min returns the minimum item in tree.
// Returns nil if the tree has no items.
func (tr *bTree) Min() (kind, bool) {
// Returns nil if the treex has no items.
func (tr *BTreeG[T]) Min() (T, bool) {
if tr.rlock() {
defer tr.runlock()
}
@ -784,7 +773,7 @@ func (tr *bTree) Min() (kind, bool) {
// Max returns the maximum item in tree.
// Returns nil if the tree has no items.
func (tr *bTree) Max() (kind, bool) {
func (tr *BTreeG[T]) Max() (T, bool) {
if tr.rlock() {
defer tr.runlock()
}
@ -802,7 +791,7 @@ func (tr *bTree) Max() (kind, bool) {
// PopMin removes the minimum item in tree and returns it.
// Returns nil if the tree has no items.
func (tr *bTree) PopMin() (kind, bool) {
func (tr *BTreeG[T]) PopMin() (T, bool) {
if tr.lock() {
defer tr.unlock()
}
@ -810,7 +799,7 @@ func (tr *bTree) PopMin() (kind, bool) {
return tr.empty, false
}
n := tr.cowLoad(&tr.root)
var item kind
var item T
for {
n.count-- // optimistically update counts
if n.leaf() {
@ -841,9 +830,9 @@ func (tr *bTree) PopMin() (kind, bool) {
return tr.deleteHint(item, nil)
}
// PopMax removes the minimum item in tree and returns it.
// PopMax removes the maximum item in tree and returns it.
// Returns nil if the tree has no items.
func (tr *bTree) PopMax() (kind, bool) {
func (tr *BTreeG[T]) PopMax() (T, bool) {
if tr.lock() {
defer tr.unlock()
}
@ -851,7 +840,7 @@ func (tr *bTree) PopMax() (kind, bool) {
return tr.empty, false
}
n := tr.cowLoad(&tr.root)
var item kind
var item T
for {
n.count-- // optimistically update counts
if n.leaf() {
@ -883,7 +872,7 @@ func (tr *bTree) PopMax() (kind, bool) {
// GetAt returns the value at index.
// Return nil if the tree is empty or the index is out of bounds.
func (tr *bTree) GetAt(index int) (kind, bool) {
func (tr *BTreeG[T]) GetAt(index int) (T, bool) {
if tr.rlock() {
defer tr.runlock()
}
@ -910,7 +899,7 @@ func (tr *bTree) GetAt(index int) (kind, bool) {
// DeleteAt deletes the item at index.
// Return nil if the tree is empty or the index is out of bounds.
func (tr *bTree) DeleteAt(index int) (kind, bool) {
func (tr *BTreeG[T]) DeleteAt(index int) (T, bool) {
if tr.lock() {
defer tr.unlock()
}
@ -919,7 +908,7 @@ func (tr *bTree) DeleteAt(index int) (kind, bool) {
}
var pathbuf [8]uint8 // track the path
path := pathbuf[:0]
var item kind
var item T
n := tr.cowLoad(&tr.root)
outer:
for {
@ -955,7 +944,7 @@ outer:
n = tr.cowLoad(&(*n.children)[i])
}
// revert the counts
var hint bPathHint
var hint PathHint
n = tr.root
for i := 0; i < len(path); i++ {
if i < len(hint.path) {
@ -972,7 +961,7 @@ outer:
// Height returns the height of the tree.
// Returns zero if tree has no items.
func (tr *bTree) Height() int {
func (tr *BTreeG[T]) Height() int {
if tr.rlock() {
defer tr.runlock()
}
@ -992,7 +981,7 @@ func (tr *bTree) Height() int {
// Walk iterates over all items in tree, in order.
// The items param will contain one or more items.
func (tr *bTree) Walk(iter func(item []kind) bool) {
func (tr *BTreeG[T]) Walk(iter func(item []T) bool) {
if tr.rlock() {
defer tr.runlock()
}
@ -1001,7 +990,7 @@ func (tr *bTree) Walk(iter func(item []kind) bool) {
}
}
func (n *node) walk(iter func(item []kind) bool) bool {
func (n *node[T]) walk(iter func(item []T) bool) bool {
if n.leaf() {
if !iter(n.items) {
return false
@ -1020,60 +1009,60 @@ func (n *node) walk(iter func(item []kind) bool) bool {
// Copy the tree. This is a copy-on-write operation and is very fast because
// it only performs a shadowed copy.
func (tr *bTree) Copy() *bTree {
func (tr *BTreeG[T]) Copy() *BTreeG[T] {
if tr.lock() {
defer tr.unlock()
}
tr.cow = new(cow)
tr2 := new(bTree)
tr.cow = atomic.AddUint64(&gcow, 1)
tr2 := new(BTreeG[T])
*tr2 = *tr
tr2.mu = new(sync.RWMutex)
tr2.cow = new(cow)
tr2.cow = atomic.AddUint64(&gcow, 1)
return tr2
}
func (tr *bTree) lock() bool {
func (tr *BTreeG[T]) lock() bool {
if tr.locks {
tr.mu.Lock()
}
return tr.locks
}
func (tr *bTree) unlock() {
func (tr *BTreeG[T]) unlock() {
tr.mu.Unlock()
}
func (tr *bTree) rlock() bool {
func (tr *BTreeG[T]) rlock() bool {
if tr.locks {
tr.mu.RLock()
}
return tr.locks
}
func (tr *bTree) runlock() {
func (tr *BTreeG[T]) runlock() {
tr.mu.RUnlock()
}
// Iter represents an iterator
type bIter struct {
tr *bTree
type GenericIter[T any] struct {
tr *BTreeG[T]
locked bool
seeked bool
atstart bool
atend bool
stack []iterStackItem
item kind
stack []genericIterStackItem[T]
item T
}
type iterStackItem struct {
n *node
type genericIterStackItem[T any] struct {
n *node[T]
i int
}
// Iter returns a read-only iterator.
// The Release method must be called finished with iterator.
func (tr *bTree) Iter() bIter {
var iter bIter
func (tr *BTreeG[T]) Iter() GenericIter[T] {
var iter GenericIter[T]
iter.tr = tr
iter.locked = tr.rlock()
return iter
@ -1081,7 +1070,7 @@ func (tr *bTree) Iter() bIter {
// Seek to item greater-or-equal-to key.
// Returns false if there was no item found.
func (iter *bIter) Seek(key kind) bool {
func (iter *GenericIter[T]) Seek(key T) bool {
if iter.tr == nil {
return false
}
@ -1093,16 +1082,14 @@ func (iter *bIter) Seek(key kind) bool {
n := iter.tr.root
for {
i, found := iter.tr.find(n, key, nil, 0)
iter.stack = append(iter.stack, iterStackItem{n, i})
iter.stack = append(iter.stack, genericIterStackItem[T]{n, i})
if found {
iter.item = n.items[i]
return true
}
if n.leaf() {
if i == len(n.items) {
iter.stack = iter.stack[:0]
return false
}
return true
iter.stack[len(iter.stack)-1].i--
return iter.Next()
}
n = (*n.children)[i]
}
@ -1110,7 +1097,7 @@ func (iter *bIter) Seek(key kind) bool {
// First moves iterator to first item in tree.
// Returns false if the tree is empty.
func (iter *bIter) First() bool {
func (iter *GenericIter[T]) First() bool {
if iter.tr == nil {
return false
}
@ -1123,7 +1110,7 @@ func (iter *bIter) First() bool {
}
n := iter.tr.root
for {
iter.stack = append(iter.stack, iterStackItem{n, 0})
iter.stack = append(iter.stack, genericIterStackItem[T]{n, 0})
if n.leaf() {
break
}
@ -1136,7 +1123,7 @@ func (iter *bIter) First() bool {
// Last moves iterator to last item in tree.
// Returns false if the tree is empty.
func (iter *bIter) Last() bool {
func (iter *GenericIter[T]) Last() bool {
if iter.tr == nil {
return false
}
@ -1147,7 +1134,7 @@ func (iter *bIter) Last() bool {
}
n := iter.tr.root
for {
iter.stack = append(iter.stack, iterStackItem{n, len(n.items)})
iter.stack = append(iter.stack, genericIterStackItem[T]{n, len(n.items)})
if n.leaf() {
iter.stack[len(iter.stack)-1].i--
break
@ -1159,9 +1146,8 @@ func (iter *bIter) Last() bool {
return true
}
// First moves iterator to first item in tree.
// Returns false if the tree is empty.
func (iter *bIter) Release() {
// Release the iterator.
func (iter *GenericIter[T]) Release() {
if iter.tr == nil {
return
}
@ -1176,7 +1162,7 @@ func (iter *bIter) Release() {
// Next moves iterator to the next item in iterator.
// Returns false if the tree is empty or the iterator is at the end of
// the tree.
func (iter *bIter) Next() bool {
func (iter *GenericIter[T]) Next() bool {
if iter.tr == nil {
return false
}
@ -1208,7 +1194,7 @@ func (iter *bIter) Next() bool {
} else {
n := (*s.n.children)[s.i]
for {
iter.stack = append(iter.stack, iterStackItem{n, 0})
iter.stack = append(iter.stack, genericIterStackItem[T]{n, 0})
if n.leaf() {
break
}
@ -1223,7 +1209,7 @@ func (iter *bIter) Next() bool {
// Prev moves iterator to the previous item in iterator.
// Returns false if the tree is empty or the iterator is at the beginning of
// the tree.
func (iter *bIter) Prev() bool {
func (iter *GenericIter[T]) Prev() bool {
if iter.tr == nil {
return false
}
@ -1256,7 +1242,7 @@ func (iter *bIter) Prev() bool {
} else {
n := (*s.n.children)[s.i]
for {
iter.stack = append(iter.stack, iterStackItem{n, len(n.items)})
iter.stack = append(iter.stack, genericIterStackItem[T]{n, len(n.items)})
if n.leaf() {
iter.stack[len(iter.stack)-1].i--
break
@ -1270,6 +1256,48 @@ func (iter *bIter) Prev() bool {
}
// Item returns the current iterator item.
func (iter *bIter) Item() kind {
func (iter *GenericIter[T]) Item() T {
return iter.item
}
// Items returns all the items in order.
func (tr *BTreeG[T]) Items() []T {
items := make([]T, 0, tr.Len())
if tr.root != nil {
items = tr.root.aitems(items)
}
return items
}
func (n *node[T]) aitems(items []T) []T {
if n.leaf() {
return append(items, n.items...)
}
for i := 0; i < len(n.items); i++ {
items = (*n.children)[i].aitems(items)
items = append(items, n.items[i])
}
return (*n.children)[len(*n.children)-1].aitems(items)
}
// Generic BTree
// Deprecated: use BTreeG
type Generic[T any] struct {
*BTreeG[T]
}
// NewGeneric returns a generic BTree
// Deprecated: use NewBTreeG
func NewGeneric[T any](less func(a, b T) bool) *Generic[T] {
return &Generic[T]{NewBTreeGOptions(less, Options{})}
}
// NewGenericOptions returns a generic BTree
// Deprecated: use NewBTreeGOptions
func NewGenericOptions[T any](less func(a, b T) bool, opts Options) *Generic[T] {
return &Generic[T]{NewBTreeGOptions(less, opts)}
}
func (tr *Generic[T]) Copy() *Generic[T] {
return &Generic[T]{tr.BTreeG.Copy()}
}

1056
vendor/github.com/tidwall/btree/map.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

168
vendor/github.com/tidwall/btree/set.go generated vendored Normal file
View File

@ -0,0 +1,168 @@
package btree
type Set[K ordered] struct {
base Map[K, struct{}]
}
// Copy
func (tr *Set[K]) Copy() *Set[K] {
tr2 := new(Set[K])
tr2.base = *tr.base.Copy()
return tr2
}
// Insert an item
func (tr *Set[K]) Insert(key K) {
tr.base.Set(key, struct{}{})
}
func (tr *Set[K]) Scan(iter func(key K) bool) {
tr.base.Scan(func(key K, value struct{}) bool {
return iter(key)
})
}
// Get a value for key
func (tr *Set[K]) Contains(key K) bool {
_, ok := tr.base.Get(key)
return ok
}
// Len returns the number of items in the tree
func (tr *Set[K]) Len() int {
return tr.base.Len()
}
// Delete an item
func (tr *Set[K]) Delete(key K) {
tr.base.Delete(key)
}
// Ascend the tree within the range [pivot, last]
// Pass nil for pivot to scan all item in ascending order
// Return false to stop iterating
func (tr *Set[K]) Ascend(pivot K, iter func(key K) bool) {
tr.base.Ascend(pivot, func(key K, value struct{}) bool {
return iter(key)
})
}
func (tr *Set[K]) Reverse(iter func(key K) bool) {
tr.base.Reverse(func(key K, value struct{}) bool {
return iter(key)
})
}
// Descend the tree within the range [pivot, first]
// Pass nil for pivot to scan all item in descending order
// Return false to stop iterating
func (tr *Set[K]) Descend(pivot K, iter func(key K) bool) {
tr.base.Descend(pivot, func(key K, value struct{}) bool {
return iter(key)
})
}
// Load is for bulk loading pre-sorted items
func (tr *Set[K]) Load(key K) {
tr.base.Load(key, struct{}{})
}
// Min returns the minimum item in tree.
// Returns nil if the treex has no items.
func (tr *Set[K]) Min() (K, bool) {
key, _, ok := tr.base.Min()
return key, ok
}
// Max returns the maximum item in tree.
// Returns nil if the tree has no items.
func (tr *Set[K]) Max() (K, bool) {
key, _, ok := tr.base.Max()
return key, ok
}
// PopMin removes the minimum item in tree and returns it.
// Returns nil if the tree has no items.
func (tr *Set[K]) PopMin() (K, bool) {
key, _, ok := tr.base.PopMin()
return key, ok
}
// PopMax removes the maximum item in tree and returns it.
// Returns nil if the tree has no items.
func (tr *Set[K]) PopMax() (K, bool) {
key, _, ok := tr.base.PopMax()
return key, ok
}
// GetAt returns the value at index.
// Return nil if the tree is empty or the index is out of bounds.
func (tr *Set[K]) GetAt(index int) (K, bool) {
key, _, ok := tr.base.GetAt(index)
return key, ok
}
// DeleteAt deletes the item at index.
// Return nil if the tree is empty or the index is out of bounds.
func (tr *Set[K]) DeleteAt(index int) (K, bool) {
key, _, ok := tr.base.DeleteAt(index)
return key, ok
}
// Height returns the height of the tree.
// Returns zero if tree has no items.
func (tr *Set[K]) Height() int {
return tr.base.Height()
}
// SetIter represents an iterator for btree.Set
type SetIter[K ordered] struct {
base MapIter[K, struct{}]
}
// Iter returns a read-only iterator.
func (tr *Set[K]) Iter() SetIter[K] {
return SetIter[K]{tr.base.Iter()}
}
// Seek to item greater-or-equal-to key.
// Returns false if there was no item found.
func (iter *SetIter[K]) Seek(key K) bool {
return iter.base.Seek(key)
}
// First moves iterator to first item in tree.
// Returns false if the tree is empty.
func (iter *SetIter[K]) First() bool {
return iter.base.First()
}
// Last moves iterator to last item in tree.
// Returns false if the tree is empty.
func (iter *SetIter[K]) Last() bool {
return iter.base.Last()
}
// Next moves iterator to the next item in iterator.
// Returns false if the tree is empty or the iterator is at the end of
// the tree.
func (iter *SetIter[K]) Next() bool {
return iter.base.Next()
}
// Prev moves iterator to the previous item in iterator.
// Returns false if the tree is empty or the iterator is at the beginning of
// the tree.
func (iter *SetIter[K]) Prev() bool {
return iter.base.Prev()
}
// Key returns the current iterator item key.
func (iter *SetIter[K]) Key() K {
return iter.base.Key()
}
// Keys returns all the keys in order.
func (tr *Set[K]) Keys() []K {
return tr.base.Keys()
}

View File

@ -7,6 +7,7 @@ package buntdb
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"sort"
@ -749,13 +750,13 @@ func (db *DB) Shrink() error {
if err := db.file.Close(); err != nil {
return err
}
// Any failures below here is really bad. So just panic.
// Any failures below here are really bad. So just panic.
if err := os.Rename(tmpname, fname); err != nil {
panic(err)
panicErr(err)
}
db.file, err = os.OpenFile(fname, os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
panic(err)
panicErr(err)
}
pos, err := db.file.Seek(0, 2)
if err != nil {
@ -766,6 +767,10 @@ func (db *DB) Shrink() error {
}()
}
func panicErr(err error) error {
panic(fmt.Errorf("buntdb: %w", err))
}
// readLoad reads from the reader and loads commands into the database.
// modTime is the modified time of the reader, should be no greater than
// the current time.Now().
@ -1209,10 +1214,10 @@ func (tx *Tx) Commit() error {
// should be killed to avoid corrupting the file.
pos, err := tx.db.file.Seek(-int64(n), 1)
if err != nil {
panic(err)
panicErr(err)
}
if err := tx.db.file.Truncate(pos); err != nil {
panic(err)
panicErr(err)
}
}
tx.rollbackInner()

View File

@ -16,6 +16,10 @@ It has features such as [one line retrieval](#get-a-value), [dot notation paths]
Also check out [SJSON](https://github.com/tidwall/sjson) for modifying json, and the [JJ](https://github.com/tidwall/jj) command line tool.
This README is a quick overview of how to use GJSON, for more information check out [GJSON Syntax](SYNTAX.md).
GJSON is also available for [Python](https://github.com/volans-/gjson-py) and [Rust](https://github.com/tidwall/gjson.rs)
Getting Started
===============
@ -204,6 +208,9 @@ There are currently the following built-in modifiers:
- `@join`: Joins multiple objects into a single object.
- `@keys`: Returns an array of keys for an object.
- `@values`: Returns an array of values for an object.
- `@tostr`: Converts json to a string. Wraps a json string.
- `@fromstr`: Converts a string from json. Unwraps a json string.
- `@group`: Groups arrays of objects. See [e4fc67c](https://github.com/tidwall/gjson/commit/e4fc67c92aeebf2089fabc7872f010e340d105db).
### Modifier arguments

View File

@ -22,7 +22,7 @@ Use the [GJSON Playground](https://gjson.dev) to experiment with the syntax onli
A GJSON Path is intended to be easily expressed as a series of components seperated by a `.` character.
Along with `.` character, there are a few more that have special meaning, including `|`, `#`, `@`, `\`, `*`, and `?`.
Along with `.` character, there are a few more that have special meaning, including `|`, `#`, `@`, `\`, `*`, `!`, and `?`.
## Example
@ -77,7 +77,7 @@ Special purpose characters, such as `.`, `*`, and `?` can be escaped with `\`.
fav\.movie "Deer Hunter"
```
You'll also need to make sure that the `\` character is correctly escaped when hardcoding a path in you source code.
You'll also need to make sure that the `\` character is correctly escaped when hardcoding a path in your source code.
```go
// Go
@ -238,6 +238,9 @@ There are currently the following built-in modifiers:
- `@join`: Joins multiple objects into a single object.
- `@keys`: Returns an array of keys for an object.
- `@values`: Returns an array of values for an object.
- `@tostr`: Converts json to a string. Wraps a json string.
- `@fromstr`: Converts a string from json. Unwraps a json string.
- `@group`: Groups arrays of objects. See [e4fc67c](https://github.com/tidwall/gjson/commit/e4fc67c92aeebf2089fabc7872f010e340d105db).
#### Modifier arguments

View File

@ -2,7 +2,6 @@
package gjson
import (
"encoding/json"
"strconv"
"strings"
"time"
@ -214,6 +213,11 @@ func (t Result) IsArray() bool {
return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '['
}
// IsBool returns true if the result value is a JSON boolean.
func (t Result) IsBool() bool {
return t.Type == True || t.Type == False
}
// ForEach iterates through values.
// If the result represents a non-existent value, then no values will be
// iterated. If the result is an Object, the iterator will pass the key and
@ -771,7 +775,7 @@ func parseArrayPath(path string) (r arrayPathResult) {
}
if path[i] == '.' {
r.part = path[:i]
if !r.arrch && i < len(path)-1 && isDotPiperChar(path[i+1]) {
if !r.arrch && i < len(path)-1 && isDotPiperChar(path[i+1:]) {
r.pipe = path[i+1:]
r.piped = true
} else {
@ -932,8 +936,23 @@ right:
}
// peek at the next byte and see if it's a '@', '[', or '{'.
func isDotPiperChar(c byte) bool {
return !DisableModifiers && (c == '@' || c == '[' || c == '{')
func isDotPiperChar(s string) bool {
if DisableModifiers {
return false
}
c := s[0]
if c == '@' {
// check that the next component is *not* a modifier.
i := 1
for ; i < len(s); i++ {
if s[i] == '.' || s[i] == '|' || s[i] == ':' {
break
}
}
_, ok := modifiers[s[1:i]]
return ok
}
return c == '[' || c == '{'
}
type objectPathResult struct {
@ -955,7 +974,7 @@ func parseObjectPath(path string) (r objectPathResult) {
}
if path[i] == '.' {
r.part = path[:i]
if i < len(path)-1 && isDotPiperChar(path[i+1]) {
if i < len(path)-1 && isDotPiperChar(path[i+1:]) {
r.pipe = path[i+1:]
r.piped = true
} else {
@ -985,7 +1004,7 @@ func parseObjectPath(path string) (r objectPathResult) {
continue
} else if path[i] == '.' {
r.part = string(epart)
if i < len(path)-1 && isDotPiperChar(path[i+1]) {
if i < len(path)-1 && isDotPiperChar(path[i+1:]) {
r.pipe = path[i+1:]
r.piped = true
} else {
@ -1819,17 +1838,64 @@ func isSimpleName(component string) bool {
return true
}
func appendJSONString(dst []byte, s string) []byte {
var hexchars = [...]byte{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f',
}
func appendHex16(dst []byte, x uint16) []byte {
return append(dst,
hexchars[x>>12&0xF], hexchars[x>>8&0xF],
hexchars[x>>4&0xF], hexchars[x>>0&0xF],
)
}
// AppendJSONString is a convenience function that converts the provided string
// to a valid JSON string and appends it to dst.
func AppendJSONString(dst []byte, s string) []byte {
dst = append(dst, make([]byte, len(s)+2)...)
dst = append(dst[:len(dst)-len(s)-2], '"')
for i := 0; i < len(s); i++ {
if s[i] < ' ' || s[i] == '\\' || s[i] == '"' || s[i] > 126 {
d, _ := json.Marshal(s)
return append(dst, string(d)...)
if s[i] < ' ' {
dst = append(dst, '\\')
switch s[i] {
case '\n':
dst = append(dst, 'n')
case '\r':
dst = append(dst, 'r')
case '\t':
dst = append(dst, 't')
default:
dst = append(dst, 'u')
dst = appendHex16(dst, uint16(s[i]))
}
} else if s[i] == '>' || s[i] == '<' || s[i] == '&' {
dst = append(dst, '\\', 'u')
dst = appendHex16(dst, uint16(s[i]))
} else if s[i] == '\\' {
dst = append(dst, '\\', '\\')
} else if s[i] == '"' {
dst = append(dst, '\\', '"')
} else if s[i] > 127 {
// read utf8 character
r, n := utf8.DecodeRuneInString(s[i:])
if n == 0 {
break
}
if r == utf8.RuneError && n == 1 {
dst = append(dst, `\ufffd`...)
} else if r == '\u2028' || r == '\u2029' {
dst = append(dst, `\u202`...)
dst = append(dst, hexchars[r&0xF])
} else {
dst = append(dst, s[i:i+n]...)
}
i = i + n - 1
} else {
dst = append(dst, s[i])
}
}
dst = append(dst, '"')
dst = append(dst, s...)
dst = append(dst, '"')
return dst
return append(dst, '"')
}
type parseContext struct {
@ -1919,14 +1985,14 @@ func Get(json, path string) Result {
if sub.name[0] == '"' && Valid(sub.name) {
b = append(b, sub.name...)
} else {
b = appendJSONString(b, sub.name)
b = AppendJSONString(b, sub.name)
}
} else {
last := nameOfLast(sub.path)
if isSimpleName(last) {
b = appendJSONString(b, last)
b = AppendJSONString(b, last)
} else {
b = appendJSONString(b, "_")
b = AppendJSONString(b, "_")
}
}
b = append(b, ':')
@ -2669,6 +2735,9 @@ var modifiers = map[string]func(json, arg string) string{
"valid": modValid,
"keys": modKeys,
"values": modValues,
"tostr": modToStr,
"fromstr": modFromStr,
"group": modGroup,
}
// AddModifier binds a custom modifier command to the GJSON syntax.
@ -2954,6 +3023,56 @@ func modValid(json, arg string) string {
return json
}
// @fromstr converts a string to json
// "{\"id\":1023,\"name\":\"alert\"}" -> {"id":1023,"name":"alert"}
func modFromStr(json, arg string) string {
if !Valid(json) {
return ""
}
return Parse(json).String()
}
// @tostr converts a string to json
// {"id":1023,"name":"alert"} -> "{\"id\":1023,\"name\":\"alert\"}"
func modToStr(str, arg string) string {
return string(AppendJSONString(nil, str))
}
func modGroup(json, arg string) string {
res := Parse(json)
if !res.IsObject() {
return ""
}
var all [][]byte
res.ForEach(func(key, value Result) bool {
if !value.IsArray() {
return true
}
var idx int
value.ForEach(func(_, value Result) bool {
if idx == len(all) {
all = append(all, []byte{})
}
all[idx] = append(all[idx], ("," + key.Raw + ":" + value.Raw)...)
idx++
return true
})
return true
})
var data []byte
data = append(data, '[')
for i, item := range all {
if i > 0 {
data = append(data, ',')
}
data = append(data, '{')
data = append(data, item[1:]...)
data = append(data, '}')
}
data = append(data, ']')
return string(data)
}
// stringHeader instead of reflect.StringHeader
type stringHeader struct {
data unsafe.Pointer
@ -3088,6 +3207,20 @@ func revSquash(json string) string {
return json
}
// Paths returns the original GJSON paths for a Result where the Result came
// from a simple query path that returns an array, like:
//
// gjson.Get(json, "friends.#.first")
//
// The returned value will be in the form of a JSON array:
//
// ["friends.0.first","friends.1.first","friends.2.first"]
//
// The param 'json' must be the original JSON used when calling Get.
//
// Returns an empty string if the paths cannot be determined, which can happen
// when the Result came from a path that contained a multipath, modifier,
// or a nested query.
func (t Result) Paths(json string) []string {
if t.Indexes == nil {
return nil
@ -3103,8 +3236,20 @@ func (t Result) Paths(json string) []string {
return paths
}
// Path returns the original GJSON path for Result.
// The json param must be the original JSON used when calling Get.
// Path returns the original GJSON path for a Result where the Result came
// from a simple path that returns a single value, like:
//
// gjson.Get(json, "friends.#(last=Murphy)")
//
// The returned value will be in the form of a JSON string:
//
// "friends.0"
//
// The param 'json' must be the original JSON used when calling Get.
//
// Returns an empty string if the paths cannot be determined, which can happen
// when the Result came from a path that contained a multipath, modifier,
// or a nested query.
func (t Result) Path(json string) string {
var path []byte
var comps []string // raw components

11
vendor/modules.txt vendored
View File

@ -45,14 +45,13 @@ github.com/okzk/sdnotify
## explicit
# github.com/stretchr/testify v1.4.0
## explicit
# github.com/tidwall/btree v1.1.0
## explicit; go 1.16
# github.com/tidwall/btree v1.4.2
## explicit; go 1.18
github.com/tidwall/btree
github.com/tidwall/btree/internal
# github.com/tidwall/buntdb v1.2.9
## explicit; go 1.16
# github.com/tidwall/buntdb v1.2.10
## explicit; go 1.18
github.com/tidwall/buntdb
# github.com/tidwall/gjson v1.12.1
# github.com/tidwall/gjson v1.14.3
## explicit; go 1.12
github.com/tidwall/gjson
# github.com/tidwall/grect v0.1.4