Skip to content

Commit

Permalink
Some adjustments of SearchV2 behavior after initial implementation (#682
Browse files Browse the repository at this point in the history
)
  • Loading branch information
cthulhu-rider authored Feb 25, 2025
2 parents bf4a81e + 2e75853 commit 5afd886
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 11 deletions.
39 changes: 34 additions & 5 deletions client/object_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"io"
"slices"
"time"

"github.com/nspcc-dev/neofs-sdk-go/bearer"
Expand Down Expand Up @@ -77,7 +76,10 @@ func (x SearchObjectsOptions) Count() uint32 { return x.count }
// operation from. To start the search anew, pass an empty cursor.
//
// Max number of filters is 8. Max number of attributes is 8. If attributes are
// specified, filters must include the 1st of them.
// specified, the 1st filter must be about it. Neither filters nor
// attributes can contain [object.FilterContainerID] or [object.FilterID].
// Filters by [object.FilterRoot] and [object.FilterPhysical] properties must
// have zero value and matcher.
//
// Note that if requested attribute is missing in the matching object,
// corresponding element in its [SearchResultItem.Attributes] is empty.
Expand Down Expand Up @@ -109,9 +111,13 @@ func (c *Client) SearchObjects(ctx context.Context, cnr cid.ID, filters object.S
return nil, "", err
}
for i := range attrs {
if attrs[i] == "" {
switch attrs[i] {
case "":
err = fmt.Errorf("empty attribute #%d", i)
return nil, "", err
case object.FilterContainerID, object.FilterID:
err = fmt.Errorf("prohibited attribute %s", attrs[i])
return nil, "", err
}
for j := i + 1; j < len(attrs); j++ {
if attrs[i] == attrs[j] {
Expand All @@ -120,8 +126,14 @@ func (c *Client) SearchObjects(ctx context.Context, cnr cid.ID, filters object.S
}
}
}
if !slices.ContainsFunc(filters, func(f object.SearchFilter) bool { return f.Header() == attrs[0] }) {
err = fmt.Errorf("attribute %q is requested but not filtered", attrs[0])
if len(filters) == 0 || filters[0].Header() != attrs[0] {
err = fmt.Errorf("1st attribute %q is requested but not filtered 1st", attrs[0])
return nil, "", err
}
}
for i := range filters {
if err = verifySearchFilter(filters[i]); err != nil {
err = fmt.Errorf("invalid filter #%d: %w", i, err)
return nil, "", err
}
}
Expand Down Expand Up @@ -237,6 +249,23 @@ func (c *Client) SearchObjects(ctx context.Context, cnr cid.ID, filters object.S
return res, resp.Body.Cursor, nil
}

func verifySearchFilter(f object.SearchFilter) error {
switch attr := f.Header(); attr {
case "":
return errors.New("missing attribute")
case object.FilterContainerID, object.FilterID:
return fmt.Errorf("prohibited attribute %s", attr)
case object.FilterRoot, object.FilterPhysical:
if m := f.Operation(); m != 0 {
return fmt.Errorf("non-zero matcher %s for attribute %s", m, attr)
}
if val := f.Value(); val != "" {
return fmt.Errorf("value for attribute %s is prohibited", attr)
}
}
return nil
}

// PrmObjectSearch groups optional parameters of ObjectSearch operation.
type PrmObjectSearch struct {
sessionContainer
Expand Down
49 changes: 45 additions & 4 deletions client/object_search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1069,12 +1069,51 @@ func TestClient_SearchObjects(t *testing.T) {
t.Run("missing 1st requested attribute", func(t *testing.T) {
as := []string{"a1", "a2", "a3"}
var fs object.SearchFilters
for i := range as[1:] {
fs.AddFilter(as[i+1], "any_val", 100)
for i := range as {
fs.AddFilter(as[i], "any_val", 100)
}

_, _, err := okConn.SearchObjects(ctx, anyCID, fs, as, anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, `attribute "a1" is requested but not filtered`)
_, _, err := okConn.SearchObjects(ctx, anyCID, fs, as[1:], anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, `1st attribute "a2" is requested but not filtered 1st`)
_, _, err = okConn.SearchObjects(ctx, anyCID, nil, as[2:], anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, `1st attribute "a3" is requested but not filtered 1st`)
})
t.Run("prohibited", func(t *testing.T) {
var fs object.SearchFilters
fs.AddFilter("attr", "val", object.MatchStringEqual)
fs.AddObjectContainerIDFilter(object.SearchMatchType(rand.Int31()), cidtest.ID())
_, _, err := okConn.SearchObjects(ctx, anyCID, fs, []string{"attr"}, anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, "invalid filter #1: prohibited attribute $Object:containerID")
fs = fs[:1]
fs.AddObjectIDFilter(object.SearchMatchType(rand.Int31()), oidtest.ID())
_, _, err = okConn.SearchObjects(ctx, anyCID, fs, []string{"attr"}, anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, "invalid filter #1: prohibited attribute $Object:objectID")
})
t.Run("empty", func(t *testing.T) {
var fs object.SearchFilters
fs.AddFilter("attr", "val", object.MatchStringEqual)
fs.AddFilter("", "val", object.SearchMatchType(rand.Int31()))
_, _, err := okConn.SearchObjects(ctx, anyCID, fs, []string{"attr"}, anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, "invalid filter #1: missing attribute")
})
t.Run("key-only", func(t *testing.T) {
var fs object.SearchFilters
fs.AddFilter("attr", "val", object.MatchStringEqual)
fs.AddFilter(object.FilterRoot, "", 123)
_, _, err := okConn.SearchObjects(ctx, anyCID, fs, []string{"attr"}, anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, "invalid filter #1: non-zero matcher 123 for attribute $Object:ROOT")
fs = fs[:1]
fs.AddFilter(object.FilterRoot, "val", 0)
_, _, err = okConn.SearchObjects(ctx, anyCID, fs, []string{"attr"}, anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, "invalid filter #1: value for attribute $Object:ROOT is prohibited")
fs = fs[:1]
fs.AddFilter(object.FilterPhysical, "", 123)
_, _, err = okConn.SearchObjects(ctx, anyCID, fs, []string{"attr"}, anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, "invalid filter #1: non-zero matcher 123 for attribute $Object:PHY")
fs = fs[:1]
fs.AddFilter(object.FilterPhysical, "val", 0)
_, _, err = okConn.SearchObjects(ctx, anyCID, fs, []string{"attr"}, anyRequestCursor, anyValidSigner, anyValidOpts)
require.EqualError(t, err, "invalid filter #1: value for attribute $Object:PHY is prohibited")
})
})
t.Run("attributes", func(t *testing.T) {
Expand All @@ -1085,6 +1124,8 @@ func TestClient_SearchObjects(t *testing.T) {
{name: "empty", err: "empty attribute #1", as: []string{"a1", "", "a3"}},
{name: "duplicated", err: `duplicated attribute "a2"`, as: []string{"a1", "a2", "a3", "a2"}},
{name: "limit exceeded", err: "more than 8 attributes", as: []string{"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9"}},
{name: "prohibited/CID", err: "prohibited attribute $Object:containerID", as: []string{object.FilterContainerID}},
{name: "prohibited/OID", err: "prohibited attribute $Object:objectID", as: []string{object.FilterID}},
} {
t.Run(tc.name, func(t *testing.T) {
_, _, err := okConn.SearchObjects(ctx, anyCID, anyValidFilters, tc.as, anyRequestCursor, anyValidSigner, anyValidOpts)
Expand Down
6 changes: 4 additions & 2 deletions object/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ type SearchFilters []SearchFilter
const (
reservedFilterPrefix = "$Object:"
FilterVersion = reservedFilterPrefix + "version"
FilterID = reservedFilterPrefix + "objectID"
FilterContainerID = reservedFilterPrefix + "containerID"
FilterID = reservedFilterPrefix + "objectID" // Deprecated.
FilterContainerID = reservedFilterPrefix + "containerID" // Deprecated.
FilterOwnerID = reservedFilterPrefix + "ownerID"
FilterPayloadChecksum = reservedFilterPrefix + "payloadHash"
FilterType = reservedFilterPrefix + "objectType"
Expand Down Expand Up @@ -252,6 +252,7 @@ func (f *SearchFilters) AddObjectVersionFilter(op SearchMatchType, v version.Ver
// AddObjectContainerIDFilter adds a filter by container id.
//
// The m must not be numeric (like [MatchNumGT]).
// Deprecated: must not be used.
func (f *SearchFilters) AddObjectContainerIDFilter(m SearchMatchType, id cid.ID) {
f.addFilter(m, FilterContainerID, id.EncodeToString())
}
Expand Down Expand Up @@ -321,6 +322,7 @@ func (f *SearchFilters) AddParentIDFilter(m SearchMatchType, id oid.ID) {
// AddObjectIDFilter adds filter by object identifier.
//
// The m must not be numeric (like [MatchNumGT]).
// Deprecated: must not be used.
func (f *SearchFilters) AddObjectIDFilter(m SearchMatchType, id oid.ID) {
f.addFilter(m, FilterID, id.EncodeToString())
}
Expand Down

0 comments on commit 5afd886

Please sign in to comment.