linkrs_query/executor/streaming/chunk/
selection.rs1use super::pool::RowBufferPool;
32use super::typed::{gather_typed_column, repeat_typed_column};
33use crate::executor::streaming::chunk::core::DataChunk;
34
35impl DataChunk {
36 pub fn multiplicity(&self) -> u64 {
40 self.multiplicity
41 }
42
43 pub fn set_multiplicity(&mut self, multiplicity: u64) {
44 debug_assert!(multiplicity >= 1, "multiplicity must be >= 1");
45 self.multiplicity = multiplicity.max(1);
46 }
47
48 pub fn with_multiplicity(mut self, multiplicity: u64) -> Self {
49 self.set_multiplicity(multiplicity);
50 self
51 }
52
53 pub fn logical_len(&self) -> u64 {
56 (self.visible_count() as u64).saturating_mul(self.multiplicity)
57 }
58
59 pub fn visible_rows(&self) -> impl Iterator<Item = &Vec<linkrs_core::Value>> {
61 match &self.selection {
62 Some(indices) => VisibleRows::Selected {
63 rows: &self.rows,
64 indices,
65 pos: 0,
66 },
67 None => VisibleRows::All {
68 rows: &self.rows,
69 pos: 0,
70 },
71 }
72 }
73
74 pub fn expand_visible_rows(&mut self) -> Vec<Vec<linkrs_core::Value>> {
85 let multiplicity = self.multiplicity;
86 let visible_indices: Vec<usize> = match &self.selection {
89 Some(indices) => indices.clone(),
90 None => (0..self.rows.len()).collect(),
91 };
92 let out = match self.selection.take() {
93 Some(indices) => {
94 let mut selected = Vec::with_capacity(indices.len());
95 for &i in &indices {
96 selected.push(std::mem::take(&mut self.rows[i]));
97 }
98 self.rows.clear();
99 selected
100 }
101 None => std::mem::take(&mut self.rows),
102 };
103 self.typed_columns = self.typed_columns.take().map(|cols| {
108 cols.iter()
109 .map(|col| {
110 let gathered = gather_typed_column(col, &visible_indices);
111 if multiplicity <= 1 {
112 gathered
113 } else {
114 repeat_typed_column(&gathered, multiplicity as usize)
115 }
116 })
117 .collect()
118 });
119 self.multiplicity = 1;
120 debug_assert!(
121 self.selection.is_none() && self.multiplicity == 1,
122 "expand_visible_rows must leave a flat chunk: \
123 selection consumed, multiplicity reset to 1"
124 );
125 if multiplicity <= 1 || out.is_empty() {
126 return out;
127 }
128 let total = (out.len() as u64).saturating_mul(multiplicity) as usize;
129 if let Some(stats) = &self.columnar_stats {
130 stats.record_multiplicity_expanded(total as u64);
131 }
132 expand_rows_reusing_buffers(out, multiplicity, total)
133 }
134
135 pub fn with_selection(mut self, indices: Vec<usize>) -> Self {
138 debug_assert!(indices.is_sorted() && indices.windows(2).all(|w| w[0] < w[1]));
139 debug_assert!(indices.last().is_none_or(|&i| i < self.rows.len()));
140 self.selection = Some(indices);
141 self
142 }
143
144 pub fn selection(&self) -> Option<&[usize]> {
145 self.selection.as_deref()
146 }
147
148 pub fn visible_count(&self) -> usize {
149 self.selection
150 .as_ref()
151 .map(Vec::len)
152 .unwrap_or(self.rows.len())
153 }
154
155 pub fn visible_indices(&self) -> Vec<usize> {
156 match &self.selection {
157 Some(indices) => indices.clone(),
158 None => (0..self.rows.len()).collect(),
159 }
160 }
161
162 pub fn is_visible(&self, idx: usize) -> bool {
163 match &self.selection {
164 None => idx < self.rows.len(),
165 Some(indices) => indices.binary_search(&idx).is_ok(),
166 }
167 }
168
169 pub fn take_selection(&mut self) -> Option<Vec<usize>> {
170 self.selection.take()
171 }
172
173 pub fn expand_multiplicity_in_place(&mut self) -> bool {
182 if self.multiplicity <= 1 {
183 return false;
184 }
185 let multiplicity = self.multiplicity;
186 let visible_indices: Vec<usize> = match &self.selection {
189 Some(indices) => indices.clone(),
190 None => (0..self.rows.len()).collect(),
191 };
192 let taken: Vec<Vec<linkrs_core::Value>> = match self.selection.take() {
195 Some(indices) => {
196 let mut selected = Vec::with_capacity(indices.len());
197 for &i in &indices {
198 selected.push(std::mem::take(&mut self.rows[i]));
199 }
200 self.rows.clear();
201 selected
202 }
203 None => std::mem::take(&mut self.rows),
204 };
205 self.typed_columns = self.typed_columns.take().map(|cols| {
209 cols.iter()
210 .map(|col| {
211 let gathered = gather_typed_column(col, &visible_indices);
212 repeat_typed_column(&gathered, multiplicity as usize)
213 })
214 .collect()
215 });
216 self.multiplicity = 1;
217 debug_assert!(
218 self.selection.is_none() && self.multiplicity == 1,
219 "expand_multiplicity_in_place must leave a flat chunk: \
220 selection consumed, multiplicity reset to 1"
221 );
222 if taken.is_empty() {
223 return true;
224 }
225 let total = (taken.len() as u64).saturating_mul(multiplicity) as usize;
226 self.rows = expand_rows_reusing_buffers(taken, multiplicity, total);
227 if let Some(stats) = &self.columnar_stats {
228 stats.record_multiplicity_expanded(total as u64);
229 }
230 true
231 }
232
233 pub fn normalize_for_opaque(&mut self, op: &'static str) -> bool {
240 let expanded = self.expand_multiplicity_in_place();
241 let materialized = self.materialize_selection_by(op);
242 expanded || materialized
243 }
244
245 pub fn materialize_selection_by(&mut self, op: &'static str) -> bool {
257 let did = self.materialize_selection_inner();
258 if did {
259 if let Some(stats) = &self.columnar_stats {
260 stats.record_selection_materialized_by(op);
261 }
262 }
263 did
264 }
265
266 fn materialize_selection_inner(&mut self) -> bool {
267 let Some(indices) = self.selection.take() else {
268 return false;
269 };
270 let mut selected = Vec::with_capacity(indices.len());
271 for &i in &indices {
272 selected.push(std::mem::take(&mut self.rows[i]));
273 }
274 self.rows = selected;
275 self.typed_columns = self.typed_columns.as_ref().map(|cols| {
276 cols.iter()
277 .map(|col| gather_typed_column(col, &indices))
278 .collect()
279 });
280 true
281 }
282
283 pub fn take_indices(&mut self, indices: &[usize]) -> Self {
286 let layout = std::sync::Arc::clone(&self.layout);
287 let schema = std::sync::Arc::clone(&self.schema);
288 let mut selected = Vec::with_capacity(indices.len());
289 for &i in indices {
290 selected.push(std::mem::take(&mut self.rows[i]));
291 }
292 let typed_columns = self.typed_columns.as_ref().map(|cols| {
293 cols.iter()
294 .map(|col| gather_typed_column(col, indices))
295 .collect()
296 });
297 Self {
298 rows: selected,
299 typed_columns,
300 columnar_build_deferred: self.columnar_build_deferred,
301 selection: None,
302 multiplicity: self.multiplicity,
303 schema,
304 layout,
305 memory_reservation: self.memory_reservation.take(),
306 columnar_stats: self.columnar_stats.clone(),
307 }
308 }
309
310 pub fn slice(&mut self, start: usize, end: usize) -> Self {
311 assert!(end <= self.rows.len(), "slice end out of bounds");
312 let layout = std::sync::Arc::clone(&self.layout);
313 let schema = std::sync::Arc::clone(&self.schema);
314 let mut selected = Vec::with_capacity(end - start);
315 for i in start..end {
316 selected.push(std::mem::take(&mut self.rows[i]));
317 }
318 let indices: Vec<usize> = (start..end).collect();
319 let typed_columns = self.typed_columns.as_ref().map(|cols| {
320 cols.iter()
321 .map(|col| gather_typed_column(col, &indices))
322 .collect()
323 });
324 Self {
325 rows: selected,
326 typed_columns,
327 columnar_build_deferred: self.columnar_build_deferred,
328 selection: None,
329 multiplicity: self.multiplicity,
330 schema,
331 layout,
332 memory_reservation: self.memory_reservation.take(),
333 columnar_stats: self.columnar_stats.clone(),
334 }
335 }
336}
337
338fn expand_rows_reusing_buffers(
349 source: Vec<Vec<linkrs_core::Value>>,
350 multiplicity: u64,
351 total: usize,
352) -> Vec<Vec<linkrs_core::Value>> {
353 debug_assert!(multiplicity > 1, "pool path is only for real expansion");
354 debug_assert!(!source.is_empty(), "empty source needs no expansion");
355 let round = source.len();
356 let mut expanded = Vec::with_capacity(total.min(1 << 22));
357 expanded.extend(source);
358 let mut recycled = RowBufferPool::acquire_rows(total.saturating_sub(round));
360 for _ in 1..multiplicity {
361 for i in 0..round {
362 let mut buf = recycled.pop().unwrap_or_default();
363 buf.extend(expanded[i].iter().cloned());
364 expanded.push(buf);
365 }
366 if expanded.len() >= (1 << 22) {
367 expanded.reserve(total.saturating_sub(expanded.len()).min(1 << 22));
368 }
369 }
370 expanded
371}
372
373enum VisibleRows<'a> {
374 All {
375 rows: &'a [Vec<linkrs_core::Value>],
376 pos: usize,
377 },
378 Selected {
379 rows: &'a [Vec<linkrs_core::Value>],
380 indices: &'a [usize],
381 pos: usize,
382 },
383}
384
385impl<'a> Iterator for VisibleRows<'a> {
386 type Item = &'a Vec<linkrs_core::Value>;
387
388 fn next(&mut self) -> Option<Self::Item> {
389 match self {
390 VisibleRows::All { rows, pos } => {
391 let row = rows.get(*pos)?;
392 *pos += 1;
393 Some(row)
394 }
395 VisibleRows::Selected { rows, indices, pos } => {
396 let &i = indices.get(*pos)?;
397 *pos += 1;
398 rows.get(i)
399 }
400 }
401 }
402
403 fn size_hint(&self) -> (usize, Option<usize>) {
404 let remaining = match self {
405 VisibleRows::All { rows, pos } => rows.len().saturating_sub(*pos),
406 VisibleRows::Selected { indices, pos, .. } => indices.len().saturating_sub(*pos),
407 };
408 (remaining, Some(remaining))
409 }
410}
411
412impl ExactSizeIterator for VisibleRows<'_> {}
413
414#[cfg(test)]
415mod tests {
416 use super::super::core::DataChunk;
417 use crate::executor::streaming::slot::SlotLayout;
418 use linkrs_core::Value;
419 use std::sync::Arc;
420
421 fn layout1() -> Arc<SlotLayout> {
422 Arc::new(SlotLayout::from_names(&["a".to_string()]))
423 }
424
425 #[test]
426 fn opaque_boundary_expands_multiplicity() {
427 let rows = vec![vec![Value::Int(1)], vec![Value::Int(2)]];
428 let mut chunk = DataChunk::new_with_layout(rows, layout1()).with_selection(vec![0, 1]);
429 chunk.set_multiplicity(3);
430 assert_eq!(chunk.logical_len(), 6);
431 assert!(chunk.normalize_for_opaque("Sort"));
432 assert_eq!(chunk.multiplicity(), 1);
433 assert!(chunk.selection().is_none());
434 assert_eq!(chunk.rows.len(), 6);
435 assert_eq!(
436 chunk.rows,
437 vec![
438 vec![Value::Int(1)],
439 vec![Value::Int(2)],
440 vec![Value::Int(1)],
441 vec![Value::Int(2)],
442 vec![Value::Int(1)],
443 vec![Value::Int(2)],
444 ]
445 );
446 }
447
448 #[test]
449 fn opaque_boundary_noop_when_flat() {
450 let rows = vec![vec![Value::Int(1)]];
451 let mut chunk = DataChunk::new_with_layout(rows, layout1());
452 assert!(!chunk.normalize_for_opaque("Sort"));
453 assert_eq!(chunk.rows.len(), 1);
454 }
455
456 #[test]
457 fn expand_multiplicity_drops_hidden_rows() {
458 let rows = vec![
459 vec![Value::Int(1)],
460 vec![Value::Int(2)],
461 vec![Value::Int(3)],
462 ];
463 let mut chunk = DataChunk::new_with_layout(rows, layout1()).with_selection(vec![0, 2]);
464 chunk.set_multiplicity(2);
465 assert!(chunk.expand_multiplicity_in_place());
466 assert_eq!(chunk.rows.len(), 4);
467 assert_eq!(
468 chunk.rows,
469 vec![
470 vec![Value::Int(1)],
471 vec![Value::Int(3)],
472 vec![Value::Int(1)],
473 vec![Value::Int(3)],
474 ]
475 );
476 }
477
478 #[test]
479 fn multiplicity_expansion_runs_on_pooled_buffers() {
480 use super::super::pool::{RowBufferPool, MAX_POOLED_ROWS};
481 RowBufferPool::clear_for_test();
486 let mut spare: Vec<Vec<Value>> = (0..(MAX_POOLED_ROWS + 8))
487 .map(|i| vec![Value::Int(i as i32)])
488 .collect();
489 RowBufferPool::release_rows(&mut spare);
490 let rows = vec![vec![Value::Int(7)], vec![Value::Int(8)]];
491 let mut chunk = DataChunk::new_with_layout(rows, layout1());
492 chunk.set_multiplicity(3);
493 assert!(chunk.expand_multiplicity_in_place());
494 assert_eq!(
495 chunk.rows,
496 vec![
497 vec![Value::Int(7)],
498 vec![Value::Int(8)],
499 vec![Value::Int(7)],
500 vec![Value::Int(8)],
501 vec![Value::Int(7)],
502 vec![Value::Int(8)],
503 ]
504 );
505 assert!(RowBufferPool::pool_len() <= MAX_POOLED_ROWS);
506 RowBufferPool::clear_for_test();
507 }
508}