2020-10-28 14:49:30 +00:00
|
|
|
package meta
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
2020-11-10 12:55:54 +00:00
|
|
|
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
2020-10-28 14:49:30 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"go.etcd.io/bbolt"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Select returns list of addresses of objects that match search filters.
|
2020-11-06 09:41:59 +00:00
|
|
|
func (db *DB) Select(fs object.SearchFilters) (res []*object.Address, err error) {
|
|
|
|
err = db.boltDB.View(func(tx *bbolt.Tx) error {
|
|
|
|
res, err = db.selectObjects(tx, fs)
|
|
|
|
return err
|
|
|
|
})
|
2020-11-03 12:08:54 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
return
|
|
|
|
}
|
2020-11-03 12:08:54 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
func (db *DB) selectObjects(tx *bbolt.Tx, fs object.SearchFilters) ([]*object.Address, error) {
|
|
|
|
if len(fs) == 0 {
|
|
|
|
return db.selectAll(tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// get indexed bucket
|
|
|
|
indexBucket := tx.Bucket(indexBucket)
|
|
|
|
if indexBucket == nil {
|
|
|
|
// empty storage
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// keep processed addresses
|
|
|
|
// value equal to number (index+1) of latest matched filter
|
|
|
|
mAddr := make(map[string]int)
|
|
|
|
|
|
|
|
for fNum := range fs {
|
|
|
|
matchFunc, ok := db.matchers[fs[fNum].Operation()]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("no function for matcher %v", fs[fNum].Operation())
|
|
|
|
}
|
2020-11-03 12:08:54 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
key := fs[fNum].Header()
|
2020-11-03 12:08:54 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
// get bucket with values
|
|
|
|
keyBucket := indexBucket.Bucket([]byte(key))
|
|
|
|
if keyBucket == nil {
|
|
|
|
// no object has this attribute => empty result
|
|
|
|
return nil, nil
|
2020-11-03 12:08:54 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
fVal := fs[fNum].Value()
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
// iterate over all existing values for the key
|
|
|
|
if err := keyBucket.ForEach(func(k, v []byte) error {
|
2020-11-10 12:55:54 +00:00
|
|
|
include := matchFunc(key, string(cutKeyBytes(k)), fVal)
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-11-09 14:58:22 +00:00
|
|
|
if include {
|
|
|
|
return keyBucket.Bucket(k).ForEach(func(k, _ []byte) error {
|
|
|
|
if num := mAddr[string(k)]; num == fNum {
|
|
|
|
// otherwise object does not match current or some previous filter
|
|
|
|
mAddr[string(k)] = fNum + 1
|
|
|
|
}
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-11-09 14:58:22 +00:00
|
|
|
return nil
|
|
|
|
})
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "(%T) could not iterate bucket %s", db, key)
|
|
|
|
}
|
|
|
|
}
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
fLen := len(fs)
|
|
|
|
res := make([]*object.Address, 0, len(mAddr))
|
2020-10-29 11:38:50 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
for a, ind := range mAddr {
|
|
|
|
if ind != fLen {
|
|
|
|
continue
|
|
|
|
}
|
2020-11-03 12:12:56 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
// check if object marked as deleted
|
|
|
|
if objectRemoved(tx, []byte(a)) {
|
|
|
|
continue
|
|
|
|
}
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
addr := object.NewAddress()
|
|
|
|
if err := addr.Parse(a); err != nil {
|
|
|
|
// TODO: storage was broken, so we need to handle it
|
|
|
|
return nil, err
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
res = append(res, addr)
|
|
|
|
}
|
2020-11-03 14:24:23 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
return res, nil
|
|
|
|
}
|
2020-11-03 13:12:34 +00:00
|
|
|
|
2020-11-06 09:41:59 +00:00
|
|
|
func (db *DB) selectAll(tx *bbolt.Tx) ([]*object.Address, error) {
|
2020-11-10 12:55:54 +00:00
|
|
|
result := map[string]struct{}{}
|
2020-10-29 12:03:55 +00:00
|
|
|
|
2020-11-10 12:55:54 +00:00
|
|
|
primaryBucket := tx.Bucket(primaryBucket)
|
|
|
|
indexBucket := tx.Bucket(indexBucket)
|
|
|
|
|
|
|
|
if primaryBucket == nil || indexBucket == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := primaryBucket.ForEach(func(k, _ []byte) error {
|
|
|
|
result[string(k)] = struct{}{}
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-11-10 12:55:54 +00:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "(%T) could not iterate primary bucket", db)
|
2020-11-06 09:41:59 +00:00
|
|
|
}
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-11-10 12:55:54 +00:00
|
|
|
rootBucket := indexBucket.Bucket([]byte(v2object.FilterPropertyRoot))
|
|
|
|
if rootBucket != nil {
|
2020-11-13 14:20:58 +00:00
|
|
|
rootBucket = rootBucket.Bucket(nonEmptyKeyBytes(nil))
|
2020-11-10 12:55:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if rootBucket != nil {
|
|
|
|
if err := rootBucket.ForEach(func(k, v []byte) error {
|
|
|
|
result[string(k)] = struct{}{}
|
2020-11-06 09:41:59 +00:00
|
|
|
|
2020-11-10 12:55:54 +00:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "(%T) could not iterate root bucket", db)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
list := make([]*object.Address, 0, len(result))
|
|
|
|
|
|
|
|
for k := range result {
|
|
|
|
// check if object marked as deleted
|
|
|
|
if objectRemoved(tx, []byte(k)) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
addr := object.NewAddress()
|
|
|
|
if err := addr.Parse(k); err != nil {
|
|
|
|
return nil, err // TODO: storage was broken, so we need to handle it
|
|
|
|
}
|
2020-11-06 09:41:59 +00:00
|
|
|
|
2020-11-10 12:55:54 +00:00
|
|
|
list = append(list, addr)
|
2020-11-06 09:41:59 +00:00
|
|
|
}
|
2020-10-28 14:49:30 +00:00
|
|
|
|
2020-11-10 12:55:54 +00:00
|
|
|
return list, nil
|
2020-10-28 14:49:30 +00:00
|
|
|
}
|