From b6c77262abadaee04f8c6bd262c296dff7100e38 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 10 Apr 2026 17:48:39 +0300 Subject: [PATCH 1/2] object: return proper errors for incorrect int filters in SearchV2 Turns out, V1 search (deleted in e4a034ceb33567d821df84c67b126e5cb23ceb0f) had this check and we have some tests for it. V2 also has some of it internally, but this was hidden by unreachable query error (returning nothing instead of an error). Signed-off-by: Roman Khimov --- CHANGELOG.md | 1 + pkg/core/object/metadata.go | 26 ++++++++++++++++---------- pkg/services/object/server.go | 4 +++- pkg/util/meta/test/metatest.go | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa7f8847fd..44f771cc32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Changelog for NeoFS Node - Policer removes redundant local shard copies that could remain on disk forever (#3908) - Treat only HALTed main transactions as successfully executed, retry the rest (#3868) - `different object owner and session issuer` for stored objects (#3929) +- SearchV2 method returning zero result instead of "bad request" error for incorrect numeric filters (#3934) ### Changed - `object search` CLI command is now the same as `object searchv2` (#3931) diff --git a/pkg/core/object/metadata.go b/pkg/core/object/metadata.go index 06ae994a25..829b254628 100644 --- a/pkg/core/object/metadata.go +++ b/pkg/core/object/metadata.go @@ -709,9 +709,9 @@ func PreprocessSearchQuery(fs object.SearchFilters, attrs []string, cursor strin if blindlyProcess(fs) { return nil, nil, ErrUnreachableQuery } - ofs, ok := parseIntFilters(fs) - if !ok { - return nil, nil, ErrUnreachableQuery + ofs, err := parseIntFilters(fs) + if err != nil { + return nil, nil, err } if oidSorted { @@ -773,7 +773,7 @@ func blindlyProcess(fs object.SearchFilters) bool { return false } -func parseIntFilters(fs object.SearchFilters) ([]SearchFilter, bool) { +func parseIntFilters(fs object.SearchFilters) ([]SearchFilter, error) { ofs := make([]SearchFilter, len(fs)) for i := range fs { ofs[i].SearchFilter = fs[i] @@ -784,16 +784,22 @@ func parseIntFilters(fs object.SearchFilters) ([]SearchFilter, bool) { } n, ok := new(big.Int).SetString(val, 10) if !ok { - return nil, false + return nil, fmt.Errorf("non-integer value in numeric filter number %d", i) } if c := n.Cmp(maxUint256); c >= 0 { - if c > 0 || m == object.MatchNumGT { - return nil, false + if c > 0 { + return nil, fmt.Errorf("too big integer in numeric filter number %d", i) + } + if m == object.MatchNumGT { + return nil, ErrUnreachableQuery } ofs[i].AutoMatch = m == object.MatchNumLE } else if c = n.Cmp(maxUint256Neg); c <= 0 { - if c < 0 || m == object.MatchNumLT { - return nil, false + if c < 0 { + return nil, fmt.Errorf("too low integer in numeric filter number %d", i) + } + if m == object.MatchNumLT { + return nil, ErrUnreachableQuery } ofs[i].AutoMatch = m == object.MatchNumGE } @@ -805,7 +811,7 @@ func parseIntFilters(fs object.SearchFilters) ([]SearchFilter, bool) { } // TODO: #1148 there are more auto-cases (like <=X AND >=X, X), cover more here } - return ofs, true + return ofs, nil } // BigIntBytes returns integer's raw representation. Int must belong to diff --git a/pkg/services/object/server.go b/pkg/services/object/server.go index 170e1450ff..e3b19d7328 100644 --- a/pkg/services/object/server.go +++ b/pkg/services/object/server.go @@ -2049,7 +2049,9 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req if errors.Is(err, objectcore.ErrUnreachableQuery) { return nil, nil, nil } - return nil, nil, err + var bad = new(apistatus.BadRequest) + bad.SetMessage(err.Error()) + return nil, nil, bad } var cID cid.ID diff --git a/pkg/util/meta/test/metatest.go b/pkg/util/meta/test/metatest.go index 18eaffe506..2cbc5ede73 100644 --- a/pkg/util/meta/test/metatest.go +++ b/pkg/util/meta/test/metatest.go @@ -1057,7 +1057,7 @@ func _assertSearchResultWithLimit(t testing.TB, db DB, cnr cid.ID, fs object.Sea ofs, cursor, err := objectcore.PreprocessSearchQuery(fs, attrs, strCursor) if err != nil { if len(all) == 0 { - require.ErrorIs(t, err, objectcore.ErrUnreachableQuery) + require.Error(t, err) } else { require.NoError(t, err) } From d242cabaf111bcf9095713683655cac60cb33e87 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 10 Apr 2026 17:50:56 +0300 Subject: [PATCH 2/2] acl: drop obsolete SearchRequestToInfo Unused since e4a034ceb33567d821df84c67b126e5cb23ceb0f. Signed-off-by: Roman Khimov --- pkg/services/object/acl/v2/service.go | 14 -------------- pkg/services/object/acl/v2/service_test.go | 17 ----------------- pkg/services/object/server.go | 1 - 3 files changed, 32 deletions(-) diff --git a/pkg/services/object/acl/v2/service.go b/pkg/services/object/acl/v2/service.go index a1dbb2365c..1b95553033 100644 --- a/pkg/services/object/acl/v2/service.go +++ b/pkg/services/object/acl/v2/service.go @@ -463,23 +463,9 @@ func (b Service) HeadRequestToInfo(request *protoobject.HeadRequest) (RequestInf return b.findRequestInfo(request, cnr, acl.OpObjectHead, sessionSDK.VerbObjectHead, sessionv2.VerbObjectHead, *obj) } -// SearchRequestToInfo resolves RequestInfo from the request to check it using -// [ACLChecker]. -func (b Service) SearchRequestToInfo(request *protoobject.SearchRequest) (RequestInfo, error) { - return b.searchRequestToInfo(request) -} - // SearchV2RequestToInfo resolves RequestInfo from the request to check it using // [ACLChecker]. func (b Service) SearchV2RequestToInfo(request *protoobject.SearchV2Request) (RequestInfo, error) { - return b.searchRequestToInfo(request) -} - -// unifies V1 and V2 search request processing. -func (b Service) searchRequestToInfo(request interface { - GetMetaHeader() *protosession.RequestMetaHeader - GetVerifyHeader() *protosession.RequestVerificationHeader -}) (RequestInfo, error) { id, err := getContainerIDFromRequest(request) if err != nil { return RequestInfo{}, err diff --git a/pkg/services/object/acl/v2/service_test.go b/pkg/services/object/acl/v2/service_test.go index 65e55ca535..a5842ac2c7 100644 --- a/pkg/services/object/acl/v2/service_test.go +++ b/pkg/services/object/acl/v2/service_test.go @@ -198,23 +198,6 @@ func TestService_GetRequestToInfo_BearerTokenIssuer(t *testing.T) { }) } -func TestService_SearchRequestToInfo_BearerTokenIssuer(t *testing.T) { - testBearerTokenIssuer(t, (*aclsvc.Service).SearchRequestToInfo, func(t *testing.T, signer neofscrypto.Signer, cnrID cid.ID, meta *protosession.RequestMetaHeader) *protoobject.SearchRequest { - req := &protoobject.SearchRequest{ - Body: &protoobject.SearchRequest_Body{ - ContainerId: cnrID.ProtoMessage(), - }, - MetaHeader: meta, - } - - var err error - req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer(signer, req, nil) - require.NoError(t, err) - - return req - }) -} - func TestService_SearchV2RequestToInfo_BearerTokenIssuer(t *testing.T) { testBearerTokenIssuer(t, (*aclsvc.Service).SearchV2RequestToInfo, func(t *testing.T, signer neofscrypto.Signer, cnrID cid.ID, meta *protosession.RequestMetaHeader) *protoobject.SearchV2Request { req := &protoobject.SearchV2Request{ diff --git a/pkg/services/object/server.go b/pkg/services/object/server.go index e3b19d7328..cc3da1577d 100644 --- a/pkg/services/object/server.go +++ b/pkg/services/object/server.go @@ -159,7 +159,6 @@ type ACLInfoExtractor interface { HashRequestToInfo(*protoobject.GetRangeHashRequest) (aclsvc.RequestInfo, error) GetRequestToInfo(*protoobject.GetRequest) (aclsvc.RequestInfo, error) RangeRequestToInfo(*protoobject.GetRangeRequest) (aclsvc.RequestInfo, error) - SearchRequestToInfo(*protoobject.SearchRequest) (aclsvc.RequestInfo, error) SearchV2RequestToInfo(*protoobject.SearchV2Request) (aclsvc.RequestInfo, error) }