@@ -834,3 +834,138 @@ fn t_not_multiple_of_data_ingestion_interval_returns_planner_error() {
834834 . generate ( ) ;
835835 assert ! ( matches!( result, Err ( ControllerError :: PlannerError ( _) ) ) ) ;
836836}
837+
838+ // ── SQL top-k (CountMinSketchWithHeap) ────────────────────────────────────────
839+
840+ /// COUNT … ORDER BY <alias> DESC LIMIT k → single heap-only sketch.
841+ #[ test]
842+ fn spatial_count_topk_heap ( ) {
843+ let q = "SELECT srcip, COUNT(pkt_len) AS transfer_events FROM netflow_table \
844+ WHERE time BETWEEN DATEADD(s, -11, NOW()) AND DATEADD(s, -10, NOW()) \
845+ GROUP BY srcip ORDER BY transfer_events DESC LIMIT 10";
846+ let out = SQLController :: from_yaml ( & netflow_one_query_config ( q, 1 ) , sql_opts_1s_ingest ( ) )
847+ . unwrap ( )
848+ . generate ( )
849+ . unwrap ( ) ;
850+
851+ assert_eq ! ( out. streaming_aggregation_count( ) , 1 ) ;
852+ assert_eq ! ( out. inference_query_count( ) , 1 ) ;
853+ assert ! ( out. has_aggregation_type( "CountMinSketchWithHeap" ) ) ;
854+ assert ! ( out. has_aggregation_type_and_sub_type( "CountMinSketchWithHeap" , "topk" ) ) ;
855+ assert ! ( !out. has_aggregation_type( "DeltaSetAggregator" ) ) ;
856+ assert ! ( !out. has_aggregation_type( "CountMinSketch" ) ) ;
857+ assert ! ( out. all_tumbling_window_sizes_eq( 1 ) ) ;
858+ assert_eq ! (
859+ out. aggregation_labels( "CountMinSketchWithHeap" , "grouping" ) ,
860+ Vec :: <String >:: new( )
861+ ) ;
862+ assert_eq ! (
863+ out. aggregation_labels( "CountMinSketchWithHeap" , "aggregated" ) ,
864+ vec![ "srcip" . to_string( ) ]
865+ ) ;
866+ let mut rollup = out. aggregation_labels ( "CountMinSketchWithHeap" , "rollup" ) ;
867+ rollup. sort ( ) ;
868+ assert_eq ! ( rollup, vec![ "dstip" . to_string( ) , "proto" . to_string( ) ] ) ;
869+ assert_eq ! (
870+ out. aggregation_parameter( "CountMinSketchWithHeap" , "heapsize" )
871+ . and_then( |v| v. as_u64( ) ) ,
872+ Some ( 40 )
873+ ) ;
874+ assert_eq ! (
875+ out. aggregation_parameter( "CountMinSketchWithHeap" , "count_events" )
876+ . and_then( |v| v. as_bool( ) ) ,
877+ Some ( true )
878+ ) ;
879+ }
880+
881+ /// SUM … ORDER BY <alias> DESC LIMIT k → value-weighted heap sketch.
882+ #[ test]
883+ fn spatial_sum_topk_heap ( ) {
884+ let q = "SELECT srcip, SUM(pkt_len) AS total_bytes FROM netflow_table \
885+ WHERE time BETWEEN DATEADD(s, -11, NOW()) AND DATEADD(s, -10, NOW()) \
886+ GROUP BY srcip ORDER BY total_bytes DESC LIMIT 10";
887+ let out = SQLController :: from_yaml ( & netflow_one_query_config ( q, 1 ) , sql_opts_1s_ingest ( ) )
888+ . unwrap ( )
889+ . generate ( )
890+ . unwrap ( ) ;
891+
892+ assert_eq ! ( out. streaming_aggregation_count( ) , 1 ) ;
893+ assert_eq ! ( out. inference_query_count( ) , 1 ) ;
894+ assert ! ( out. has_aggregation_type( "CountMinSketchWithHeap" ) ) ;
895+ assert ! ( out. has_aggregation_type_and_sub_type( "CountMinSketchWithHeap" , "topk" ) ) ;
896+ assert ! ( !out. has_aggregation_type( "DeltaSetAggregator" ) ) ;
897+ assert ! ( !out. has_aggregation_type( "CountMinSketch" ) ) ;
898+ assert ! ( out. all_tumbling_window_sizes_eq( 1 ) ) ;
899+ assert_eq ! (
900+ out. aggregation_labels( "CountMinSketchWithHeap" , "grouping" ) ,
901+ Vec :: <String >:: new( )
902+ ) ;
903+ assert_eq ! (
904+ out. aggregation_labels( "CountMinSketchWithHeap" , "aggregated" ) ,
905+ vec![ "srcip" . to_string( ) ]
906+ ) ;
907+ let mut rollup = out. aggregation_labels ( "CountMinSketchWithHeap" , "rollup" ) ;
908+ rollup. sort ( ) ;
909+ assert_eq ! ( rollup, vec![ "dstip" . to_string( ) , "proto" . to_string( ) ] ) ;
910+ assert_eq ! (
911+ out. aggregation_parameter( "CountMinSketchWithHeap" , "heapsize" )
912+ . and_then( |v| v. as_u64( ) ) ,
913+ Some ( 40 )
914+ ) ;
915+ assert_eq ! (
916+ out. aggregation_parameter( "CountMinSketchWithHeap" , "count_events" )
917+ . and_then( |v| v. as_bool( ) ) ,
918+ Some ( false )
919+ ) ;
920+ }
921+
922+ /// Plain COUNT without ORDER BY / LIMIT stays on the CMS + DeltaSet path.
923+ #[ test]
924+ fn spatial_count_without_order_by_is_not_topk ( ) {
925+ let q = "SELECT srcip, COUNT(pkt_len) AS transfer_events FROM netflow_table \
926+ WHERE time BETWEEN DATEADD(s, -11, NOW()) AND DATEADD(s, -10, NOW()) \
927+ GROUP BY srcip";
928+ let out = SQLController :: from_yaml ( & netflow_one_query_config ( q, 1 ) , sql_opts_1s_ingest ( ) )
929+ . unwrap ( )
930+ . generate ( )
931+ . unwrap ( ) ;
932+
933+ assert_eq ! ( out. streaming_aggregation_count( ) , 2 ) ;
934+ assert ! ( out. has_aggregation_type( "CountMinSketch" ) ) ;
935+ assert ! ( out. has_aggregation_type( "DeltaSetAggregator" ) ) ;
936+ assert ! ( !out. has_aggregation_type( "CountMinSketchWithHeap" ) ) ;
937+ }
938+
939+ /// ORDER BY aggregate alias ASC (bottom-k) stays on the CMS + DeltaSet path.
940+ #[ test]
941+ fn spatial_count_order_by_asc_limit_is_not_topk ( ) {
942+ let q = "SELECT srcip, COUNT(pkt_len) AS transfer_events FROM netflow_table \
943+ WHERE time BETWEEN DATEADD(s, -11, NOW()) AND DATEADD(s, -10, NOW()) \
944+ GROUP BY srcip ORDER BY transfer_events ASC LIMIT 10";
945+ let out = SQLController :: from_yaml ( & netflow_one_query_config ( q, 1 ) , sql_opts_1s_ingest ( ) )
946+ . unwrap ( )
947+ . generate ( )
948+ . unwrap ( ) ;
949+
950+ assert_eq ! ( out. streaming_aggregation_count( ) , 2 ) ;
951+ assert ! ( out. has_aggregation_type( "CountMinSketch" ) ) ;
952+ assert ! ( out. has_aggregation_type( "DeltaSetAggregator" ) ) ;
953+ assert ! ( !out. has_aggregation_type( "CountMinSketchWithHeap" ) ) ;
954+ }
955+
956+ /// LIMIT 0 is treated as non-top-k and uses the normal CMS + DeltaSet path.
957+ #[ test]
958+ fn spatial_count_limit_zero_is_not_topk ( ) {
959+ let q = "SELECT srcip, COUNT(pkt_len) AS transfer_events FROM netflow_table \
960+ WHERE time BETWEEN DATEADD(s, -11, NOW()) AND DATEADD(s, -10, NOW()) \
961+ GROUP BY srcip ORDER BY transfer_events DESC LIMIT 0";
962+ let out = SQLController :: from_yaml ( & netflow_one_query_config ( q, 1 ) , sql_opts_1s_ingest ( ) )
963+ . unwrap ( )
964+ . generate ( )
965+ . unwrap ( ) ;
966+
967+ assert_eq ! ( out. streaming_aggregation_count( ) , 2 ) ;
968+ assert ! ( out. has_aggregation_type( "CountMinSketch" ) ) ;
969+ assert ! ( out. has_aggregation_type( "DeltaSetAggregator" ) ) ;
970+ assert ! ( !out. has_aggregation_type( "CountMinSketchWithHeap" ) ) ;
971+ }
0 commit comments