@@ -1932,7 +1932,17 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
1932
1932
if err := fs .FromProtoMessage (body .Filters ); err != nil {
1933
1933
return nil , nil , fmt .Errorf ("invalid filters: %w" , err )
1934
1934
}
1935
- ofs , cursor , err := objectcore .PreprocessSearchQuery (fs , body .Attributes , body .Cursor )
1935
+
1936
+ filteredAttributeless := len (body .Attributes ) == 0 && len (body .Filters ) > 0
1937
+ attrs := body .Attributes
1938
+ if filteredAttributeless {
1939
+ // 1st attribute is required for merge function to provide proper paging. This
1940
+ // requires collecting attribute values from other nodes. Therefore, if the
1941
+ // attribute is not requested, it is forcefully returned for local queries, and
1942
+ // will end up in resulting cursor.
1943
+ attrs = []string {body .Filters [0 ].Key }
1944
+ }
1945
+ ofs , cursor , err := objectcore .PreprocessSearchQuery (fs , attrs , body .Cursor )
1936
1946
if err != nil {
1937
1947
if errors .Is (err , objectcore .ErrUnreachableQuery ) {
1938
1948
return nil , nil , nil
@@ -1961,11 +1971,11 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
1961
1971
count := uint16 (body .Count ) // legit according to the limit
1962
1972
switch {
1963
1973
case ttl == 1 :
1964
- if res , newCursor , err = s .storage .SearchObjects (cID , ofs , body . Attributes , cursor , count ); err != nil {
1974
+ if res , newCursor , err = s .storage .SearchObjects (cID , ofs , attrs , cursor , count ); err != nil {
1965
1975
return nil , nil , err
1966
1976
}
1967
1977
case handleWithMetaService :
1968
- res , newCursor , err = s .meta .Search (cID , ofs , body . Attributes , cursor , count )
1978
+ res , newCursor , err = s .meta .Search (cID , ofs , attrs , cursor , count )
1969
1979
if err != nil {
1970
1980
return nil , nil , err
1971
1981
}
@@ -1993,7 +2003,7 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
1993
2003
wg .Add (1 )
1994
2004
go func () {
1995
2005
defer wg .Done ()
1996
- if set , crsr , err := s .storage .SearchObjects (cID , ofs , body . Attributes , cursor , count ); err == nil {
2006
+ if set , crsr , err := s .storage .SearchObjects (cID , ofs , attrs , cursor , count ); err == nil {
1997
2007
add (set , crsr != nil )
1998
2008
} // TODO: else log error
1999
2009
}()
@@ -2029,7 +2039,7 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
2029
2039
firstAttr string
2030
2040
firstFilter * object.SearchFilter
2031
2041
)
2032
- if len (body . Attributes ) > 0 {
2042
+ if len (attrs ) > 0 {
2033
2043
firstAttr = fs [0 ].Header ()
2034
2044
firstFilter = & ofs [0 ].SearchFilter
2035
2045
}
@@ -2039,12 +2049,23 @@ func (s *Server) ProcessSearch(ctx context.Context, req *protoobject.SearchV2Req
2039
2049
return nil , nil , fmt .Errorf ("merge results from container nodes: %w" , err )
2040
2050
}
2041
2051
if more {
2052
+ if filteredAttributeless && body .Filters [0 ].MatchType == protoobject .MatchType_STRING_EQUAL {
2053
+ // temporary add the only possible value just to calculate the cursor. Condition below reverts it.
2054
+ res [len (res )- 1 ].Attributes = []string {body .Filters [0 ].Value }
2055
+ }
2042
2056
if newCursor , err = objectcore .CalculateCursor (firstFilter , res [len (res )- 1 ]); err != nil {
2043
2057
return nil , nil , fmt .Errorf ("recalculate cursor: %w" , err )
2044
2058
}
2045
2059
}
2046
2060
}
2047
2061
2062
+ if filteredAttributeless && (ttl != 1 || body .Filters [0 ].MatchType == protoobject .MatchType_STRING_EQUAL ) {
2063
+ // for K=V queries, V is unambiguous, so there is no need to transmit it in each item
2064
+ for i := range res {
2065
+ res [i ].Attributes = nil
2066
+ }
2067
+ }
2068
+
2048
2069
return res , newCursor , nil
2049
2070
}
2050
2071
@@ -2116,13 +2137,14 @@ func searchOnRemoteAddress(ctx context.Context, conn *grpc.ClientConn, nodePub [
2116
2137
// TODO: we can theoretically do without type conversion, thus avoiding
2117
2138
// additional allocation. At the same time, this will require generic code for merging.
2118
2139
res := make ([]sdkclient.SearchResultItem , n )
2140
+ filteredAttributeless := len (req .Body .Attributes ) == 0 && len (req .Body .Filters ) > 0
2119
2141
for i , r := range resp .Body .Result {
2120
2142
switch {
2121
2143
case r == nil :
2122
2144
return nil , false , fmt .Errorf ("invalid response body: nil element #%d" , i )
2123
2145
case r .Id == nil :
2124
2146
return nil , false , fmt .Errorf ("invalid response body: invalid element #%d: missing ID" , i )
2125
- case len (r .Attributes ) != len (req .Body .Attributes ):
2147
+ case ! filteredAttributeless && len (r .Attributes ) != len (req .Body .Attributes ) || filteredAttributeless && len ( r . Attributes ) > 1 :
2126
2148
return nil , false , fmt .Errorf ("invalid response body: invalid element #%d: wrong attribute count %d" , i , len (r .Attributes ))
2127
2149
}
2128
2150
if err := res [i ].ID .FromProtoMessage (r .Id ); err != nil {
0 commit comments