246 lines
4.7 KiB
Go
246 lines
4.7 KiB
Go
package index
|
|
|
|
import (
|
|
"bytes"
|
|
|
|
"github.com/asdine/storm/internal"
|
|
"github.com/boltdb/bolt"
|
|
)
|
|
|
|
// NewListIndex loads a ListIndex
|
|
func NewListIndex(parent *bolt.Bucket, indexName []byte) (*ListIndex, error) {
|
|
var err error
|
|
b := parent.Bucket(indexName)
|
|
if b == nil {
|
|
if !parent.Writable() {
|
|
return nil, ErrNotFound
|
|
}
|
|
b, err = parent.CreateBucket(indexName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
ids, err := NewUniqueIndex(b, []byte("storm__ids"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ListIndex{
|
|
IndexBucket: b,
|
|
Parent: parent,
|
|
IDs: ids,
|
|
}, nil
|
|
}
|
|
|
|
// ListIndex is an index that references values and the corresponding IDs.
|
|
type ListIndex struct {
|
|
Parent *bolt.Bucket
|
|
IndexBucket *bolt.Bucket
|
|
IDs *UniqueIndex
|
|
}
|
|
|
|
// Add a value to the list index
|
|
func (idx *ListIndex) Add(newValue []byte, targetID []byte) error {
|
|
if newValue == nil || len(newValue) == 0 {
|
|
return ErrNilParam
|
|
}
|
|
if targetID == nil || len(targetID) == 0 {
|
|
return ErrNilParam
|
|
}
|
|
|
|
key := idx.IDs.Get(targetID)
|
|
if key != nil {
|
|
err := idx.IndexBucket.Delete(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = idx.IDs.Remove(targetID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
key = key[:0]
|
|
}
|
|
|
|
key = append(key, newValue...)
|
|
key = append(key, '_')
|
|
key = append(key, '_')
|
|
key = append(key, targetID...)
|
|
|
|
err := idx.IDs.Add(targetID, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return idx.IndexBucket.Put(key, targetID)
|
|
}
|
|
|
|
// Remove a value from the unique index
|
|
func (idx *ListIndex) Remove(value []byte) error {
|
|
var err error
|
|
var keys [][]byte
|
|
|
|
c := idx.IndexBucket.Cursor()
|
|
prefix := generatePrefix(value)
|
|
|
|
for k, _ := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, _ = c.Next() {
|
|
keys = append(keys, k)
|
|
}
|
|
|
|
for _, k := range keys {
|
|
err = idx.IndexBucket.Delete(k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return idx.IDs.RemoveID(value)
|
|
}
|
|
|
|
// RemoveID removes an ID from the list index
|
|
func (idx *ListIndex) RemoveID(targetID []byte) error {
|
|
value := idx.IDs.Get(targetID)
|
|
if value == nil {
|
|
return nil
|
|
}
|
|
|
|
err := idx.IndexBucket.Delete(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return idx.IDs.Remove(targetID)
|
|
}
|
|
|
|
// Get the first ID corresponding to the given value
|
|
func (idx *ListIndex) Get(value []byte) []byte {
|
|
c := idx.IndexBucket.Cursor()
|
|
prefix := generatePrefix(value)
|
|
|
|
for k, id := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, id = c.Next() {
|
|
return id
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// All the IDs corresponding to the given value
|
|
func (idx *ListIndex) All(value []byte, opts *Options) ([][]byte, error) {
|
|
var list [][]byte
|
|
c := idx.IndexBucket.Cursor()
|
|
cur := internal.Cursor{C: c, Reverse: opts != nil && opts.Reverse}
|
|
|
|
prefix := generatePrefix(value)
|
|
|
|
k, id := c.Seek(prefix)
|
|
if cur.Reverse {
|
|
var count int
|
|
for ; bytes.HasPrefix(k, prefix) && k != nil; k, _ = c.Next() {
|
|
count++
|
|
}
|
|
k, id = c.Prev()
|
|
list = make([][]byte, 0, count)
|
|
}
|
|
|
|
for ; bytes.HasPrefix(k, prefix); k, id = cur.Next() {
|
|
if opts != nil && opts.Skip > 0 {
|
|
opts.Skip--
|
|
continue
|
|
}
|
|
|
|
if opts != nil && opts.Limit == 0 {
|
|
break
|
|
}
|
|
|
|
if opts != nil && opts.Limit > 0 {
|
|
opts.Limit--
|
|
}
|
|
|
|
list = append(list, id)
|
|
}
|
|
|
|
return list, nil
|
|
}
|
|
|
|
// AllRecords returns all the IDs of this index
|
|
func (idx *ListIndex) AllRecords(opts *Options) ([][]byte, error) {
|
|
var list [][]byte
|
|
|
|
c := internal.Cursor{C: idx.IndexBucket.Cursor(), Reverse: opts != nil && opts.Reverse}
|
|
|
|
for k, id := c.First(); k != nil; k, id = c.Next() {
|
|
if id == nil || bytes.Equal(k, []byte("storm__ids")) {
|
|
continue
|
|
}
|
|
|
|
if opts != nil && opts.Skip > 0 {
|
|
opts.Skip--
|
|
continue
|
|
}
|
|
|
|
if opts != nil && opts.Limit == 0 {
|
|
break
|
|
}
|
|
|
|
if opts != nil && opts.Limit > 0 {
|
|
opts.Limit--
|
|
}
|
|
|
|
list = append(list, id)
|
|
}
|
|
|
|
return list, nil
|
|
}
|
|
|
|
// Range returns the ids corresponding to the given range of values
|
|
func (idx *ListIndex) Range(min []byte, max []byte, opts *Options) ([][]byte, error) {
|
|
var list [][]byte
|
|
|
|
c := internal.RangeCursor{
|
|
C: idx.IndexBucket.Cursor(),
|
|
Reverse: opts != nil && opts.Reverse,
|
|
Min: min,
|
|
Max: max,
|
|
CompareFn: func(val, limit []byte) int {
|
|
pos := bytes.LastIndex(val, []byte("__"))
|
|
return bytes.Compare(val[:pos], limit)
|
|
},
|
|
}
|
|
|
|
for k, id := c.First(); c.Continue(k); k, id = c.Next() {
|
|
if id == nil || bytes.Equal(k, []byte("storm__ids")) {
|
|
continue
|
|
}
|
|
|
|
if opts != nil && opts.Skip > 0 {
|
|
opts.Skip--
|
|
continue
|
|
}
|
|
|
|
if opts != nil && opts.Limit == 0 {
|
|
break
|
|
}
|
|
|
|
if opts != nil && opts.Limit > 0 {
|
|
opts.Limit--
|
|
}
|
|
|
|
list = append(list, id)
|
|
}
|
|
|
|
return list, nil
|
|
}
|
|
|
|
func generatePrefix(value []byte) []byte {
|
|
prefix := make([]byte, len(value)+2)
|
|
var i int
|
|
for i = range value {
|
|
prefix[i] = value[i]
|
|
}
|
|
prefix[i+1] = '_'
|
|
prefix[i+2] = '_'
|
|
return prefix
|
|
}
|