Skip to content

Commit

Permalink
Support go-immutable-radix ReverseIterator (#81)
Browse files Browse the repository at this point in the history
* Support go-immutable-radix ReverseIterator

go-immutable-radix 1.3.0 introduced a ReverseIterator.

This Commit adds Last(), GetReverse(), ReverseLowerBound()
and other functions to support working iteratively in reverse.
  • Loading branch information
drewbailey authored Oct 6, 2020
1 parent ac8c839 commit 4cf1c7f
Show file tree
Hide file tree
Showing 4 changed files with 430 additions and 3 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ module github.com/hashicorp/go-memdb
go 1.12

require (
github.com/hashicorp/go-immutable-radix v1.2.0
github.com/hashicorp/go-immutable-radix v1.3.0
github.com/hashicorp/golang-lru v0.5.4 // indirect
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/hashicorp/go-immutable-radix v1.2.0 h1:l6UW37iCXwZkZoAbEYnptSHVE/cQ5bOTPYG5W3vf9+8=
github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
github.com/hashicorp/go-immutable-radix v1.3.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=
Expand Down
111 changes: 111 additions & 0 deletions txn.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,13 +536,48 @@ func (txn *Txn) FirstWatch(table, index string, args ...interface{}) (<-chan str
return watch, value, nil
}

// LastWatch is used to return the last matching object for
// the given constraints on the index along with the watch channel
func (txn *Txn) LastWatch(table, index string, args ...interface{}) (<-chan struct{}, interface{}, error) {
// Get the index value
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)

// Do an exact lookup
if indexSchema.Unique && val != nil && indexSchema.Name == index {
watch, obj, ok := indexTxn.GetWatch(val)
if !ok {
return watch, nil, nil
}
return watch, obj, nil
}

// Handle non-unique index by using an iterator and getting the last value
iter := indexTxn.Root().ReverseIterator()
watch := iter.SeekPrefixWatch(val)
_, value, _ := iter.Previous()
return watch, value, nil
}

// First is used to return the first matching object for
// the given constraints on the index
func (txn *Txn) First(table, index string, args ...interface{}) (interface{}, error) {
_, val, err := txn.FirstWatch(table, index, args...)
return val, err
}

// Last is used to return the last matching object for
// the given constraints on the index
func (txn *Txn) Last(table, index string, args ...interface{}) (interface{}, error) {
_, val, err := txn.LastWatch(table, index, args...)
return val, err
}

// LongestPrefix is used to fetch the longest prefix match for the given
// constraints on the index. Note that this will not work with the memdb
// StringFieldIndex because it adds null terminators which prevent the
Expand Down Expand Up @@ -654,6 +689,26 @@ func (txn *Txn) Get(table, index string, args ...interface{}) (ResultIterator, e
return iter, nil
}

// GetReverse is used to construct a Reverse ResultIterator over all the
// rows that match the given constraints of an index.
// The returned ResultIterator's Next() will return the next Previous value
func (txn *Txn) GetReverse(table, index string, args ...interface{}) (ResultIterator, error) {
indexIter, val, err := txn.getIndexIteratorReverse(table, index, args...)
if err != nil {
return nil, err
}

// Seek the iterator to the appropriate sub-set
watchCh := indexIter.SeekPrefixWatch(val)

// Create an iterator
iter := &radixReverseIterator{
iter: indexIter,
watchCh: watchCh,
}
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
Expand All @@ -676,6 +731,29 @@ func (txn *Txn) LowerBound(table, index string, args ...interface{}) (ResultIter
return iter, nil
}

// ReverseLowerBound is used to construct a Reverse ResultIterator over all the
// the range of rows that have an index value less than or equal to the
// provide args. Calling this then iterating until the rows are lower 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) ReverseLowerBound(table, index string, args ...interface{}) (ResultIterator, error) {
indexIter, val, err := txn.getIndexIteratorReverse(table, index, args...)
if err != nil {
return nil, err
}

// Seek the iterator to the appropriate sub-set
indexIter.SeekReverseLowerBound(val)

// Create an iterator
iter := &radixReverseIterator{
iter: indexIter,
}
return iter, nil
}

// objectID is a tuple of table name and the raw internal id byte slice
// converted to a string. It's only converted to a string to make it comparable
// so this struct can be used as a map index.
Expand Down Expand Up @@ -777,6 +855,22 @@ func (txn *Txn) getIndexIterator(table, index string, args ...interface{}) (*ira
return indexIter, val, nil
}

func (txn *Txn) getIndexIteratorReverse(table, index string, args ...interface{}) (*iradix.ReverseIterator, []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.ReverseIterator()
return indexIter, val, nil
}

// Defer is used to push a new arbitrary function onto a stack which
// gets called when a transaction is committed and finished. Deferred
// functions are called in LIFO order, and only invoked at the end of
Expand Down Expand Up @@ -805,6 +899,23 @@ func (r *radixIterator) Next() interface{} {
return value
}

type radixReverseIterator struct {
iter *iradix.ReverseIterator
watchCh <-chan struct{}
}

func (r *radixReverseIterator) Next() interface{} {
_, value, ok := r.iter.Previous()
if !ok {
return nil
}
return value
}

func (r *radixReverseIterator) WatchCh() <-chan struct{} {
return r.watchCh
}

// Snapshot creates a snapshot of the current state of the transaction.
// Returns a new read-only transaction or nil if the transaction is already
// aborted or committed.
Expand Down
Loading

0 comments on commit 4cf1c7f

Please sign in to comment.