mirror of
				https://github.com/asdine/storm.git
				synced 2025-10-31 18:33:19 +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
	 Asdine El Hrychy
					Asdine El Hrychy