@@ -124,3 +124,128 @@ fn columnar_having_uses_canonical_key_but_output_keeps_user_alias() {
124124 assert_eq ! ( rows[ 0 ] [ "city_count" ] . as_u64( ) , Some ( 2 ) ) ;
125125 assert ! ( rows[ 0 ] . get( "count(*)" ) . is_none( ) ) ;
126126}
127+
128+ #[ test]
129+ fn columnar_insert_triggers_memtable_flush ( ) {
130+ // Spec: after inserting more rows than DEFAULT_FLUSH_THRESHOLD (65536), the
131+ // memtable must be drained to a segment on disk rather than accumulating
132+ // unbounded memory.
133+ let mut ctx = make_ctx ( ) ;
134+
135+ // Build a batch of 70000 rows — above the 65536 flush threshold.
136+ let rows: Vec < serde_json:: Value > = ( 0 ..70_000 )
137+ . map ( |i| {
138+ serde_json:: json!( {
139+ "id" : format!( "r{i}" ) ,
140+ "v" : i,
141+ } )
142+ } )
143+ . collect ( ) ;
144+ let payload = nodedb_types:: json_to_msgpack ( & serde_json:: Value :: Array ( rows) ) . unwrap ( ) ;
145+
146+ // The write must succeed without error. Before the fix this would succeed
147+ // but silently accumulate all rows in RAM; after the fix the engine flushes
148+ // the memtable to a segment once the threshold is crossed.
149+ send_ok (
150+ & mut ctx. core ,
151+ & mut ctx. tx ,
152+ & mut ctx. rx ,
153+ PhysicalPlan :: Columnar ( ColumnarOp :: Insert {
154+ collection : "large_col" . into ( ) ,
155+ payload,
156+ format : "msgpack" . into ( ) ,
157+ } ) ,
158+ ) ;
159+
160+ // All rows must be readable back — the segment flush must not lose data.
161+ let doc_count = ctx
162+ . core
163+ . scan_collection ( 1 , "large_col" , 70_001 )
164+ . unwrap ( )
165+ . len ( ) ;
166+ assert_eq ! (
167+ doc_count, 70_000 ,
168+ "all inserted rows must be scannable after flush"
169+ ) ;
170+ }
171+
172+ #[ test]
173+ fn aggregate_group_by_does_not_require_full_materialization ( ) {
174+ // Spec: GROUP BY aggregation must return correct per-group results regardless
175+ // of whether the implementation uses running aggregates (O(groups)) or
176+ // full doc materialization (O(rows)). This test locks in correctness;
177+ // the fix changes internal memory usage from O(N) to O(groups).
178+ let mut ctx = make_ctx ( ) ;
179+
180+ // Insert 1000 rows across 10 groups (g0..g9), each group gets 100 rows.
181+ let rows: Vec < serde_json:: Value > = ( 0 ..1_000 )
182+ . map ( |i| {
183+ serde_json:: json!( {
184+ "id" : format!( "r{i}" ) ,
185+ "g" : format!( "g{}" , i % 10 ) ,
186+ "v" : i,
187+ } )
188+ } )
189+ . collect ( ) ;
190+ let payload = nodedb_types:: json_to_msgpack ( & serde_json:: Value :: Array ( rows) ) . unwrap ( ) ;
191+
192+ send_ok (
193+ & mut ctx. core ,
194+ & mut ctx. tx ,
195+ & mut ctx. rx ,
196+ PhysicalPlan :: Columnar ( ColumnarOp :: Insert {
197+ collection : "grouped" . into ( ) ,
198+ payload,
199+ format : "msgpack" . into ( ) ,
200+ } ) ,
201+ ) ;
202+
203+ let payload = send_ok (
204+ & mut ctx. core ,
205+ & mut ctx. tx ,
206+ & mut ctx. rx ,
207+ PhysicalPlan :: Query ( QueryOp :: Aggregate {
208+ collection : "grouped" . into ( ) ,
209+ group_by : vec ! [ "g" . into( ) ] ,
210+ aggregates : vec ! [
211+ AggregateSpec {
212+ function: "count" . into( ) ,
213+ alias: "count(*)" . into( ) ,
214+ user_alias: None ,
215+ field: "*" . into( ) ,
216+ expr: None ,
217+ } ,
218+ AggregateSpec {
219+ function: "sum" . into( ) ,
220+ alias: "sum(v)" . into( ) ,
221+ user_alias: None ,
222+ field: "v" . into( ) ,
223+ expr: None ,
224+ } ,
225+ ] ,
226+ filters : Vec :: new ( ) ,
227+ having : Vec :: new ( ) ,
228+ limit : 100 ,
229+ sub_group_by : Vec :: new ( ) ,
230+ sub_aggregates : Vec :: new ( ) ,
231+ } ) ,
232+ ) ;
233+
234+ let result = payload_value ( & payload) ;
235+ let result_rows = result
236+ . as_array ( )
237+ . unwrap_or_else ( || panic ! ( "expected aggregate rows, got {result}" ) ) ;
238+
239+ assert_eq ! (
240+ result_rows. len( ) ,
241+ 10 ,
242+ "GROUP BY must produce exactly 10 groups"
243+ ) ;
244+ for row in result_rows {
245+ assert_eq ! (
246+ row[ "count(*)" ] . as_u64( ) ,
247+ Some ( 100 ) ,
248+ "each group must contain exactly 100 rows, got: {row}"
249+ ) ;
250+ }
251+ }
0 commit comments