forked from TrueCloudLab/frostfs-node
[#61] object/search: Support latest search filters
Refactor query to match object and its parents in a single call. Support KeyRoot and KeyLeaf filters. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
f89c848e84
commit
51e373c3f0
6 changed files with 85 additions and 113 deletions
|
@ -51,17 +51,14 @@ func (s *localStream) stream(ctx context.Context, ch chan<- []*objectSDK.ID) err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *searchQueryFilter) Pass(ctx context.Context, meta *localstore.ObjectMeta) *localstore.FilterResult {
|
func (f *searchQueryFilter) Pass(ctx context.Context, meta *localstore.ObjectMeta) *localstore.FilterResult {
|
||||||
loop:
|
if obj := meta.Head(); f.cid.Equal(obj.GetContainerID()) {
|
||||||
for obj := meta.Head(); obj != nil; obj = obj.GetParent() {
|
f.query.Match(meta.Head(), func(id *objectSDK.ID) {
|
||||||
if !f.cid.Equal(obj.GetContainerID()) || !f.query.Match(obj) {
|
select {
|
||||||
continue
|
case <-ctx.Done():
|
||||||
}
|
return
|
||||||
|
case f.ch <- []*objectSDK.ID{id}:
|
||||||
select {
|
}
|
||||||
case <-ctx.Done():
|
})
|
||||||
break loop
|
|
||||||
case f.ch <- []*objectSDK.ID{obj.GetID()}:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return localstore.ResultPass()
|
return localstore.ResultPass()
|
||||||
|
|
|
@ -7,5 +7,5 @@ import (
|
||||||
|
|
||||||
type Query interface {
|
type Query interface {
|
||||||
ToSearchFilters() objectSDK.SearchFilters
|
ToSearchFilters() objectSDK.SearchFilters
|
||||||
Match(*object.Object) bool
|
Match(*object.Object, func(*objectSDK.ID))
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,24 +13,24 @@ const keyNoChildrenField = "Object.Header.Split.NoChildren"
|
||||||
|
|
||||||
const keyParentIDField = "Object.Header.Split.Parent"
|
const keyParentIDField = "Object.Header.Split.Parent"
|
||||||
|
|
||||||
func NewEmptyChildrenFilter() *Filter {
|
|
||||||
return NewFilterEqual(keyNoChildrenField, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewParentIDFilter(par *object.ID) *Filter {
|
|
||||||
return NewFilterEqual(keyParentIDField, idValue(par))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRightChildQuery(par *object.ID) query.Query {
|
func NewRightChildQuery(par *object.ID) query.Query {
|
||||||
return New(
|
q := &Query{
|
||||||
NewParentIDFilter(par),
|
filters: make(object.SearchFilters, 0, 2),
|
||||||
NewEmptyChildrenFilter(),
|
}
|
||||||
)
|
|
||||||
|
q.filters.AddFilter(keyParentIDField, idValue(par), object.MatchStringEqual)
|
||||||
|
q.filters.AddFilter(keyNoChildrenField, "", object.MatchStringEqual)
|
||||||
|
|
||||||
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLinkingQuery(par *object.ID) query.Query {
|
func NewLinkingQuery(par *object.ID) query.Query {
|
||||||
return New(
|
q := &Query{
|
||||||
NewParentIDFilter(par),
|
filters: make(object.SearchFilters, 0, 2),
|
||||||
NewFilterEqual(keyParentField, ""),
|
}
|
||||||
)
|
|
||||||
|
q.filters.AddFilter(keyParentIDField, idValue(par), object.MatchStringEqual)
|
||||||
|
q.filters.AddFilter(keyParentField, "", object.MatchStringEqual)
|
||||||
|
|
||||||
|
return q
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package query
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||||
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
@ -12,23 +11,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
filters []*Filter
|
filters objectSDK.SearchFilters
|
||||||
}
|
}
|
||||||
|
|
||||||
type matchType uint8
|
func New(filters objectSDK.SearchFilters) query.Query {
|
||||||
|
|
||||||
type Filter struct {
|
|
||||||
matchType matchType
|
|
||||||
|
|
||||||
key, val string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
_ matchType = iota
|
|
||||||
matchStringEqual
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(filters ...*Filter) query.Query {
|
|
||||||
return &Query{
|
return &Query{
|
||||||
filters: filters,
|
filters: filters,
|
||||||
}
|
}
|
||||||
|
@ -38,47 +24,38 @@ func idValue(id *objectSDK.ID) string {
|
||||||
return hex.EncodeToString(id.ToV2().GetValue())
|
return hex.EncodeToString(id.ToV2().GetValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIDEqualFilter(id *objectSDK.ID) *Filter {
|
|
||||||
return NewFilterEqual(objectSDK.HdrSysNameID, idValue(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
func cidValue(id *container.ID) string {
|
func cidValue(id *container.ID) string {
|
||||||
return hex.EncodeToString(id.ToV2().GetValue())
|
return hex.EncodeToString(id.ToV2().GetValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainerIDEqualFilter(id *container.ID) *Filter {
|
|
||||||
return NewFilterEqual(objectSDK.HdrSysNameCID, cidValue(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ownerIDValue(id *owner.ID) string {
|
func ownerIDValue(id *owner.ID) string {
|
||||||
return hex.EncodeToString(id.ToV2().GetValue())
|
return hex.EncodeToString(id.ToV2().GetValue())
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOwnerIDEqualFilter(id *owner.ID) *Filter {
|
func (q *Query) Match(obj *object.Object, handler func(*objectSDK.ID)) {
|
||||||
return NewFilterEqual(objectSDK.HdrSysNameOwnerID, ownerIDValue(id))
|
for par := (*object.Object)(nil); obj != nil; par, obj = obj, obj.GetParent() {
|
||||||
}
|
match := true
|
||||||
|
|
||||||
func NewFilterEqual(key, val string) *Filter {
|
for i := 0; match && i < len(q.filters); i++ {
|
||||||
return &Filter{
|
switch typ := q.filters[i].Operation(); typ {
|
||||||
matchType: matchStringEqual,
|
default:
|
||||||
key: key,
|
match = false
|
||||||
val: val,
|
case objectSDK.MatchStringEqual:
|
||||||
}
|
switch key := q.filters[i].Header(); key {
|
||||||
}
|
default:
|
||||||
|
match = headerEqual(obj, key, q.filters[i].Value())
|
||||||
func (q *Query) Match(obj *object.Object) bool {
|
case objectSDK.KeyRoot:
|
||||||
for _, f := range q.filters {
|
match = !obj.HasParent()
|
||||||
switch f.matchType {
|
case objectSDK.KeyLeaf:
|
||||||
case matchStringEqual:
|
match = par == nil
|
||||||
if !headerEqual(obj, f.key, f.val) {
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
default:
|
}
|
||||||
panic(fmt.Sprintf("unsupported match type %d", f.matchType))
|
|
||||||
|
if match {
|
||||||
|
handler(obj.GetID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func headerEqual(obj *object.Object, key, value string) bool {
|
func headerEqual(obj *object.Object, key, value string) bool {
|
||||||
|
@ -108,11 +85,5 @@ func headerEqual(obj *object.Object, key, value string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) ToSearchFilters() objectSDK.SearchFilters {
|
func (q *Query) ToSearchFilters() objectSDK.SearchFilters {
|
||||||
fs := make(objectSDK.SearchFilters, 0, len(q.filters))
|
return q.filters
|
||||||
|
|
||||||
for i := range q.filters {
|
|
||||||
fs.AddFilter(q.filters[i].key, q.filters[i].val, objectSDK.MatchStringEqual)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/owner"
|
"github.com/nspcc-dev/neofs-api-go/pkg/owner"
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/core/object"
|
"github.com/nspcc-dev/neofs-node/pkg/core/object"
|
||||||
|
"github.com/nspcc-dev/neofs-node/pkg/services/object/search/query"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,6 +49,14 @@ func testOwnerID(t *testing.T) *owner.ID {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchesQuery(q query.Query, obj *object.Object) (res bool) {
|
||||||
|
q.Match(obj, func(id *objectSDK.ID) {
|
||||||
|
res = id.Equal(obj.GetID())
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func TestQ_Match(t *testing.T) {
|
func TestQ_Match(t *testing.T) {
|
||||||
t.Run("object identifier equal", func(t *testing.T) {
|
t.Run("object identifier equal", func(t *testing.T) {
|
||||||
obj := object.NewRaw()
|
obj := object.NewRaw()
|
||||||
|
@ -55,15 +64,16 @@ func TestQ_Match(t *testing.T) {
|
||||||
id := testID(t)
|
id := testID(t)
|
||||||
obj.SetID(id)
|
obj.SetID(id)
|
||||||
|
|
||||||
q := New(
|
fs := objectSDK.SearchFilters{}
|
||||||
NewIDEqualFilter(id),
|
fs.AddFilter(objectSDK.HdrSysNameID, idValue(id), objectSDK.MatchStringEqual)
|
||||||
)
|
|
||||||
|
|
||||||
require.True(t, q.Match(obj.Object()))
|
q := New(fs)
|
||||||
|
|
||||||
|
require.True(t, matchesQuery(q, obj.Object()))
|
||||||
|
|
||||||
obj.SetID(testID(t))
|
obj.SetID(testID(t))
|
||||||
|
|
||||||
require.False(t, q.Match(obj.Object()))
|
require.False(t, matchesQuery(q, obj.Object()))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("container identifier equal", func(t *testing.T) {
|
t.Run("container identifier equal", func(t *testing.T) {
|
||||||
|
@ -72,15 +82,16 @@ func TestQ_Match(t *testing.T) {
|
||||||
id := testCID(t)
|
id := testCID(t)
|
||||||
obj.SetContainerID(id)
|
obj.SetContainerID(id)
|
||||||
|
|
||||||
q := New(
|
fs := objectSDK.SearchFilters{}
|
||||||
NewContainerIDEqualFilter(id),
|
fs.AddFilter(objectSDK.HdrSysNameCID, cidValue(id), objectSDK.MatchStringEqual)
|
||||||
)
|
|
||||||
|
|
||||||
require.True(t, q.Match(obj.Object()))
|
q := New(fs)
|
||||||
|
|
||||||
|
require.True(t, matchesQuery(q, obj.Object()))
|
||||||
|
|
||||||
obj.SetContainerID(testCID(t))
|
obj.SetContainerID(testCID(t))
|
||||||
|
|
||||||
require.False(t, q.Match(obj.Object()))
|
require.False(t, matchesQuery(q, obj.Object()))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("owner identifier equal", func(t *testing.T) {
|
t.Run("owner identifier equal", func(t *testing.T) {
|
||||||
|
@ -89,15 +100,16 @@ func TestQ_Match(t *testing.T) {
|
||||||
id := testOwnerID(t)
|
id := testOwnerID(t)
|
||||||
obj.SetOwnerID(id)
|
obj.SetOwnerID(id)
|
||||||
|
|
||||||
q := New(
|
fs := objectSDK.SearchFilters{}
|
||||||
NewOwnerIDEqualFilter(id),
|
fs.AddFilter(objectSDK.HdrSysNameOwnerID, ownerIDValue(id), objectSDK.MatchStringEqual)
|
||||||
)
|
|
||||||
|
|
||||||
require.True(t, q.Match(obj.Object()))
|
q := New(fs)
|
||||||
|
|
||||||
|
require.True(t, matchesQuery(q, obj.Object()))
|
||||||
|
|
||||||
obj.SetOwnerID(testOwnerID(t))
|
obj.SetOwnerID(testOwnerID(t))
|
||||||
|
|
||||||
require.False(t, q.Match(obj.Object()))
|
require.False(t, matchesQuery(q, obj.Object()))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("attribute equal", func(t *testing.T) {
|
t.Run("attribute equal", func(t *testing.T) {
|
||||||
|
@ -110,14 +122,15 @@ func TestQ_Match(t *testing.T) {
|
||||||
|
|
||||||
obj.SetAttributes(a)
|
obj.SetAttributes(a)
|
||||||
|
|
||||||
q := New(
|
fs := objectSDK.SearchFilters{}
|
||||||
NewFilterEqual(k, v),
|
fs.AddFilter(k, v, objectSDK.MatchStringEqual)
|
||||||
)
|
|
||||||
|
|
||||||
require.True(t, q.Match(obj.Object()))
|
q := New(fs)
|
||||||
|
|
||||||
|
require.True(t, matchesQuery(q, obj.Object()))
|
||||||
|
|
||||||
a.SetKey(k + "1")
|
a.SetKey(k + "1")
|
||||||
|
|
||||||
require.False(t, q.Match(obj.Object()))
|
require.False(t, matchesQuery(q, obj.Object()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package searchsvc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||||
|
objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/object"
|
"github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
searchsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/search"
|
searchsvc "github.com/nspcc-dev/neofs-node/pkg/services/object/search"
|
||||||
|
@ -18,19 +19,9 @@ func toPrm(body *object.SearchRequestBody, req *object.SearchRequest) (*searchsv
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("unsupported query version #%d", v)
|
return nil, errors.Errorf("unsupported query version #%d", v)
|
||||||
case 1:
|
case 1:
|
||||||
fs := body.GetFilters()
|
q = queryV1.New(
|
||||||
fsV1 := make([]*queryV1.Filter, 0, len(fs))
|
objectSDK.NewSearchFiltersFromV2(body.GetFilters()),
|
||||||
|
)
|
||||||
for _, f := range fs {
|
|
||||||
switch mt := f.GetMatchType(); mt {
|
|
||||||
default:
|
|
||||||
return nil, errors.Errorf("unsupported match type %d in query version #%d", mt, v)
|
|
||||||
case object.MatchStringEqual:
|
|
||||||
fsV1 = append(fsV1, queryV1.NewFilterEqual(f.GetName(), f.GetValue()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
q = queryV1.New(fsV1...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(searchsvc.Prm).
|
return new(searchsvc.Prm).
|
||||||
|
|
Loading…
Reference in a new issue