mirror of
https://github.com/asdine/storm.git
synced 2025-11-01 10:52:33 +08:00
Prefix method
This commit is contained in:
90
finder.go
90
finder.go
@@ -1,6 +1,7 @@
|
|||||||
package storm
|
package storm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/asdine/storm/index"
|
"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 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
|
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 counts all the records of a bucket
|
||||||
Count(data interface{}) (int, error)
|
Count(data interface{}) (int, error)
|
||||||
}
|
}
|
||||||
@@ -403,6 +407,92 @@ func (n *node) rnge(tx *bolt.Tx, bucketName, fieldName string, cfg *structConfig
|
|||||||
return sorter.flush()
|
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
|
// Count counts all the records of a bucket
|
||||||
func (n *node) Count(data interface{}) (int, error) {
|
func (n *node) Count(data interface{}) (int, error) {
|
||||||
return n.Select().Count(data)
|
return n.Select().Count(data)
|
||||||
|
|||||||
@@ -617,3 +617,71 @@ func TestRange(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, users, 60)
|
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)
|
All(value []byte, opts *Options) ([][]byte, error)
|
||||||
AllRecords(opts *Options) ([][]byte, error)
|
AllRecords(opts *Options) ([][]byte, error)
|
||||||
Range(min []byte, max []byte, 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...)
|
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
|
// 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 {
|
func (s *DB) AllByIndex(fieldName string, to interface{}, options ...func(*index.Options)) error {
|
||||||
return s.root.AllByIndex(fieldName, to, options...)
|
return s.root.AllByIndex(fieldName, to, options...)
|
||||||
|
|||||||
Reference in New Issue
Block a user