@@ -126,14 +126,16 @@ impl SearchResult {
126126
127127 /// Return top-k results by score (descending).
128128 pub fn top_k ( & self , k : usize ) -> Self {
129- if self . row_ids . len ( ) <= k {
130- return self . clone ( ) ;
131- }
129+ // Always sort best-first, even when no truncation is needed (len <= k):
130+ // downstream consumers rely on relevance order, not input/shard order.
132131 let mut indices: Vec < usize > = ( 0 ..self . row_ids . len ( ) ) . collect ( ) ;
132+ // Best-first by score (total_cmp is NaN-safe), then smaller row id. This
133+ // matches vector_search's top_k and keeps the order deterministic regardless
134+ // of the input/shard order.
133135 indices. sort_by ( |& a, & b| {
134136 self . scores [ b]
135- . partial_cmp ( & self . scores [ a] )
136- . unwrap_or ( std :: cmp:: Ordering :: Equal )
137+ . total_cmp ( & self . scores [ a] )
138+ . then_with ( || self . row_ids [ a ] . cmp ( & self . row_ids [ b ] ) )
137139 } ) ;
138140 indices. truncate ( k) ;
139141 let row_ids = indices. iter ( ) . map ( |& i| self . row_ids [ i] ) . collect ( ) ;
@@ -258,8 +260,30 @@ mod tests {
258260 . without_deleted_row_ranges ( Some ( & deleted) )
259261 . unwrap ( )
260262 . top_k ( 10 ) ;
261- assert_eq ! ( filtered. row_ids, vec![ 1 , 4 ] ) ;
262- assert_eq ! ( filtered. scores, vec![ 0.1 , 0.2 ] ) ;
263+ // Rows 2..3 are deleted; the remaining rows come back best-first by score.
264+ assert_eq ! ( filtered. row_ids, vec![ 4 , 1 ] ) ;
265+ assert_eq ! ( filtered. scores, vec![ 0.2 , 0.1 ] ) ;
266+ }
267+
268+ #[ test]
269+ fn test_search_result_top_k_sorts_best_first_without_truncation ( ) {
270+ // k >= candidate count: results must still be best-first by score, not in
271+ // input/shard order.
272+ let result = SearchResult :: new ( vec ! [ 3 , 1 , 2 ] , vec ! [ 0.1 , 0.9 , 0.5 ] ) ;
273+
274+ let top = result. top_k ( 3 ) ;
275+ assert_eq ! ( top. row_ids, vec![ 1 , 2 , 3 ] ) ;
276+ assert_eq ! ( top. scores, vec![ 0.9 , 0.5 , 0.1 ] ) ;
277+ }
278+
279+ #[ test]
280+ fn test_search_result_top_k_tie_breaks_by_smaller_row_id ( ) {
281+ // Equal scores: order by smaller row id, independent of input order.
282+ let result = SearchResult :: new ( vec ! [ 30 , 10 , 20 ] , vec ! [ 0.9 , 0.9 , 0.9 ] ) ;
283+
284+ let top = result. top_k ( 3 ) ;
285+ assert_eq ! ( top. row_ids, vec![ 10 , 20 , 30 ] ) ;
286+ assert_eq ! ( top. scores, vec![ 0.9 , 0.9 , 0.9 ] ) ;
263287 }
264288
265289 #[ test]
0 commit comments