Prefix method

This commit is contained in:
Asdine El Hrychy
2017-09-09 13:55:10 +02:00
parent b08b6179a1
commit 04c46dea78
4 changed files with 164 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
package storm
import (
"fmt"
"reflect"
"github.com/asdine/storm/index"
@@ -29,6 +30,9 @@ type Finder interface {
// Range returns one or more records by the specified index within the specified range
Range(fieldName string, min, max, to interface{}, options ...func(*index.Options)) error
// Prefix returns one or more records whose given field starts with the specified prefix.
Prefix(fieldName string, prefix string, to interface{}, options ...func(*index.Options)) error
// Count counts all the records of a bucket
Count(data interface{}) (int, error)
}
@@ -403,6 +407,92 @@ func (n *node) rnge(tx *bolt.Tx, bucketName, fieldName string, cfg *structConfig
return sorter.flush()
}
// Prefix returns one or more records whose given field starts with the specified prefix.
func (n *node) Prefix(fieldName string, prefix string, to interface{}, options ...func(*index.Options)) error {
sink, err := newListSink(n, to)
if err != nil {
return err
}
bucketName := sink.bucketName()
if bucketName == "" {
return ErrNoName
}
ref := reflect.Indirect(reflect.New(sink.elemType))
cfg, err := extractSingleField(&ref, fieldName)
if err != nil {
return err
}
opts := index.NewOptions()
for _, fn := range options {
fn(opts)
}
field, ok := cfg.Fields[fieldName]
if !ok || (!field.IsID && field.Index == "") {
query := newQuery(n, q.Re(fieldName, fmt.Sprintf("^%s", prefix)))
query.Skip(opts.Skip).Limit(opts.Limit)
if opts.Reverse {
query.Reverse()
}
err = n.readTx(func(tx *bolt.Tx) error {
return query.query(tx, sink)
})
if err != nil {
return err
}
return sink.flush()
}
prfx, err := toBytes(prefix, n.s.codec)
if err != nil {
return err
}
return n.readTx(func(tx *bolt.Tx) error {
return n.prefix(tx, bucketName, fieldName, cfg, sink, prfx, opts)
})
}
func (n *node) prefix(tx *bolt.Tx, bucketName, fieldName string, cfg *structConfig, sink *listSink, prefix []byte, opts *index.Options) error {
bucket := n.GetBucket(tx, bucketName)
if bucket == nil {
reflect.Indirect(sink.ref).SetLen(0)
return nil
}
idx, err := getIndex(bucket, cfg.Fields[fieldName].Index, fieldName)
if err != nil {
return err
}
list, err := idx.Prefix(prefix, opts)
if err != nil {
return err
}
sink.results = reflect.MakeSlice(reflect.Indirect(sink.ref).Type(), len(list), len(list))
sorter := newSorter(n, sink)
for i := range list {
raw := bucket.Get(list[i])
if raw == nil {
return ErrNotFound
}
if _, err := sorter.filter(nil, bucket, list[i], raw); err != nil {
return err
}
}
return sorter.flush()
}
// Count counts all the records of a bucket
func (n *node) Count(data interface{}) (int, error) {
return n.Select().Count(data)

View File

@@ -617,3 +617,71 @@ func TestRange(t *testing.T) {
require.NoError(t, err)
require.Len(t, users, 60)
}
func TestPrefix(t *testing.T) {
db, cleanup := createDB(t)
defer cleanup()
for i := 0; i < 50; i++ {
w := User{
ID: i + 1,
}
if i%5 == 0 {
w.Name = "John"
w.Group = "group100"
} else {
w.Name = "Jack"
w.Group = "group200"
}
err := db.Save(&w)
require.NoError(t, err)
}
var users []User
err := db.Prefix("Name", "Jo", users)
require.Equal(t, ErrSlicePtrNeeded, err)
// Using indexes
err = db.Prefix("Name", "Jo", &users)
require.NoError(t, err)
require.Len(t, users, 10)
require.Equal(t, 1, users[0].ID)
require.Equal(t, 46, users[9].ID)
err = db.Prefix("Name", "Ja", &users)
require.NoError(t, err)
require.Len(t, users, 40)
require.Equal(t, 2, users[0].ID)
require.Equal(t, 50, users[39].ID)
err = db.Prefix("Name", "Ja", &users, Limit(10), Skip(20), Reverse())
require.NoError(t, err)
require.Len(t, users, 10)
require.Equal(t, 25, users[0].ID)
require.Equal(t, 14, users[9].ID)
// Using Select
err = db.Prefix("Group", "group1", &users)
require.NoError(t, err)
require.Len(t, users, 10)
require.Equal(t, 1, users[0].ID)
require.Equal(t, 46, users[9].ID)
err = db.Prefix("Group", "group2", &users)
require.NoError(t, err)
require.Len(t, users, 40)
require.Equal(t, 2, users[0].ID)
require.Equal(t, 50, users[39].ID)
err = db.Prefix("Group", "group2", &users, Limit(10), Skip(20), Reverse())
require.NoError(t, err)
require.Len(t, users, 10)
require.Equal(t, 25, users[0].ID)
require.Equal(t, 14, users[9].ID)
// Bad value
err = db.Prefix("Group", "group3", &users)
require.Equal(t, ErrNotFound, err)
}

View File

@@ -10,4 +10,5 @@ type Index interface {
All(value []byte, opts *Options) ([][]byte, error)
AllRecords(opts *Options) ([][]byte, error)
Range(min []byte, max []byte, opts *Options) ([][]byte, error)
Prefix(prefix []byte, opts *Options) ([][]byte, error)
}

View File

@@ -187,6 +187,11 @@ func (s *DB) Range(fieldName string, min, max, to interface{}, options ...func(*
return s.root.Range(fieldName, min, max, to, options...)
}
// Prefix returns one or more records whose given field starts with the specified prefix.
func (s *DB) Prefix(fieldName string, prefix string, to interface{}, options ...func(*index.Options)) error {
return s.root.Prefix(fieldName, prefix, to, options...)
}
// AllByIndex gets all the records of a bucket that are indexed in the specified index
func (s *DB) AllByIndex(fieldName string, to interface{}, options ...func(*index.Options)) error {
return s.root.AllByIndex(fieldName, to, options...)