1use crate::executor::streaming::chunk::DataChunk;
2use crate::executor::streaming::chunk::{gather_typed_column, VECTORIZED_BATCH_SIZE};
3use crate::executor::streaming::executor::StreamingExecutor;
4use linkrs_core::error::QueryError;
5
6pub const DEFAULT_FLATTEN_BATCH_SIZE: usize = VECTORIZED_BATCH_SIZE;
11
12pub(crate) fn prepare_flatten_buffer(chunk: DataChunk) -> (Vec<usize>, DataChunk) {
21 let sel = chunk.visible_indices();
22 let mut buffered = chunk;
23 let _ = buffered.take_selection();
24 (sel, buffered)
25}
26
27pub(crate) fn flatten_next_inner(
28 current_idx: &mut usize,
29 size_to_flatten: &mut usize,
30 saved_sel_vector: &mut Option<Vec<usize>>,
31 buffered_chunk: &mut Option<DataChunk>,
32 input: &mut StreamingExecutor,
33) -> Result<Option<DataChunk>, QueryError> {
34 flatten_next_batch(
35 current_idx,
36 size_to_flatten,
37 saved_sel_vector,
38 buffered_chunk,
39 input,
40 1,
41 )
42}
43
44pub(crate) fn flatten_next_batch(
53 current_idx: &mut usize,
54 size_to_flatten: &mut usize,
55 saved_sel_vector: &mut Option<Vec<usize>>,
56 buffered_chunk: &mut Option<DataChunk>,
57 input: &mut StreamingExecutor,
58 batch_size: usize,
59) -> Result<Option<DataChunk>, QueryError> {
60 let batch_size = batch_size.max(1);
61 loop {
62 if buffered_chunk.is_some() {
63 if *current_idx < *size_to_flatten {
64 let take = (*size_to_flatten - *current_idx).min(batch_size);
65 let out = {
68 let chunk = buffered_chunk
69 .as_ref()
70 .expect("buffered chunk must be present");
71 let sel_vec = saved_sel_vector
72 .as_ref()
73 .expect("saved sel vector must be present");
74 let positions: Vec<usize> = sel_vec[*current_idx..*current_idx + take].to_vec();
75 build_flatten_batch_chunk(chunk, &positions)
76 };
77 *current_idx += take;
78 if *current_idx >= *size_to_flatten {
79 *buffered_chunk = None;
80 *saved_sel_vector = None;
81 *size_to_flatten = 0;
82 *current_idx = 0;
83 }
84 return Ok(Some(out));
85 } else {
86 *saved_sel_vector = None;
87 *size_to_flatten = 0;
88 *current_idx = 0;
89 }
90 }
91 let child_chunk = match input.advance()? {
92 Some(c) => c,
93 None => return Ok(None),
94 };
95 if child_chunk.visible_count() == 0 {
96 continue;
97 }
98 let (sel, buffered) = prepare_flatten_buffer(child_chunk);
99 *size_to_flatten = sel.len();
100 *saved_sel_vector = Some(sel);
101 *current_idx = 0;
102 *buffered_chunk = Some(buffered);
103 }
104}
105
106fn build_flatten_batch_chunk(chunk: &DataChunk, positions: &[usize]) -> DataChunk {
112 let layout = chunk.get_layout();
113 let schema = chunk.schema.clone();
114 let rows = chunk.to_batch().gather(positions).to_rows();
115 let typed_columns = chunk.typed_columns.as_ref().map(|cols| {
116 cols.iter()
117 .map(|col| gather_typed_column(col, positions))
118 .collect::<Vec<_>>()
119 });
120 let columnar_stats = chunk.columnar_stats.clone();
121 let mut out = DataChunk::new_with_layout(rows, layout);
122 out.schema = schema;
123 out.typed_columns = typed_columns;
124 out.columnar_build_deferred = chunk.columnar_build_deferred && out.typed_columns.is_none();
127 out.columnar_stats = columnar_stats;
128 out
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::executor::streaming::chunk::DataChunk;
135 use crate::executor::streaming::executor::StreamingExecutor;
136 use crate::executor::streaming::operators::base::OperatorBase;
137 use crate::executor::streaming::operators::source_operator::{
138 SourceOperator, SourceOperatorKind,
139 };
140 use crate::executor::streaming::slot::SlotLayout;
141 use linkrs_core::Value;
142 use std::sync::Arc;
143
144 fn test_layout(names: &[&str]) -> Arc<SlotLayout> {
145 Arc::new(SlotLayout::from_names(
146 &names.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
147 ))
148 }
149
150 fn source_executor(rows: Vec<Vec<Value>>, col_names: Vec<&str>) -> StreamingExecutor {
151 let layout = test_layout(&col_names);
152 StreamingExecutor::Source(
153 OperatorBase::new(0),
154 SourceOperator::new(
155 SourceOperatorKind::ScanVertices {
156 buffer: rows,
157 current_index: 0,
158 col_names: col_names.into_iter().map(|s| s.to_string()).collect(),
159 },
160 layout,
161 ),
162 )
163 }
164
165 #[test]
166 fn flatten_single_batch_selection_vector_path() {
167 let rows = vec![
168 vec![Value::Int(1), Value::string("a")],
169 vec![Value::Int(2), Value::string("b")],
170 vec![Value::Int(3), Value::string("c")],
171 ];
172 let mut src = source_executor(rows, vec!["id", "name"]);
173 src.open().expect("open");
174 let mut current_idx = 0usize;
175 let mut size_to_flatten = 0usize;
176 let mut saved = None;
177 let mut buffered = None;
178 let mut out_rows = Vec::new();
179 loop {
180 let chunk = flatten_next_inner(
181 &mut current_idx,
182 &mut size_to_flatten,
183 &mut saved,
184 &mut buffered,
185 &mut src,
186 )
187 .expect("next");
188 match chunk {
189 Some(c) => {
190 assert_eq!(c.len(), 1);
191 out_rows.push(c.rows[0].clone());
192 }
193 None => break,
194 }
195 }
196 assert_eq!(out_rows.len(), 3);
197 assert_eq!(out_rows[0][0], Value::Int(1));
198 assert_eq!(out_rows[1][0], Value::Int(2));
199 assert_eq!(out_rows[2][0], Value::Int(3));
200 }
201
202 #[test]
203 fn flatten_with_child_selection() {
204 let rows = vec![
205 vec![Value::Int(10)],
206 vec![Value::Int(20)],
207 vec![Value::Int(30)],
208 ];
209 let layout = test_layout(&["v"]);
210 let mut chunk = DataChunk::new_with_layout(rows.clone(), layout.clone());
211 chunk = chunk.with_selection(vec![0, 2]);
212 assert_eq!(chunk.visible_count(), 2);
213 let (sel, buffered) = prepare_flatten_buffer(chunk);
214 assert_eq!(sel, vec![0, 2]);
215 assert_eq!(buffered.rows.len(), 3);
216 assert_eq!(buffered.visible_count(), 3);
217 }
218
219 #[test]
220 fn flatten_empty_input_returns_none() {
221 let mut src = source_executor(vec![], vec!["id"]);
222 src.open().expect("open");
223 let mut current_idx = 0usize;
224 let mut size_to_flatten = 0usize;
225 let mut saved = None;
226 let mut buffered = None;
227 let out = flatten_next_inner(
228 &mut current_idx,
229 &mut size_to_flatten,
230 &mut saved,
231 &mut buffered,
232 &mut src,
233 )
234 .expect("next");
235 assert!(out.is_none());
236 }
237
238 #[test]
239 fn typed_columns_preserved() {
240 let layout = test_layout(&["a", "b"]);
241 let mut chunk = DataChunk::new_with_layout(
242 vec![
243 vec![Value::Int(1), Value::Int(10)],
244 vec![Value::Int(2), Value::Int(20)],
245 ],
246 layout.clone(),
247 );
248 chunk.build_typed_columns(true);
249 assert!(chunk.typed_columns.is_some());
250 let (sel, buffered) = prepare_flatten_buffer(chunk);
251 assert_eq!(sel.len(), 2);
252 let rows = vec![
253 vec![Value::Int(1), Value::Int(10)],
254 vec![Value::Int(2), Value::Int(20)],
255 ];
256 let mut src = source_executor(rows, vec!["a", "b"]);
257 let mut current_idx = 0usize;
258 let mut size_to_flatten = sel.len();
259 let mut saved = Some(sel);
260 let mut buf_opt = Some(buffered);
261 let out1 = flatten_next_inner(
262 &mut current_idx,
263 &mut size_to_flatten,
264 &mut saved,
265 &mut buf_opt,
266 &mut src,
267 )
268 .expect("next")
269 .expect("chunk");
270 assert_eq!(out1.len(), 1);
271 assert!(out1.typed_columns.is_some());
272 assert_eq!(out1.typed_columns.as_ref().unwrap()[0].len(), 1);
273 let out2 = flatten_next_inner(
274 &mut current_idx,
275 &mut size_to_flatten,
276 &mut saved,
277 &mut buf_opt,
278 &mut src,
279 )
280 .expect("next")
281 .expect("chunk");
282 assert_eq!(out2.len(), 1);
283 assert!(out2.typed_columns.is_some());
284 }
285
286 #[test]
287 fn empty_selection_batch() {
288 let layout = test_layout(&["v"]);
289 let mut chunk = DataChunk::new_with_layout(vec![], layout.clone());
290 chunk = chunk.with_selection(vec![]);
291 assert_eq!(chunk.visible_count(), 0);
292 let (sel, _) = prepare_flatten_buffer(chunk);
293 assert_eq!(sel.len(), 0);
294 }
295
296 fn drain_batch(src: &mut StreamingExecutor, batch_size: usize) -> Vec<DataChunk> {
297 let mut current_idx = 0usize;
298 let mut size_to_flatten = 0usize;
299 let mut saved = None;
300 let mut buffered = None;
301 let mut out = Vec::new();
302 loop {
303 let chunk = flatten_next_batch(
304 &mut current_idx,
305 &mut size_to_flatten,
306 &mut saved,
307 &mut buffered,
308 src,
309 batch_size,
310 )
311 .expect("next");
312 match chunk {
313 Some(c) => out.push(c),
314 None => break,
315 }
316 }
317 out
318 }
319
320 fn flat_rows(chunks: &[DataChunk]) -> Vec<Vec<Value>> {
321 chunks
322 .iter()
323 .flat_map(|c| c.rows.clone())
324 .collect::<Vec<_>>()
325 }
326
327 #[test]
328 fn flatten_batch_size_two_matches_single_row_path() {
329 let rows = vec![
330 vec![Value::Int(1), Value::string("a")],
331 vec![Value::Int(2), Value::string("b")],
332 vec![Value::Int(3), Value::string("c")],
333 vec![Value::Int(4), Value::string("d")],
334 vec![Value::Int(5), Value::string("e")],
335 ];
336 let mut single = source_executor(rows.clone(), vec!["id", "name"]);
337 single.open().expect("open");
338 let mut current_idx = 0usize;
339 let mut size_to_flatten = 0usize;
340 let mut saved = None;
341 let mut buffered = None;
342 let mut single_rows = Vec::new();
343 loop {
344 let chunk = flatten_next_inner(
345 &mut current_idx,
346 &mut size_to_flatten,
347 &mut saved,
348 &mut buffered,
349 &mut single,
350 )
351 .expect("next");
352 match chunk {
353 Some(c) => {
354 assert_eq!(c.len(), 1);
355 single_rows.push(c.rows[0].clone());
356 }
357 None => break,
358 }
359 }
360 let mut batched = source_executor(rows, vec!["id", "name"]);
361 batched.open().expect("open");
362 let chunks = drain_batch(&mut batched, 2);
363 assert_eq!(chunks.len(), 3);
364 assert_eq!(chunks[0].len(), 2);
365 assert_eq!(chunks[1].len(), 2);
366 assert_eq!(chunks[2].len(), 1);
367 assert_eq!(flat_rows(&chunks), single_rows);
368 }
369
370 #[test]
371 fn flatten_single_row_path_matches_full_morsel() {
372 let rows = vec![
375 vec![Value::Int(1)],
376 vec![Value::Int(2)],
377 vec![Value::Int(3)],
378 vec![Value::Int(4)],
379 ];
380 let mut single = source_executor(rows.clone(), vec!["id"]);
381 single.open().expect("open");
382 let mut current_idx = 0usize;
383 let mut size_to_flatten = 0usize;
384 let mut saved = None;
385 let mut buffered = None;
386 let mut single_rows = Vec::new();
387 loop {
388 let chunk = flatten_next_inner(
389 &mut current_idx,
390 &mut size_to_flatten,
391 &mut saved,
392 &mut buffered,
393 &mut single,
394 )
395 .expect("next");
396 match chunk {
397 Some(c) => single_rows.extend(c.rows.clone()),
398 None => break,
399 }
400 }
401 let mut batched = source_executor(rows.clone(), vec!["id"]);
402 batched.open().expect("open");
403 let chunks = drain_batch(&mut batched, 2048);
404 assert_eq!(chunks.len(), 1);
405 assert_eq!(flat_rows(&chunks), single_rows);
406 assert_eq!(flat_rows(&chunks), rows);
407 }
408
409 #[test]
410 fn flatten_batch_full_morsel_single_call() {
411 let rows = vec![
412 vec![Value::Int(1)],
413 vec![Value::Int(2)],
414 vec![Value::Int(3)],
415 ];
416 let mut src = source_executor(rows.clone(), vec!["id"]);
417 src.open().expect("open");
418 let chunks = drain_batch(&mut src, 2048);
419 assert_eq!(chunks.len(), 1);
420 assert_eq!(chunks[0].len(), 3);
421 assert_eq!(
422 flat_rows(&chunks),
423 rows,
424 "batch_size=2048 must emit the whole morsel at once"
425 );
426 }
427
428 #[test]
429 fn flatten_batch_typed_columns_equivalence() {
430 let layout = test_layout(&["a", "b"]);
431 let rows = vec![
432 vec![Value::Int(1), Value::Int(10)],
433 vec![Value::Int(2), Value::Int(20)],
434 vec![Value::Int(3), Value::Int(30)],
435 ];
436 let mut chunk = DataChunk::new_with_layout(rows.clone(), layout.clone());
437 chunk.build_typed_columns(true);
438 assert!(chunk.typed_columns.is_some());
439 let (sel, buffered) = prepare_flatten_buffer(chunk);
440 assert_eq!(sel.len(), 3);
441 let mut src = source_executor(rows.clone(), vec!["a", "b"]);
442 let mut current_idx = 0usize;
443 let mut size_to_flatten = sel.len();
444 let mut saved = Some(sel);
445 let mut buf_opt = Some(buffered);
446 let out = flatten_next_batch(
447 &mut current_idx,
448 &mut size_to_flatten,
449 &mut saved,
450 &mut buf_opt,
451 &mut src,
452 2,
453 )
454 .expect("next")
455 .expect("chunk");
456 assert_eq!(out.len(), 2);
457 let typed = out.typed_columns.as_ref().expect("typed columns");
458 assert_eq!(typed[0].len(), 2);
459 assert_eq!(typed[1].len(), 2);
460 assert_eq!(typed[0].value_at(0), Some(Value::Int(1)));
461 assert_eq!(typed[0].value_at(1), Some(Value::Int(2)));
462 assert_eq!(typed[1].value_at(0), Some(Value::Int(10)));
463 assert_eq!(typed[1].value_at(1), Some(Value::Int(20)));
464 let rest = flatten_next_batch(
465 &mut current_idx,
466 &mut size_to_flatten,
467 &mut saved,
468 &mut buf_opt,
469 &mut src,
470 2,
471 )
472 .expect("next")
473 .expect("chunk");
474 assert_eq!(rest.len(), 1);
475 assert_eq!(
476 rest.typed_columns.as_ref().expect("typed")[0].value_at(0),
477 Some(Value::Int(3))
478 );
479 assert!(saved.is_none());
481 assert!(buf_opt.is_none());
482 }
483
484 #[test]
485 fn flatten_batch_empty_input_returns_none() {
486 let mut src = source_executor(vec![], vec!["id"]);
487 src.open().expect("open");
488 let chunks = drain_batch(&mut src, 2);
489 assert!(chunks.is_empty());
490 }
491}