[#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:
Leonard Lyubich 2020-10-01 15:12:39 +03:00 committed by Alex Vanin
parent f89c848e84
commit 51e373c3f0
6 changed files with 85 additions and 113 deletions

View file

@ -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()

View file

@ -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))
} }

View file

@ -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
} }

View file

@ -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
} }

View file

@ -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()))
}) })
} }

View file

@ -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).