Dependency: Update github.com/hashicorp/go-memdb to v1.0.3 (#6626)
This commit is contained in:
parent
979ad7fecb
commit
e8ee7c42a3
2
go.mod
2
go.mod
|
@ -31,7 +31,7 @@ require (
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1
|
github.com/hashicorp/go-cleanhttp v0.5.1
|
||||||
github.com/hashicorp/go-discover v0.0.0-20190403160810-22221edb15cd
|
github.com/hashicorp/go-discover v0.0.0-20190403160810-22221edb15cd
|
||||||
github.com/hashicorp/go-hclog v0.9.2
|
github.com/hashicorp/go-hclog v0.9.2
|
||||||
github.com/hashicorp/go-memdb v0.0.0-20180223233045-1289e7fffe71
|
github.com/hashicorp/go-memdb v1.0.3
|
||||||
github.com/hashicorp/go-msgpack v0.5.5
|
github.com/hashicorp/go-msgpack v0.5.5
|
||||||
github.com/hashicorp/go-multierror v1.0.0
|
github.com/hashicorp/go-multierror v1.0.0
|
||||||
github.com/hashicorp/go-plugin v1.0.1
|
github.com/hashicorp/go-plugin v1.0.1
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -127,8 +127,10 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC
|
||||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
github.com/hashicorp/go-memdb v0.0.0-20180223233045-1289e7fffe71 h1:yxxFgVz31vFoKKTtRUNbXLNe4GFnbLKqg+0N7yG42L8=
|
github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc=
|
||||||
github.com/hashicorp/go-memdb v0.0.0-20180223233045-1289e7fffe71/go.mod h1:kbfItVoBJwCfKXDXN4YoAXjxcFVZ7MRrJzyTX6H4giE=
|
github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-memdb v1.0.3 h1:iiqzNk8jKB6/sLRj623Ui/Vi1zf21LOUpgzGjTge6a8=
|
||||||
|
github.com/hashicorp/go-memdb v1.0.3/go.mod h1:LWQ8R70vPrS4OEY9k28D2z8/Zzyu34NVzeRibGAzHO0=
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# 1.1.0 (May 22nd, 2019)
|
||||||
|
|
||||||
|
FEATURES
|
||||||
|
|
||||||
|
* Add `SeekLowerBound` to allow for range scans. [[GH-24](https://github.com/hashicorp/go-immutable-radix/pull/24)]
|
||||||
|
|
||||||
|
# 1.0.0 (August 30th, 2018)
|
||||||
|
|
||||||
|
* go mod adopted
|
|
@ -39,3 +39,28 @@ if string(m) != "foo" {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Here is an example of performing a range scan of the keys.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Create a tree
|
||||||
|
r := iradix.New()
|
||||||
|
r, _, _ = r.Insert([]byte("001"), 1)
|
||||||
|
r, _, _ = r.Insert([]byte("002"), 2)
|
||||||
|
r, _, _ = r.Insert([]byte("005"), 5)
|
||||||
|
r, _, _ = r.Insert([]byte("010"), 10)
|
||||||
|
r, _, _ = r.Insert([]byte("100"), 10)
|
||||||
|
|
||||||
|
// Range scan over the keys that sort lexicographically between [003, 050)
|
||||||
|
it := r.Root().Iterator()
|
||||||
|
it.SeekLowerBound([]byte("003"))
|
||||||
|
for key, _, ok := it.Next(); ok; key, _, ok = it.Next() {
|
||||||
|
if key >= "050" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fmt.Println(key)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// 005
|
||||||
|
// 010
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package iradix
|
package iradix
|
||||||
|
|
||||||
import "bytes"
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
// Iterator is used to iterate over a set of nodes
|
// Iterator is used to iterate over a set of nodes
|
||||||
// in pre-order
|
// in pre-order
|
||||||
|
@ -53,6 +55,101 @@ func (i *Iterator) SeekPrefix(prefix []byte) {
|
||||||
i.SeekPrefixWatch(prefix)
|
i.SeekPrefixWatch(prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Iterator) recurseMin(n *Node) *Node {
|
||||||
|
// Traverse to the minimum child
|
||||||
|
if n.leaf != nil {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
if len(n.edges) > 0 {
|
||||||
|
// Add all the other edges to the stack (the min node will be added as
|
||||||
|
// we recurse)
|
||||||
|
i.stack = append(i.stack, n.edges[1:])
|
||||||
|
return i.recurseMin(n.edges[0].node)
|
||||||
|
}
|
||||||
|
// Shouldn't be possible
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SeekLowerBound is used to seek the iterator to the smallest key that is
|
||||||
|
// greater or equal to the given key. There is no watch variant as it's hard to
|
||||||
|
// predict based on the radix structure which node(s) changes might affect the
|
||||||
|
// result.
|
||||||
|
func (i *Iterator) SeekLowerBound(key []byte) {
|
||||||
|
// Wipe the stack. Unlike Prefix iteration, we need to build the stack as we
|
||||||
|
// go because we need only a subset of edges of many nodes in the path to the
|
||||||
|
// leaf with the lower bound.
|
||||||
|
i.stack = []edges{}
|
||||||
|
n := i.node
|
||||||
|
search := key
|
||||||
|
|
||||||
|
found := func(n *Node) {
|
||||||
|
i.node = n
|
||||||
|
i.stack = append(i.stack, edges{edge{node: n}})
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Compare current prefix with the search key's same-length prefix.
|
||||||
|
var prefixCmp int
|
||||||
|
if len(n.prefix) < len(search) {
|
||||||
|
prefixCmp = bytes.Compare(n.prefix, search[0:len(n.prefix)])
|
||||||
|
} else {
|
||||||
|
prefixCmp = bytes.Compare(n.prefix, search)
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefixCmp > 0 {
|
||||||
|
// Prefix is larger, that means the lower bound is greater than the search
|
||||||
|
// and from now on we need to follow the minimum path to the smallest
|
||||||
|
// leaf under this subtree.
|
||||||
|
n = i.recurseMin(n)
|
||||||
|
if n != nil {
|
||||||
|
found(n)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefixCmp < 0 {
|
||||||
|
// Prefix is smaller than search prefix, that means there is no lower
|
||||||
|
// bound
|
||||||
|
i.node = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix is equal, we are still heading for an exact match. If this is a
|
||||||
|
// leaf we're done.
|
||||||
|
if n.leaf != nil {
|
||||||
|
if bytes.Compare(n.leaf.key, key) < 0 {
|
||||||
|
i.node = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
found(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the search prefix
|
||||||
|
if len(n.prefix) > len(search) {
|
||||||
|
search = []byte{}
|
||||||
|
} else {
|
||||||
|
search = search[len(n.prefix):]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, take the lower bound next edge.
|
||||||
|
idx, lbNode := n.getLowerBoundEdge(search[0])
|
||||||
|
if lbNode == nil {
|
||||||
|
i.node = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create stack edges for the all strictly higher edges in this node.
|
||||||
|
if idx+1 < len(n.edges) {
|
||||||
|
i.stack = append(i.stack, n.edges[idx+1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
i.node = lbNode
|
||||||
|
// Recurse
|
||||||
|
n = lbNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Next returns the next node in order
|
// Next returns the next node in order
|
||||||
func (i *Iterator) Next() ([]byte, interface{}, bool) {
|
func (i *Iterator) Next() ([]byte, interface{}, bool) {
|
||||||
// Initialize our stack if needed
|
// Initialize our stack if needed
|
||||||
|
|
|
@ -79,6 +79,18 @@ func (n *Node) getEdge(label byte) (int, *Node) {
|
||||||
return -1, nil
|
return -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Node) getLowerBoundEdge(label byte) (int, *Node) {
|
||||||
|
num := len(n.edges)
|
||||||
|
idx := sort.Search(num, func(i int) bool {
|
||||||
|
return n.edges[i].label >= label
|
||||||
|
})
|
||||||
|
// we want lower bound behavior so return even if it's not an exact match
|
||||||
|
if idx < num {
|
||||||
|
return idx, n.edges[idx].node
|
||||||
|
}
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n *Node) delEdge(label byte) {
|
func (n *Node) delEdge(label byte) {
|
||||||
num := len(n.edges)
|
num := len(n.edges)
|
||||||
idx := sort.Search(num, func(i int) bool {
|
idx := sort.Search(num, func(i int) bool {
|
||||||
|
|
|
@ -22,3 +22,5 @@ _testmain.go
|
||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
|
|
||||||
|
.idea
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- "1.10"
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test
|
|
|
@ -1,4 +1,4 @@
|
||||||
# go-memdb
|
# go-memdb [![CircleCI](https://circleci.com/gh/hashicorp/go-memdb/tree/master.svg?style=svg)](https://circleci.com/gh/hashicorp/go-memdb/tree/master)
|
||||||
|
|
||||||
Provides the `memdb` package that implements a simple in-memory database
|
Provides the `memdb` package that implements a simple in-memory database
|
||||||
built on immutable radix trees. The database provides Atomicity, Consistency
|
built on immutable radix trees. The database provides Atomicity, Consistency
|
||||||
|
@ -21,7 +21,7 @@ The database provides the following:
|
||||||
a single field index, or more advanced compound field indexes. Certain types like
|
a single field index, or more advanced compound field indexes. Certain types like
|
||||||
UUID can be efficiently compressed from strings into byte indexes for reduced
|
UUID can be efficiently compressed from strings into byte indexes for reduced
|
||||||
storage requirements.
|
storage requirements.
|
||||||
|
|
||||||
* Watches - Callers can populate a watch set as part of a query, which can be used to
|
* Watches - Callers can populate a watch set as part of a query, which can be used to
|
||||||
detect when a modification has been made to the database which affects the query
|
detect when a modification has been made to the database which affects the query
|
||||||
results. This lets callers easily watch for changes in the database in a very general
|
results. This lets callers easily watch for changes in the database in a very general
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/hashicorp/go-memdb
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require github.com/hashicorp/go-immutable-radix v1.1.0
|
|
@ -0,0 +1,6 @@
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
@ -3,6 +3,7 @@ package memdb
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -63,9 +64,16 @@ func (s *StringFieldIndex) FromObject(obj interface{}) (bool, []byte, error) {
|
||||||
v = reflect.Indirect(v) // Dereference the pointer if any
|
v = reflect.Indirect(v) // Dereference the pointer if any
|
||||||
|
|
||||||
fv := v.FieldByName(s.Field)
|
fv := v.FieldByName(s.Field)
|
||||||
if !fv.IsValid() {
|
isPtr := fv.Kind() == reflect.Ptr
|
||||||
|
fv = reflect.Indirect(fv)
|
||||||
|
if !isPtr && !fv.IsValid() {
|
||||||
return false, nil,
|
return false, nil,
|
||||||
fmt.Errorf("field '%s' for %#v is invalid", s.Field, obj)
|
fmt.Errorf("field '%s' for %#v is invalid %v ", s.Field, obj, isPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPtr && !fv.IsValid() {
|
||||||
|
val := ""
|
||||||
|
return true, []byte(val), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
val := fv.String()
|
val := fv.String()
|
||||||
|
@ -188,6 +196,16 @@ func (s *StringSliceFieldIndex) PrefixFromArgs(args ...interface{}) ([]byte, err
|
||||||
|
|
||||||
// StringMapFieldIndex is used to extract a field of type map[string]string
|
// StringMapFieldIndex is used to extract a field of type map[string]string
|
||||||
// from an object using reflection and builds an index on that field.
|
// from an object using reflection and builds an index on that field.
|
||||||
|
//
|
||||||
|
// Note that although FromArgs in theory supports using either one or
|
||||||
|
// two arguments, there is a bug: FromObject only creates an index
|
||||||
|
// using key/value, and does not also create an index using key. This
|
||||||
|
// means a lookup using one argument will never actually work.
|
||||||
|
//
|
||||||
|
// It is currently left as-is to prevent backwards compatibility
|
||||||
|
// issues.
|
||||||
|
//
|
||||||
|
// TODO: Fix this in the next major bump.
|
||||||
type StringMapFieldIndex struct {
|
type StringMapFieldIndex struct {
|
||||||
Field string
|
Field string
|
||||||
Lowercase bool
|
Lowercase bool
|
||||||
|
@ -233,6 +251,8 @@ func (s *StringMapFieldIndex) FromObject(obj interface{}) (bool, [][]byte, error
|
||||||
return true, vals, nil
|
return true, vals, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WARNING: Because of a bug in FromObject, this function will never return
|
||||||
|
// a value when using the single-argument version.
|
||||||
func (s *StringMapFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
func (s *StringMapFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
||||||
if len(args) > 2 || len(args) == 0 {
|
if len(args) > 2 || len(args) == 0 {
|
||||||
return nil, fmt.Errorf("must provide one or two arguments")
|
return nil, fmt.Errorf("must provide one or two arguments")
|
||||||
|
@ -262,6 +282,79 @@ func (s *StringMapFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
||||||
return []byte(key), nil
|
return []byte(key), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IntFieldIndex is used to extract an int field from an object using
|
||||||
|
// reflection and builds an index on that field.
|
||||||
|
type IntFieldIndex struct {
|
||||||
|
Field string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IntFieldIndex) FromObject(obj interface{}) (bool, []byte, error) {
|
||||||
|
v := reflect.ValueOf(obj)
|
||||||
|
v = reflect.Indirect(v) // Dereference the pointer if any
|
||||||
|
|
||||||
|
fv := v.FieldByName(i.Field)
|
||||||
|
if !fv.IsValid() {
|
||||||
|
return false, nil,
|
||||||
|
fmt.Errorf("field '%s' for %#v is invalid", i.Field, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the type
|
||||||
|
k := fv.Kind()
|
||||||
|
size, ok := IsIntType(k)
|
||||||
|
if !ok {
|
||||||
|
return false, nil, fmt.Errorf("field %q is of type %v; want an int", i.Field, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the value and encode it
|
||||||
|
val := fv.Int()
|
||||||
|
buf := make([]byte, size)
|
||||||
|
binary.PutVarint(buf, val)
|
||||||
|
|
||||||
|
return true, buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IntFieldIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return nil, fmt.Errorf("must provide only a single argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.ValueOf(args[0])
|
||||||
|
if !v.IsValid() {
|
||||||
|
return nil, fmt.Errorf("%#v is invalid", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
k := v.Kind()
|
||||||
|
size, ok := IsIntType(k)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("arg is of type %v; want a int", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
val := v.Int()
|
||||||
|
buf := make([]byte, size)
|
||||||
|
binary.PutVarint(buf, val)
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIntType returns whether the passed type is a type of int and the number
|
||||||
|
// of bytes needed to encode the type.
|
||||||
|
func IsIntType(k reflect.Kind) (size int, okay bool) {
|
||||||
|
switch k {
|
||||||
|
case reflect.Int:
|
||||||
|
return binary.MaxVarintLen64, true
|
||||||
|
case reflect.Int8:
|
||||||
|
return 2, true
|
||||||
|
case reflect.Int16:
|
||||||
|
return binary.MaxVarintLen16, true
|
||||||
|
case reflect.Int32:
|
||||||
|
return binary.MaxVarintLen32, true
|
||||||
|
case reflect.Int64:
|
||||||
|
return binary.MaxVarintLen64, true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UintFieldIndex is used to extract a uint field from an object using
|
// UintFieldIndex is used to extract a uint field from an object using
|
||||||
// reflection and builds an index on that field.
|
// reflection and builds an index on that field.
|
||||||
type UintFieldIndex struct {
|
type UintFieldIndex struct {
|
||||||
|
@ -540,7 +633,7 @@ func (c *CompoundIndex) FromObject(raw interface{}) (bool, []byte, error) {
|
||||||
|
|
||||||
func (c *CompoundIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
func (c *CompoundIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
||||||
if len(args) != len(c.Indexes) {
|
if len(args) != len(c.Indexes) {
|
||||||
return nil, fmt.Errorf("less arguments than index fields")
|
return nil, fmt.Errorf("non-equivalent argument count and index fields")
|
||||||
}
|
}
|
||||||
var out []byte
|
var out []byte
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
|
@ -579,3 +672,177 @@ func (c *CompoundIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompoundMultiIndex is used to build an index using multiple
|
||||||
|
// sub-indexes.
|
||||||
|
//
|
||||||
|
// Unlike CompoundIndex, CompoundMultiIndex can have both
|
||||||
|
// SingleIndexer and MultiIndexer sub-indexers. However, each
|
||||||
|
// MultiIndexer adds considerable overhead/complexity in terms of
|
||||||
|
// the number of indexes created under-the-hood. It is not suggested
|
||||||
|
// to use more than one or two, if possible.
|
||||||
|
//
|
||||||
|
// Another change from CompoundIndexer is that if AllowMissing is
|
||||||
|
// set, not only is it valid to have empty index fields, but it will
|
||||||
|
// still create index values up to the first empty index. This means
|
||||||
|
// that if you have a value with an empty field, rather than using a
|
||||||
|
// prefix for lookup, you can simply pass in less arguments. As an
|
||||||
|
// example, if {Foo, Bar} is indexed but Bar is missing for a value
|
||||||
|
// and AllowMissing is set, an index will still be created for {Foo}
|
||||||
|
// and it is valid to do a lookup passing in only Foo as an argument.
|
||||||
|
// Note that the ordering isn't guaranteed -- it's last-insert wins,
|
||||||
|
// but this is true if you have two objects that have the same
|
||||||
|
// indexes not using AllowMissing anyways.
|
||||||
|
//
|
||||||
|
// Because StringMapFieldIndexers can take a varying number of args,
|
||||||
|
// it is currently a requirement that whenever it is used, two
|
||||||
|
// arguments must _always_ be provided for it. In theory we only
|
||||||
|
// need one, except a bug in that indexer means the single-argument
|
||||||
|
// version will never work. You can leave the second argument nil,
|
||||||
|
// but it will never produce a value. We support this for whenever
|
||||||
|
// that bug is fixed, likely in a next major version bump.
|
||||||
|
//
|
||||||
|
// Prefix-based indexing is not currently supported.
|
||||||
|
type CompoundMultiIndex struct {
|
||||||
|
Indexes []Indexer
|
||||||
|
|
||||||
|
// AllowMissing results in an index based on only the indexers
|
||||||
|
// that return data. If true, you may end up with 2/3 columns
|
||||||
|
// indexed which might be useful for an index scan. Otherwise,
|
||||||
|
// CompoundMultiIndex requires all indexers to be satisfied.
|
||||||
|
AllowMissing bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompoundMultiIndex) FromObject(raw interface{}) (bool, [][]byte, error) {
|
||||||
|
// At each entry, builder is storing the results from the next index
|
||||||
|
builder := make([][][]byte, 0, len(c.Indexes))
|
||||||
|
// Start with something higher to avoid resizing if possible
|
||||||
|
out := make([][]byte, 0, len(c.Indexes)^3)
|
||||||
|
|
||||||
|
forloop:
|
||||||
|
// This loop goes through each indexer and adds the value(s) provided to the next
|
||||||
|
// entry in the slice. We can then later walk it like a tree to construct the indices.
|
||||||
|
for i, idxRaw := range c.Indexes {
|
||||||
|
switch idx := idxRaw.(type) {
|
||||||
|
case SingleIndexer:
|
||||||
|
ok, val, err := idx.FromObject(raw)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, fmt.Errorf("single sub-index %d error: %v", i, err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
if c.AllowMissing {
|
||||||
|
break forloop
|
||||||
|
} else {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder = append(builder, [][]byte{val})
|
||||||
|
|
||||||
|
case MultiIndexer:
|
||||||
|
ok, vals, err := idx.FromObject(raw)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, fmt.Errorf("multi sub-index %d error: %v", i, err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
if c.AllowMissing {
|
||||||
|
break forloop
|
||||||
|
} else {
|
||||||
|
return false, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add each of the new values to each of the old values
|
||||||
|
builder = append(builder, vals)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false, nil, fmt.Errorf("sub-index %d does not satisfy either SingleIndexer or MultiIndexer", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are walking through the builder slice essentially in a depth-first fashion,
|
||||||
|
// building the prefix and leaves as we go. If AllowMissing is false, we only insert
|
||||||
|
// these full paths to leaves. Otherwise, we also insert each prefix along the way.
|
||||||
|
// This allows for lookup in FromArgs when AllowMissing is true that does not contain
|
||||||
|
// the full set of arguments. e.g. for {Foo, Bar} where an object has only the Foo
|
||||||
|
// field specified as "abc", it is valid to call FromArgs with just "abc".
|
||||||
|
var walkVals func([]byte, int)
|
||||||
|
walkVals = func(currPrefix []byte, depth int) {
|
||||||
|
if depth == len(builder)-1 {
|
||||||
|
// These are the "leaves", so append directly
|
||||||
|
for _, v := range builder[depth] {
|
||||||
|
out = append(out, append(currPrefix, v...))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range builder[depth] {
|
||||||
|
nextPrefix := append(currPrefix, v...)
|
||||||
|
if c.AllowMissing {
|
||||||
|
out = append(out, nextPrefix)
|
||||||
|
}
|
||||||
|
walkVals(nextPrefix, depth+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walkVals(nil, 0)
|
||||||
|
|
||||||
|
return true, out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CompoundMultiIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
||||||
|
var stringMapCount int
|
||||||
|
var argCount int
|
||||||
|
for _, index := range c.Indexes {
|
||||||
|
if argCount >= len(args) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := index.(*StringMapFieldIndex); ok {
|
||||||
|
// We require pairs for StringMapFieldIndex, but only got one
|
||||||
|
if argCount+1 >= len(args) {
|
||||||
|
return nil, errors.New("invalid number of arguments")
|
||||||
|
}
|
||||||
|
stringMapCount++
|
||||||
|
argCount += 2
|
||||||
|
} else {
|
||||||
|
argCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argCount = 0
|
||||||
|
|
||||||
|
switch c.AllowMissing {
|
||||||
|
case true:
|
||||||
|
if len(args) > len(c.Indexes)+stringMapCount {
|
||||||
|
return nil, errors.New("too many arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
if len(args) != len(c.Indexes)+stringMapCount {
|
||||||
|
return nil, errors.New("number of arguments does not equal number of indexers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []byte
|
||||||
|
var val []byte
|
||||||
|
var err error
|
||||||
|
for i, idx := range c.Indexes {
|
||||||
|
if argCount >= len(args) {
|
||||||
|
// We're done; should only hit this if AllowMissing
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := idx.(*StringMapFieldIndex); ok {
|
||||||
|
if args[argCount+1] == nil {
|
||||||
|
val, err = idx.FromArgs(args[argCount])
|
||||||
|
} else {
|
||||||
|
val, err = idx.FromArgs(args[argCount : argCount+2]...)
|
||||||
|
}
|
||||||
|
argCount += 2
|
||||||
|
} else {
|
||||||
|
val, err = idx.FromArgs(args[argCount])
|
||||||
|
argCount++
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("sub-index %d error: %v", i, err)
|
||||||
|
}
|
||||||
|
out = append(out, val...)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/hashicorp/go-immutable-radix"
|
iradix "github.com/hashicorp/go-immutable-radix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -591,19 +591,11 @@ type ResultIterator interface {
|
||||||
// Get is used to construct a ResultIterator over all the
|
// Get is used to construct a ResultIterator over all the
|
||||||
// rows that match the given constraints of an index.
|
// rows that match the given constraints of an index.
|
||||||
func (txn *Txn) Get(table, index string, args ...interface{}) (ResultIterator, error) {
|
func (txn *Txn) Get(table, index string, args ...interface{}) (ResultIterator, error) {
|
||||||
// Get the index value to scan
|
indexIter, val, err := txn.getIndexIterator(table, index, args...)
|
||||||
indexSchema, val, err := txn.getIndexValue(table, index, args...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the index itself
|
|
||||||
indexTxn := txn.readableIndex(table, indexSchema.Name)
|
|
||||||
indexRoot := indexTxn.Root()
|
|
||||||
|
|
||||||
// Get an interator over the index
|
|
||||||
indexIter := indexRoot.Iterator()
|
|
||||||
|
|
||||||
// Seek the iterator to the appropriate sub-set
|
// Seek the iterator to the appropriate sub-set
|
||||||
watchCh := indexIter.SeekPrefixWatch(val)
|
watchCh := indexIter.SeekPrefixWatch(val)
|
||||||
|
|
||||||
|
@ -615,6 +607,44 @@ func (txn *Txn) Get(table, index string, args ...interface{}) (ResultIterator, e
|
||||||
return iter, nil
|
return iter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LowerBound is used to construct a ResultIterator over all the the range of
|
||||||
|
// rows that have an index value greater than or equal to the provide args.
|
||||||
|
// Calling this then iterating until the rows are larger than required allows
|
||||||
|
// range scans within an index. It is not possible to watch the resulting
|
||||||
|
// iterator since the radix tree doesn't efficiently allow watching on lower
|
||||||
|
// bound changes. The WatchCh returned will be nill and so will block forever.
|
||||||
|
func (txn *Txn) LowerBound(table, index string, args ...interface{}) (ResultIterator, error) {
|
||||||
|
indexIter, val, err := txn.getIndexIterator(table, index, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek the iterator to the appropriate sub-set
|
||||||
|
indexIter.SeekLowerBound(val)
|
||||||
|
|
||||||
|
// Create an iterator
|
||||||
|
iter := &radixIterator{
|
||||||
|
iter: indexIter,
|
||||||
|
}
|
||||||
|
return iter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txn *Txn) getIndexIterator(table, index string, args ...interface{}) (*iradix.Iterator, []byte, error) {
|
||||||
|
// Get the index value to scan
|
||||||
|
indexSchema, val, err := txn.getIndexValue(table, index, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the index itself
|
||||||
|
indexTxn := txn.readableIndex(table, indexSchema.Name)
|
||||||
|
indexRoot := indexTxn.Root()
|
||||||
|
|
||||||
|
// Get an interator over the index
|
||||||
|
indexIter := indexRoot.Iterator()
|
||||||
|
return indexIter, val, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Defer is used to push a new arbitrary function onto a stack which
|
// Defer is used to push a new arbitrary function onto a stack which
|
||||||
// gets called when a transaction is committed and finished. Deferred
|
// gets called when a transaction is committed and finished. Deferred
|
||||||
// functions are called in LIFO order, and only invoked at the end of
|
// functions are called in LIFO order, and only invoked at the end of
|
||||||
|
|
|
@ -2,7 +2,7 @@ package memdb
|
||||||
|
|
||||||
//go:generate sh -c "go run watch-gen/main.go >watch_few.go"
|
//go:generate sh -c "go run watch-gen/main.go >watch_few.go"
|
||||||
|
|
||||||
import(
|
import (
|
||||||
"context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -186,9 +186,9 @@ github.com/hashicorp/go-discover/provider/triton
|
||||||
github.com/hashicorp/go-discover/provider/vsphere
|
github.com/hashicorp/go-discover/provider/vsphere
|
||||||
# github.com/hashicorp/go-hclog v0.9.2
|
# github.com/hashicorp/go-hclog v0.9.2
|
||||||
github.com/hashicorp/go-hclog
|
github.com/hashicorp/go-hclog
|
||||||
# github.com/hashicorp/go-immutable-radix v1.0.0
|
# github.com/hashicorp/go-immutable-radix v1.1.0
|
||||||
github.com/hashicorp/go-immutable-radix
|
github.com/hashicorp/go-immutable-radix
|
||||||
# github.com/hashicorp/go-memdb v0.0.0-20180223233045-1289e7fffe71
|
# github.com/hashicorp/go-memdb v1.0.3
|
||||||
github.com/hashicorp/go-memdb
|
github.com/hashicorp/go-memdb
|
||||||
# github.com/hashicorp/go-msgpack v0.5.5
|
# github.com/hashicorp/go-msgpack v0.5.5
|
||||||
github.com/hashicorp/go-msgpack/codec
|
github.com/hashicorp/go-msgpack/codec
|
||||||
|
|
Loading…
Reference in New Issue