@@ -3,7 +3,10 @@ use serde_json::Value;
33use crate :: aggregation:: agg_req:: { Aggregation , Aggregations } ;
44use crate :: aggregation:: agg_result:: AggregationResults ;
55use crate :: aggregation:: collector:: AggregationCollector ;
6- use crate :: aggregation:: intermediate_agg_result:: IntermediateAggregationResults ;
6+ use crate :: aggregation:: intermediate_agg_result:: {
7+ IntermediateAggregationResult , IntermediateAggregationResults , IntermediateBucketResult ,
8+ IntermediateKey ,
9+ } ;
710use crate :: aggregation:: tests:: { get_test_index_2_segments, get_test_index_from_values_and_terms} ;
811use crate :: aggregation:: DistributedAggregationCollector ;
912use crate :: docset:: COLLECT_BLOCK_BUFFER_LEN ;
@@ -1479,3 +1482,132 @@ fn test_aggregation_field_validation_helper() {
14791482 crate :: aggregation:: agg_req:: validate_aggregation_fields_exist ( & agg_req, segment_reader) ;
14801483 assert ! ( result. is_ok( ) ) ;
14811484}
1485+
1486+ // "a" has low scores (p50 ≈ 1.5), "b" has high scores (p50 ≈ 99.5).
1487+ // With order by p50 desc and segment_size=1, the segment collector should retain only "b".
1488+ #[ test]
1489+ fn test_percentile_order_segment_level ( ) -> crate :: Result < ( ) > {
1490+ let index = get_test_index_from_values_and_terms (
1491+ false ,
1492+ & [ vec ! [
1493+ ( 1.0 , "a" . to_string( ) ) ,
1494+ ( 2.0 , "a" . to_string( ) ) ,
1495+ ( 99.0 , "b" . to_string( ) ) ,
1496+ ( 100.0 , "b" . to_string( ) ) ,
1497+ ] ] ,
1498+ ) ?;
1499+
1500+ let agg_req: Aggregations = serde_json:: from_value ( serde_json:: json!( {
1501+ "my_terms" : {
1502+ "terms" : {
1503+ "field" : "string_id" ,
1504+ "size" : 1 ,
1505+ "segment_size" : 1 ,
1506+ "order" : { "my_pct.50" : "desc" }
1507+ } ,
1508+ "aggs" : {
1509+ "my_pct" : { "percentiles" : { "field" : "score_f64" , "percents" : [ 50 ] } }
1510+ }
1511+ }
1512+ } ) )
1513+ . unwrap ( ) ;
1514+
1515+ let collector = DistributedAggregationCollector :: from_aggs ( agg_req, Default :: default ( ) ) ;
1516+ let reader = index. reader ( ) ?;
1517+ let intermediate = reader. searcher ( ) . search ( & AllQuery , & collector) ?;
1518+
1519+ let IntermediateAggregationResult :: Bucket ( IntermediateBucketResult :: Terms { buckets } ) =
1520+ intermediate. aggs_res . get ( "my_terms" ) . unwrap ( )
1521+ else {
1522+ panic ! ( "expected terms bucket" ) ;
1523+ } ;
1524+ assert_eq ! (
1525+ buckets. entries. len( ) ,
1526+ 1 ,
1527+ "segment_size=1 should retain only one bucket"
1528+ ) ;
1529+ assert ! (
1530+ buckets
1531+ . entries
1532+ . contains_key( & IntermediateKey :: Str ( "b" . to_string( ) ) ) ,
1533+ "\" b\" (higher p50) should survive, not \" a\" "
1534+ ) ;
1535+ assert ! (
1536+ buckets. sum_other_doc_count > 0 ,
1537+ "pruned docs should be accounted for"
1538+ ) ;
1539+
1540+ Ok ( ( ) )
1541+ }
1542+
1543+ // Same setup with two segments. Both terms survive segment-level pruning (segment_size=2).
1544+ // After merging, prune_intermediate_results with size=1 should keep only "b".
1545+ #[ test]
1546+ fn test_percentile_order_prune_intermediate ( ) -> crate :: Result < ( ) > {
1547+ let index = get_test_index_from_values_and_terms (
1548+ false ,
1549+ & [
1550+ vec ! [
1551+ ( 1.0 , "a" . to_string( ) ) ,
1552+ ( 2.0 , "a" . to_string( ) ) ,
1553+ ( 99.0 , "b" . to_string( ) ) ,
1554+ ( 100.0 , "b" . to_string( ) ) ,
1555+ ] ,
1556+ vec ! [ ( 3.0 , "a" . to_string( ) ) , ( 98.0 , "b" . to_string( ) ) ] ,
1557+ ] ,
1558+ ) ?;
1559+
1560+ let agg_req: Aggregations = serde_json:: from_value ( serde_json:: json!( {
1561+ "my_terms" : {
1562+ "terms" : {
1563+ "field" : "string_id" ,
1564+ "size" : 1 ,
1565+ "segment_size" : 2 ,
1566+ "order" : { "my_pct.50" : "desc" }
1567+ } ,
1568+ "aggs" : {
1569+ "my_pct" : { "percentiles" : { "field" : "score_f64" , "percents" : [ 50 ] } }
1570+ }
1571+ }
1572+ } ) )
1573+ . unwrap ( ) ;
1574+
1575+ let collector = DistributedAggregationCollector :: from_aggs ( agg_req. clone ( ) , Default :: default ( ) ) ;
1576+ let reader = index. reader ( ) ?;
1577+ let mut intermediate = reader. searcher ( ) . search ( & AllQuery , & collector) ?;
1578+
1579+ // Both terms should have survived segment-level pruning (segment_size=2).
1580+ {
1581+ let IntermediateAggregationResult :: Bucket ( IntermediateBucketResult :: Terms { buckets } ) =
1582+ intermediate. aggs_res . get ( "my_terms" ) . unwrap ( )
1583+ else {
1584+ panic ! ( "expected terms bucket" ) ;
1585+ } ;
1586+ assert_eq ! (
1587+ buckets. entries. len( ) ,
1588+ 2 ,
1589+ "both terms should survive segment-level pruning"
1590+ ) ;
1591+ }
1592+
1593+ intermediate. prune_intermediate_results ( & agg_req, false ) ?;
1594+
1595+ let IntermediateAggregationResult :: Bucket ( IntermediateBucketResult :: Terms { buckets } ) =
1596+ intermediate. aggs_res . get ( "my_terms" ) . unwrap ( )
1597+ else {
1598+ panic ! ( "expected terms bucket" ) ;
1599+ } ;
1600+ assert_eq ! (
1601+ buckets. entries. len( ) ,
1602+ 1 ,
1603+ "size=1 should retain only one bucket"
1604+ ) ;
1605+ assert ! (
1606+ buckets
1607+ . entries
1608+ . contains_key( & IntermediateKey :: Str ( "b" . to_string( ) ) ) ,
1609+ "\" b\" (higher p50) should survive, not \" a\" "
1610+ ) ;
1611+
1612+ Ok ( ( ) )
1613+ }
0 commit comments