diff --git a/go.mod b/go.mod index 41199d05..17158af2 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 75714fa3..46acb69b 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/vendor/github.com/tidwall/btree/README.md b/vendor/github.com/tidwall/btree/README.md index 7d00f5a3..167dc81a 100644 --- a/vendor/github.com/tidwall/btree/README.md +++ b/vendor/github.com/tidwall/btree/README.md @@ -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 diff --git a/vendor/github.com/tidwall/btree/btree.go b/vendor/github.com/tidwall/btree/btree.go index c8cdac29..fa1eb720 100644 --- a/vendor/github.com/tidwall/btree/btree.go +++ b/vendor/github.com/tidwall/btree/btree.go @@ -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() } diff --git a/vendor/github.com/tidwall/btree/internal/btree.go b/vendor/github.com/tidwall/btree/generic.go similarity index 71% rename from vendor/github.com/tidwall/btree/internal/btree.go rename to vendor/github.com/tidwall/btree/generic.go index bde59ec5..c5af9429 100644 --- a/vendor/github.com/tidwall/btree/internal/btree.go +++ b/vendor/github.com/tidwall/btree/generic.go @@ -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()} +} diff --git a/vendor/github.com/tidwall/btree/map.go b/vendor/github.com/tidwall/btree/map.go new file mode 100644 index 00000000..cafc285c --- /dev/null +++ b/vendor/github.com/tidwall/btree/map.go @@ -0,0 +1,1056 @@ +// 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. +package btree + +import "sync/atomic" + +type ordered interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | + ~float32 | ~float64 | ~string +} + +type mapPair[K ordered, V any] struct { + // The `value` field should be before the `key` field because doing so + // allows for the Go compiler to optimize away the `value` field when + // it's a `struct{}`, which is the case for `btree.Set`. + value V + key K +} + +type Map[K ordered, V any] struct { + cow uint64 + root *mapNode[K, V] + count int + empty mapPair[K, V] +} + +type mapNode[K ordered, V any] struct { + cow uint64 + count int + items []mapPair[K, V] + children *[]*mapNode[K, V] +} + +// This operation should not be inlined because it's expensive and rarely +// called outside of heavy copy-on-write situations. Marking it "noinline" +// allows for the parent cowLoad to be inlined. +// go:noinline +func (tr *Map[K, V]) copy(n *mapNode[K, V]) *mapNode[K, V] { + n2 := new(mapNode[K, V]) + n2.cow = tr.cow + n2.count = n.count + n2.items = make([]mapPair[K, V], len(n.items), cap(n.items)) + copy(n2.items, n.items) + if !n.leaf() { + n2.children = new([]*mapNode[K, V]) + *n2.children = make([]*mapNode[K, V], 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 *Map[K, V]) cowLoad(cn **mapNode[K, V]) *mapNode[K, V] { + if (*cn).cow != tr.cow { + *cn = tr.copy(*cn) + } + return *cn +} + +func (tr *Map[K, V]) Copy() *Map[K, V] { + tr2 := new(Map[K, V]) + *tr2 = *tr + tr2.cow = atomic.AddUint64(&gcow, 1) + tr.cow = atomic.AddUint64(&gcow, 1) + return tr2 +} + +func (tr *Map[K, V]) newNode(leaf bool) *mapNode[K, V] { + n := new(mapNode[K, V]) + n.cow = tr.cow + if !leaf { + n.children = new([]*mapNode[K, V]) + } + return n +} + +// leaf returns true if the node is a leaf. +func (n *mapNode[K, V]) leaf() bool { + return n.children == nil +} + +func (tr *Map[K, V]) bsearch(n *mapNode[K, V], key K) (index int, found bool) { + low, high := 0, len(n.items) + for low < high { + h := int(uint(low+high) >> 1) + if key >= n.items[h].key { + low = h + 1 + } else { + high = h + } + } + if low > 0 && n.items[low-1].key >= key { + return low - 1, true + } + return low, false +} + +// Set or replace a value for a key +func (tr *Map[K, V]) Set(key K, value V) (V, bool) { + item := mapPair[K, V]{key: key, value: value} + if tr.root == nil { + tr.root = tr.newNode(true) + tr.root.items = append([]mapPair[K, V]{}, item) + tr.root.count = 1 + tr.count = 1 + return tr.empty.value, false + } + prev, replaced, split := tr.nodeSet(&tr.root, item) + if split { + left := tr.root + right, median := tr.nodeSplit(left) + tr.root = tr.newNode(false) + *tr.root.children = make([]*mapNode[K, V], 0, maxItems+1) + *tr.root.children = append([]*mapNode[K, V]{}, left, right) + tr.root.items = append([]mapPair[K, V]{}, median) + tr.root.updateCount() + return tr.Set(item.key, item.value) + } + if replaced { + return prev, true + } + tr.count++ + return tr.empty.value, false +} + +func (tr *Map[K, V]) nodeSplit(n *mapNode[K, V], +) (right *mapNode[K, V], median mapPair[K, V]) { + i := maxItems / 2 + median = n.items[i] + + const sliceItems = true + + // right node + right = tr.newNode(n.leaf()) + if sliceItems { + right.items = n.items[i+1:] + if !n.leaf() { + *right.children = (*n.children)[i+1:] + } + } else { + right.items = make([]mapPair[K, V], len(n.items[i+1:]), maxItems/2) + copy(right.items, n.items[i+1:]) + if !n.leaf() { + *right.children = make([]*mapNode[K, V], + len((*n.children)[i+1:]), maxItems+1) + copy(*right.children, (*n.children)[i+1:]) + } + } + right.updateCount() + + // 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 *mapNode[K, V]) updateCount() { + n.count = len(n.items) + if !n.leaf() { + for i := 0; i < len(*n.children); i++ { + n.count += (*n.children)[i].count + } + } +} + +func (tr *Map[K, V]) nodeSet(pn **mapNode[K, V], item mapPair[K, V], +) (prev V, replaced bool, split bool) { + n := tr.cowLoad(pn) + i, found := tr.bsearch(n, item.key) + if found { + prev = n.items[i].value + n.items[i].value = item.value + return prev, true, false + } + if n.leaf() { + if len(n.items) == maxItems { + return tr.empty.value, false, true + } + n.items = append(n.items, tr.empty) + copy(n.items[i+1:], n.items[i:]) + n.items[i] = item + n.count++ + return tr.empty.value, false, false + } + prev, replaced, split = tr.nodeSet(&(*n.children)[i], item) + if split { + if len(n.items) == maxItems { + return tr.empty.value, false, true + } + right, median := tr.nodeSplit((*n.children)[i]) + *n.children = append(*n.children, nil) + copy((*n.children)[i+1:], (*n.children)[i:]) + (*n.children)[i+1] = right + n.items = append(n.items, tr.empty) + copy(n.items[i+1:], n.items[i:]) + n.items[i] = median + return tr.nodeSet(&n, item) + } + if !replaced { + n.count++ + } + return prev, replaced, false +} + +func (tr *Map[K, V]) Scan(iter func(key K, value V) bool) { + if tr.root == nil { + return + } + tr.root.scan(iter) +} + +func (n *mapNode[K, V]) scan(iter func(key K, value V) bool) bool { + if n.leaf() { + for i := 0; i < len(n.items); i++ { + if !iter(n.items[i].key, n.items[i].value) { + return false + } + } + return true + } + for i := 0; i < len(n.items); i++ { + if !(*n.children)[i].scan(iter) { + return false + } + if !iter(n.items[i].key, n.items[i].value) { + return false + } + } + return (*n.children)[len(*n.children)-1].scan(iter) +} + +// Get a value for key +func (tr *Map[K, V]) Get(key K) (V, bool) { + if tr.root == nil { + return tr.empty.value, false + } + n := tr.root + for { + i, found := tr.bsearch(n, key) + if found { + return n.items[i].value, true + } + if n.leaf() { + return tr.empty.value, false + } + n = (*n.children)[i] + } +} + +// Len returns the number of items in the tree +func (tr *Map[K, V]) Len() int { + return tr.count +} + +// Delete a value for a key and returns the deleted value. +// Returns false if there was no value by that key found. +func (tr *Map[K, V]) Delete(key K) (V, bool) { + if tr.root == nil { + return tr.empty.value, false + } + prev, deleted := tr.delete(&tr.root, false, key) + if !deleted { + return tr.empty.value, false + } + if len(tr.root.items) == 0 && !tr.root.leaf() { + tr.root = (*tr.root.children)[0] + } + tr.count-- + if tr.count == 0 { + tr.root = nil + } + return prev.value, true +} + +func (tr *Map[K, V]) delete(pn **mapNode[K, V], max bool, key K, +) (mapPair[K, V], bool) { + n := tr.cowLoad(pn) + var i int + var found bool + if max { + i, found = len(n.items)-1, true + } else { + i, found = tr.bsearch(n, key) + } + if n.leaf() { + if found { + // found the items at the leaf, remove it and return. + prev := n.items[i] + copy(n.items[i:], n.items[i+1:]) + n.items[len(n.items)-1] = tr.empty + n.items = n.items[:len(n.items)-1] + n.count-- + return prev, true + } + return tr.empty, false + } + + var prev mapPair[K, V] + var deleted bool + if found { + if max { + i++ + prev, deleted = tr.delete(&(*n.children)[i], true, tr.empty.key) + } else { + prev = n.items[i] + maxItem, _ := tr.delete(&(*n.children)[i], true, tr.empty.key) + deleted = true + n.items[i] = maxItem + } + } else { + prev, deleted = tr.delete(&(*n.children)[i], max, key) + } + if !deleted { + return tr.empty, false + } + n.count-- + if len((*n.children)[i].items) < minItems { + 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 *Map[K, V]) nodeRebalance(n *mapNode[K, V], i int) { + if i == len(n.items) { + i-- + } + + // ensure copy-on-write + left := tr.cowLoad(&(*n.children)[i]) + right := tr.cowLoad(&(*n.children)[i+1]) + + if len(left.items)+len(right.items) < maxItems { + // Merges the left and right children nodes together as a single node + // that includes (left,item,right), and places the contents into the + // existing left node. Delete the right node altogether and move the + // following items and child nodes to the left by one slot. + + // merge (left,item,right) + left.items = append(left.items, n.items[i]) + left.items = append(left.items, right.items...) + if !left.leaf() { + *left.children = append(*left.children, *right.children...) + } + left.count += right.count + 1 + + // move the items over one slot + copy(n.items[i:], n.items[i+1:]) + n.items[len(n.items)-1] = tr.empty + n.items = n.items[:len(n.items)-1] + + // move the children over one slot + copy((*n.children)[i+1:], (*n.children)[i+2:]) + (*n.children)[len(*n.children)-1] = nil + (*n.children) = (*n.children)[:len(*n.children)-1] + } else if len(left.items) > len(right.items) { + // move left -> right over one slot + + // Move the item of the parent node at index into the right-node first + // slot, and move the left-node last item into the previously moved + // parent item slot. + right.items = append(right.items, tr.empty) + copy(right.items[1:], right.items) + right.items[0] = n.items[i] + right.count++ + n.items[i] = left.items[len(left.items)-1] + left.items[len(left.items)-1] = tr.empty + left.items = left.items[:len(left.items)-1] + left.count-- + + if !left.leaf() { + // move the left-node last child into the right-node first slot + *right.children = append(*right.children, nil) + copy((*right.children)[1:], *right.children) + (*right.children)[0] = (*left.children)[len(*left.children)-1] + (*left.children)[len(*left.children)-1] = nil + (*left.children) = (*left.children)[:len(*left.children)-1] + left.count -= (*right.children)[0].count + right.count += (*right.children)[0].count + } + } else { + // move left <- right over one slot + + // Same as above but the other direction + left.items = append(left.items, n.items[i]) + left.count++ + n.items[i] = right.items[0] + copy(right.items, right.items[1:]) + right.items[len(right.items)-1] = tr.empty + right.items = right.items[:len(right.items)-1] + right.count-- + + if !left.leaf() { + *left.children = append(*left.children, (*right.children)[0]) + copy(*right.children, (*right.children)[1:]) + (*right.children)[len(*right.children)-1] = nil + *right.children = (*right.children)[:len(*right.children)-1] + left.count += (*left.children)[len(*left.children)-1].count + right.count -= (*left.children)[len(*left.children)-1].count + } + } +} + +// 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 *Map[K, V]) Ascend(pivot K, iter func(key K, value V) bool) { + if tr.root == nil { + return + } + tr.ascend(tr.root, pivot, iter) +} + +// The return value of this function determines whether we should keep iterating +// upon this functions return. +func (tr *Map[K, V]) ascend(n *mapNode[K, V], pivot K, + iter func(key K, value V) bool, +) bool { + i, found := tr.bsearch(n, pivot) + if !found { + if !n.leaf() { + if !tr.ascend((*n.children)[i], pivot, iter) { + return false + } + } + } + // We are either in the case that + // - node is found, we should iterate through it starting at `i`, + // the index it was located at. + // - node is not found, and TODO: fill in. + for ; i < len(n.items); i++ { + if !iter(n.items[i].key, n.items[i].value) { + return false + } + if !n.leaf() { + if !(*n.children)[i+1].scan(iter) { + return false + } + } + } + return true +} + +func (tr *Map[K, V]) Reverse(iter func(key K, value V) bool) { + if tr.root == nil { + return + } + tr.root.reverse(iter) +} + +func (n *mapNode[K, V]) reverse(iter func(key K, value V) bool) bool { + if n.leaf() { + for i := len(n.items) - 1; i >= 0; i-- { + if !iter(n.items[i].key, n.items[i].value) { + return false + } + } + return true + } + if !(*n.children)[len(*n.children)-1].reverse(iter) { + return false + } + for i := len(n.items) - 1; i >= 0; i-- { + if !iter(n.items[i].key, n.items[i].value) { + return false + } + if !(*n.children)[i].reverse(iter) { + return false + } + } + return true +} + +// 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 *Map[K, V]) Descend(pivot K, iter func(key K, value V) bool) { + if tr.root == nil { + return + } + tr.descend(tr.root, pivot, iter) +} + +func (tr *Map[K, V]) descend(n *mapNode[K, V], pivot K, + iter func(key K, value V) bool, +) bool { + i, found := tr.bsearch(n, pivot) + if !found { + if !n.leaf() { + if !tr.descend((*n.children)[i], pivot, iter) { + return false + } + } + i-- + } + for ; i >= 0; i-- { + if !iter(n.items[i].key, n.items[i].value) { + return false + } + if !n.leaf() { + if !(*n.children)[i].reverse(iter) { + return false + } + } + } + return true +} + +// Load is for bulk loading pre-sorted items +func (tr *Map[K, V]) Load(key K, value V) (V, bool) { + item := mapPair[K, V]{key: key, value: value} + if tr.root == nil { + return tr.Set(item.key, item.value) + } + n := tr.cowLoad(&tr.root) + for { + n.count++ // optimistically update counts + if n.leaf() { + if len(n.items) < maxItems { + if n.items[len(n.items)-1].key < item.key { + n.items = append(n.items, item) + tr.count++ + return tr.empty.value, false + } + } + break + } + n = tr.cowLoad(&(*n.children)[len(*n.children)-1]) + } + // revert the counts + n = tr.root + for { + n.count-- + if n.leaf() { + break + } + n = (*n.children)[len(*n.children)-1] + } + return tr.Set(item.key, item.value) +} + +// Min returns the minimum item in tree. +// Returns nil if the treex has no items. +func (tr *Map[K, V]) Min() (K, V, bool) { + if tr.root == nil { + return tr.empty.key, tr.empty.value, false + } + n := tr.root + for { + if n.leaf() { + item := n.items[0] + return item.key, item.value, true + } + n = (*n.children)[0] + } +} + +// Max returns the maximum item in tree. +// Returns nil if the tree has no items. +func (tr *Map[K, V]) Max() (K, V, bool) { + if tr.root == nil { + return tr.empty.key, tr.empty.value, false + } + n := tr.root + for { + if n.leaf() { + item := n.items[len(n.items)-1] + return item.key, item.value, true + } + n = (*n.children)[len(*n.children)-1] + } +} + +// PopMin removes the minimum item in tree and returns it. +// Returns nil if the tree has no items. +func (tr *Map[K, V]) PopMin() (K, V, bool) { + if tr.root == nil { + return tr.empty.key, tr.empty.value, false + } + n := tr.cowLoad(&tr.root) + var item mapPair[K, V] + for { + n.count-- // optimistically update counts + if n.leaf() { + item = n.items[0] + if len(n.items) == minItems { + break + } + copy(n.items[:], n.items[1:]) + n.items[len(n.items)-1] = tr.empty + n.items = n.items[:len(n.items)-1] + tr.count-- + if tr.count == 0 { + tr.root = nil + } + return item.key, item.value, true + } + n = tr.cowLoad(&(*n.children)[0]) + } + // revert the counts + n = tr.root + for { + n.count++ + if n.leaf() { + break + } + n = (*n.children)[0] + } + value, deleted := tr.Delete(item.key) + if deleted { + return item.key, value, true + } + return tr.empty.key, tr.empty.value, false +} + +// PopMax removes the maximum item in tree and returns it. +// Returns nil if the tree has no items. +func (tr *Map[K, V]) PopMax() (K, V, bool) { + if tr.root == nil { + return tr.empty.key, tr.empty.value, false + } + n := tr.cowLoad(&tr.root) + var item mapPair[K, V] + for { + n.count-- // optimistically update counts + if n.leaf() { + item = n.items[len(n.items)-1] + if len(n.items) == minItems { + break + } + n.items[len(n.items)-1] = tr.empty + n.items = n.items[:len(n.items)-1] + tr.count-- + if tr.count == 0 { + tr.root = nil + } + return item.key, item.value, true + } + n = tr.cowLoad(&(*n.children)[len(*n.children)-1]) + } + // revert the counts + n = tr.root + for { + n.count++ + if n.leaf() { + break + } + n = (*n.children)[len(*n.children)-1] + } + value, deleted := tr.Delete(item.key) + if deleted { + return item.key, value, true + } + return tr.empty.key, tr.empty.value, false +} + +// GetAt returns the value at index. +// Return nil if the tree is empty or the index is out of bounds. +func (tr *Map[K, V]) GetAt(index int) (K, V, bool) { + if tr.root == nil || index < 0 || index >= tr.count { + return tr.empty.key, tr.empty.value, false + } + n := tr.root + for { + if n.leaf() { + return n.items[index].key, n.items[index].value, true + } + i := 0 + for ; i < len(n.items); i++ { + if index < (*n.children)[i].count { + break + } else if index == (*n.children)[i].count { + return n.items[i].key, n.items[i].value, true + } + index -= (*n.children)[i].count + 1 + } + n = (*n.children)[i] + } +} + +// DeleteAt deletes the item at index. +// Return nil if the tree is empty or the index is out of bounds. +func (tr *Map[K, V]) DeleteAt(index int) (K, V, bool) { + if tr.root == nil || index < 0 || index >= tr.count { + return tr.empty.key, tr.empty.value, false + } + var pathbuf [8]uint8 // track the path + path := pathbuf[:0] + var item mapPair[K, V] + n := tr.cowLoad(&tr.root) +outer: + for { + n.count-- // optimistically update counts + if n.leaf() { + // the index is the item position + item = n.items[index] + if len(n.items) == minItems { + path = append(path, uint8(index)) + break outer + } + copy(n.items[index:], n.items[index+1:]) + n.items[len(n.items)-1] = tr.empty + n.items = n.items[:len(n.items)-1] + tr.count-- + if tr.count == 0 { + tr.root = nil + } + return item.key, item.value, true + } + i := 0 + for ; i < len(n.items); i++ { + if index < (*n.children)[i].count { + break + } else if index == (*n.children)[i].count { + item = n.items[i] + path = append(path, uint8(i)) + break outer + } + index -= (*n.children)[i].count + 1 + } + path = append(path, uint8(i)) + n = tr.cowLoad(&(*n.children)[i]) + } + // revert the counts + n = tr.root + for i := 0; i < len(path); i++ { + n.count++ + if !n.leaf() { + n = (*n.children)[uint8(path[i])] + } + } + value, deleted := tr.Delete(item.key) + if deleted { + return item.key, value, true + } + return tr.empty.key, tr.empty.value, false +} + +// Height returns the height of the tree. +// Returns zero if tree has no items. +func (tr *Map[K, V]) Height() int { + var height int + if tr.root != nil { + n := tr.root + for { + height++ + if n.leaf() { + break + } + n = (*n.children)[0] + } + } + return height +} + +// MapIter represents an iterator for btree.Map +type MapIter[K ordered, V any] struct { + tr *Map[K, V] + seeked bool + atstart bool + atend bool + stack []mapIterStackItem[K, V] + item mapPair[K, V] +} + +type mapIterStackItem[K ordered, V any] struct { + n *mapNode[K, V] + i int +} + +// Iter returns a read-only iterator. +func (tr *Map[K, V]) Iter() MapIter[K, V] { + var iter MapIter[K, V] + iter.tr = tr + return iter +} + +// Seek to item greater-or-equal-to key. +// Returns false if there was no item found. +func (iter *MapIter[K, V]) Seek(key K) bool { + if iter.tr == nil { + return false + } + iter.seeked = true + iter.stack = iter.stack[:0] + if iter.tr.root == nil { + return false + } + n := iter.tr.root + for { + i, found := iter.tr.bsearch(n, key) + iter.stack = append(iter.stack, mapIterStackItem[K, V]{n, i}) + if found { + iter.item = n.items[i] + return true + } + if n.leaf() { + iter.stack[len(iter.stack)-1].i-- + return iter.Next() + } + n = (*n.children)[i] + } +} + +// First moves iterator to first item in tree. +// Returns false if the tree is empty. +func (iter *MapIter[K, V]) First() bool { + if iter.tr == nil { + return false + } + iter.atend = false + iter.atstart = false + iter.seeked = true + iter.stack = iter.stack[:0] + if iter.tr.root == nil { + return false + } + n := iter.tr.root + for { + iter.stack = append(iter.stack, mapIterStackItem[K, V]{n, 0}) + if n.leaf() { + break + } + n = (*n.children)[0] + } + s := &iter.stack[len(iter.stack)-1] + iter.item = s.n.items[s.i] + return true +} + +// Last moves iterator to last item in tree. +// Returns false if the tree is empty. +func (iter *MapIter[K, V]) Last() bool { + if iter.tr == nil { + return false + } + iter.seeked = true + iter.stack = iter.stack[:0] + if iter.tr.root == nil { + return false + } + n := iter.tr.root + for { + iter.stack = append(iter.stack, mapIterStackItem[K, V]{n, len(n.items)}) + if n.leaf() { + iter.stack[len(iter.stack)-1].i-- + break + } + n = (*n.children)[len(n.items)] + } + s := &iter.stack[len(iter.stack)-1] + iter.item = s.n.items[s.i] + return true +} + +// 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 *MapIter[K, V]) Next() bool { + if iter.tr == nil { + return false + } + if !iter.seeked { + return iter.First() + } + if len(iter.stack) == 0 { + if iter.atstart { + return iter.First() && iter.Next() + } + return false + } + s := &iter.stack[len(iter.stack)-1] + s.i++ + if s.n.leaf() { + if s.i == len(s.n.items) { + for { + iter.stack = iter.stack[:len(iter.stack)-1] + if len(iter.stack) == 0 { + iter.atend = true + return false + } + s = &iter.stack[len(iter.stack)-1] + if s.i < len(s.n.items) { + break + } + } + } + } else { + n := (*s.n.children)[s.i] + for { + iter.stack = append(iter.stack, mapIterStackItem[K, V]{n, 0}) + if n.leaf() { + break + } + n = (*n.children)[0] + } + } + s = &iter.stack[len(iter.stack)-1] + iter.item = s.n.items[s.i] + return true +} + +// 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 *MapIter[K, V]) Prev() bool { + if iter.tr == nil { + return false + } + if !iter.seeked { + return false + } + if len(iter.stack) == 0 { + if iter.atend { + return iter.Last() && iter.Prev() + } + return false + } + s := &iter.stack[len(iter.stack)-1] + if s.n.leaf() { + s.i-- + if s.i == -1 { + for { + iter.stack = iter.stack[:len(iter.stack)-1] + if len(iter.stack) == 0 { + iter.atstart = true + return false + } + s = &iter.stack[len(iter.stack)-1] + s.i-- + if s.i > -1 { + break + } + } + } + } else { + n := (*s.n.children)[s.i] + for { + iter.stack = append(iter.stack, + mapIterStackItem[K, V]{n, len(n.items)}) + if n.leaf() { + iter.stack[len(iter.stack)-1].i-- + break + } + n = (*n.children)[len(n.items)] + } + } + s = &iter.stack[len(iter.stack)-1] + iter.item = s.n.items[s.i] + return true +} + +// Key returns the current iterator item key. +func (iter *MapIter[K, V]) Key() K { + return iter.item.key +} + +// Value returns the current iterator item value. +func (iter *MapIter[K, V]) Value() V { + return iter.item.value +} + +// Values returns all the values in order. +func (tr *Map[K, V]) Values() []V { + values := make([]V, 0, tr.Len()) + if tr.root != nil { + values = tr.root.values(values) + } + return values +} + +func (n *mapNode[K, V]) values(values []V) []V { + if n.leaf() { + for i := 0; i < len(n.items); i++ { + values = append(values, n.items[i].value) + } + return values + } + for i := 0; i < len(n.items); i++ { + values = (*n.children)[i].values(values) + values = append(values, n.items[i].value) + } + return (*n.children)[len(*n.children)-1].values(values) +} + +// Keys returns all the keys in order. +func (tr *Map[K, V]) Keys() []K { + keys := make([]K, 0, tr.Len()) + if tr.root != nil { + keys = tr.root.keys(keys) + } + return keys +} + +func (n *mapNode[K, V]) keys(keys []K) []K { + if n.leaf() { + for i := 0; i < len(n.items); i++ { + keys = append(keys, n.items[i].key) + } + return keys + } + for i := 0; i < len(n.items); i++ { + keys = (*n.children)[i].keys(keys) + keys = append(keys, n.items[i].key) + } + return (*n.children)[len(*n.children)-1].keys(keys) +} + +// KeyValues returns all the keys and values in order. +func (tr *Map[K, V]) KeyValues() ([]K, []V) { + keys := make([]K, 0, tr.Len()) + values := make([]V, 0, tr.Len()) + if tr.root != nil { + keys, values = tr.root.keyValues(keys, values) + } + return keys, values +} + +func (n *mapNode[K, V]) keyValues(keys []K, values []V) ([]K, []V) { + if n.leaf() { + for i := 0; i < len(n.items); i++ { + keys = append(keys, n.items[i].key) + values = append(values, n.items[i].value) + } + return keys, values + } + for i := 0; i < len(n.items); i++ { + keys, values = (*n.children)[i].keyValues(keys, values) + keys = append(keys, n.items[i].key) + values = append(values, n.items[i].value) + } + return (*n.children)[len(*n.children)-1].keyValues(keys, values) +} diff --git a/vendor/github.com/tidwall/btree/set.go b/vendor/github.com/tidwall/btree/set.go new file mode 100644 index 00000000..20399e51 --- /dev/null +++ b/vendor/github.com/tidwall/btree/set.go @@ -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() +} diff --git a/vendor/github.com/tidwall/buntdb/buntdb.go b/vendor/github.com/tidwall/buntdb/buntdb.go index 8c3408da..9b39e5c2 100644 --- a/vendor/github.com/tidwall/buntdb/buntdb.go +++ b/vendor/github.com/tidwall/buntdb/buntdb.go @@ -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() diff --git a/vendor/github.com/tidwall/gjson/README.md b/vendor/github.com/tidwall/gjson/README.md index bb56b3d9..00b4c962 100644 --- a/vendor/github.com/tidwall/gjson/README.md +++ b/vendor/github.com/tidwall/gjson/README.md @@ -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 diff --git a/vendor/github.com/tidwall/gjson/SYNTAX.md b/vendor/github.com/tidwall/gjson/SYNTAX.md index 1b866346..7a9b6a2d 100644 --- a/vendor/github.com/tidwall/gjson/SYNTAX.md +++ b/vendor/github.com/tidwall/gjson/SYNTAX.md @@ -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 diff --git a/vendor/github.com/tidwall/gjson/gjson.go b/vendor/github.com/tidwall/gjson/gjson.go index 9920c4d2..330218d4 100644 --- a/vendor/github.com/tidwall/gjson/gjson.go +++ b/vendor/github.com/tidwall/gjson/gjson.go @@ -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 diff --git a/vendor/modules.txt b/vendor/modules.txt index d137a1df..7b9c33b8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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