mirror of
https://github.com/asdine/storm.git
synced 2025-09-26 19:01:14 +08:00
Prefix method
This commit is contained in:
90
finder.go
90
finder.go
@@ -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)
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
5
storm.go
5
storm.go
@@ -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...)
|
||||
|
Reference in New Issue
Block a user