linkrs_query/executor/streaming/chunk/
core.rs1use super::schema::{ColumnInfo, Schema};
4use super::typed::TypedColumn;
5use crate::executor::base::MemoryReservation;
6use crate::executor::streaming::runtime::ColumnarStats;
7use crate::executor::streaming::slot::{SlotId, SlotLayout};
8use linkrs_core::columnar::MaterializedBatch;
9use linkrs_core::Value;
10use std::sync::Arc;
11
12#[derive(Debug)]
14pub struct DataChunk {
15 pub rows: Vec<Vec<Value>>,
17 pub typed_columns: Option<Vec<TypedColumn>>,
19 pub(crate) columnar_build_deferred: bool,
23 pub selection: Option<Vec<usize>>,
25 pub multiplicity: u64,
31 pub schema: Arc<Schema>,
33 pub layout: Arc<SlotLayout>,
35 pub memory_reservation: Option<MemoryReservation>,
37 pub columnar_stats: Option<Arc<ColumnarStats>>,
39}
40
41impl Clone for DataChunk {
42 fn clone(&self) -> Self {
43 Self {
44 rows: self.rows.clone(),
45 typed_columns: self.typed_columns.clone(),
46 columnar_build_deferred: false,
50 selection: self.selection.clone(),
51 multiplicity: self.multiplicity,
52 schema: self.schema.clone(),
53 layout: Arc::clone(&self.layout),
54 memory_reservation: None,
55 columnar_stats: self.columnar_stats.clone(),
56 }
57 }
58}
59
60impl DataChunk {
61 pub fn new(rows: Vec<Vec<Value>>, schema: Arc<Schema>) -> Self {
64 let layout = Arc::new(SlotLayout::from_names(
65 &schema
66 .columns
67 .iter()
68 .map(|c| c.name.clone())
69 .collect::<Vec<_>>(),
70 ));
71 Self {
72 rows,
73 typed_columns: None,
74 columnar_build_deferred: false,
75 selection: None,
76 multiplicity: 1,
77 schema,
78 layout,
79 memory_reservation: None,
80 columnar_stats: None,
81 }
82 }
83
84 pub fn with_memory_reservation(mut self, reservation: MemoryReservation) -> Self {
85 self.memory_reservation = Some(reservation);
86 self
87 }
88
89 pub fn with_columnar_stats(mut self, stats: Arc<ColumnarStats>) -> Self {
90 self.columnar_stats = Some(stats);
91 self
92 }
93
94 pub fn take_memory_reservation(&mut self) -> Option<MemoryReservation> {
95 self.memory_reservation.take()
96 }
97
98 pub fn new_with_layout(rows: Vec<Vec<Value>>, layout: Arc<SlotLayout>) -> Self {
99 Self::try_new_with_layout(rows, layout).expect("DataChunk row width mismatch")
100 }
101
102 pub fn try_new_with_layout(
103 rows: Vec<Vec<Value>>,
104 layout: Arc<SlotLayout>,
105 ) -> Result<Self, linkrs_core::error::QueryError> {
106 let row_width = rows.first().map(Vec::len).unwrap_or(0);
107 if !layout.is_empty()
108 && !rows.is_empty()
109 && !rows.iter().all(|row| row.len() == layout.len())
110 {
111 return Err(linkrs_core::error::QueryError::execution(format!(
112 "DataChunk::new_with_layout: row width {} does not match layout width {}",
113 row_width,
114 layout.len()
115 )));
116 }
117 let columns: Vec<ColumnInfo> = layout
118 .slots
119 .iter()
120 .map(|info| ColumnInfo {
121 name: info.name.clone(),
122 data_type: info
123 .data_type
124 .as_ref()
125 .map(|dt| dt.to_string().to_lowercase())
126 .unwrap_or_else(|| "unknown".to_string()),
127 })
128 .collect();
129 let schema = Arc::new(Schema::new(columns));
130 Ok(Self {
131 rows,
132 typed_columns: None,
133 columnar_build_deferred: false,
134 selection: None,
135 multiplicity: 1,
136 schema,
137 layout,
138 memory_reservation: None,
139 columnar_stats: None,
140 })
141 }
142
143 pub fn from_rows(rows: Vec<Vec<Value>>) -> Self {
144 Self::from_rows_with_col_names(rows, None)
145 }
146
147 pub fn from_rows_with_col_names(rows: Vec<Vec<Value>>, col_names: Option<Vec<String>>) -> Self {
148 let schema = if rows.is_empty() {
149 if let Some(names) = col_names {
150 Arc::new(Schema::new(
151 names
152 .into_iter()
153 .map(|name| ColumnInfo {
154 name,
155 data_type: "unknown".to_string(),
156 })
157 .collect(),
158 ))
159 } else {
160 Arc::new(Schema::empty())
161 }
162 } else {
163 let col_count = rows[0].len();
164 let columns = (0..col_count)
165 .map(|i| {
166 let name = col_names
167 .as_ref()
168 .and_then(|names| names.get(i).cloned())
169 .unwrap_or_else(|| format!("col_{}", i));
170
171 let data_type = if let Some(row) = rows.first() {
172 if let Some(val) = row.get(i) {
173 match val {
174 Value::BigInt(_) => "bigint",
175 Value::Int(_) => "int",
176 Value::Double(_) => "double",
177 Value::Float(_) => "float",
178 Value::String(_) => "string",
179 Value::Bool(_) => "bool",
180 Value::Null(_) => "null",
181 _ => "unknown",
182 }
183 } else {
184 "unknown"
185 }
186 } else {
187 "unknown"
188 };
189
190 ColumnInfo {
191 name,
192 data_type: data_type.to_string(),
193 }
194 })
195 .collect();
196 Arc::new(Schema::new(columns))
197 };
198 let layout = Arc::new(SlotLayout::from_names(
199 &schema
200 .columns
201 .iter()
202 .map(|c| c.name.clone())
203 .collect::<Vec<_>>(),
204 ));
205 Self {
206 rows,
207 typed_columns: None,
208 columnar_build_deferred: false,
209 selection: None,
210 multiplicity: 1,
211 schema,
212 layout,
213 memory_reservation: None,
214 columnar_stats: None,
215 }
216 }
217
218 pub(crate) fn from_columns(columns: Vec<Vec<Value>>, layout: Arc<SlotLayout>) -> Self {
219 let num_cols = columns.len();
223 assert!(
224 layout.is_empty() || num_cols == layout.len(),
225 "DataChunk::from_columns: column count {} does not match layout width {}",
226 num_cols,
227 layout.len()
228 );
229 let num_rows = columns.first().map(|c| c.len()).unwrap_or(0);
230 assert!(
231 columns.iter().all(|c| c.len() == num_rows),
232 "DataChunk::from_columns: column length mismatch"
233 );
234
235 let mut rows = vec![Vec::with_capacity(num_cols); num_rows];
236 for col in columns.iter().take(num_cols) {
237 for (row_idx, val) in col.iter().enumerate().take(num_rows) {
238 rows[row_idx].push(val.clone());
239 }
240 }
241
242 let schema = Arc::new(Schema::new(
243 layout
244 .slots
245 .iter()
246 .map(|info| ColumnInfo {
247 name: info.name.clone(),
248 data_type: info
249 .data_type
250 .as_ref()
251 .map(|dt| dt.to_string().to_lowercase())
252 .unwrap_or_else(|| "unknown".to_string()),
253 })
254 .collect(),
255 ));
256
257 Self {
258 rows,
259 typed_columns: None,
260 columnar_build_deferred: false,
261 selection: None,
262 multiplicity: 1,
263 schema,
264 layout,
265 memory_reservation: None,
266 columnar_stats: None,
267 }
268 }
269
270 pub fn project_columns(columns: Vec<Vec<Value>>, layout: Arc<SlotLayout>) -> Self {
281 let mut chunk = Self::from_columns(columns, layout);
282 chunk.build_typed_columns(true);
286 chunk
287 }
288
289 pub fn from_batch(batch: &MaterializedBatch, layout: Arc<SlotLayout>) -> Self {
295 let rows = batch.to_rows();
296 Self::try_new_with_layout(rows, layout).expect("DataChunk row width mismatch")
297 }
298
299 pub fn slice_from_batch(
301 batch: &MaterializedBatch,
302 offset: usize,
303 len: usize,
304 layout: Arc<SlotLayout>,
305 ) -> Self {
306 Self::from_batch(&batch.slice_rows(offset, len), layout)
307 }
308
309 pub fn to_batch(&self) -> MaterializedBatch {
311 let mut batch = MaterializedBatch::new(self.num_columns(), 0);
312 let names: Arc<[String]> = Arc::from(
313 self.schema
314 .columns
315 .iter()
316 .map(|c| c.name.clone())
317 .collect::<Vec<_>>(),
318 );
319 batch.set_schema_names(names);
320 for row in &self.rows {
321 batch.append_row(row.clone());
322 }
323 batch
324 }
325
326 pub fn estimated_bytes(&self) -> usize {
329 let base = crate::executor::base::MemoryBudget::estimate_rows_memory(&self.rows);
330 if self.rows.is_empty() || base == 0 {
331 return 0;
332 }
333 let visible = self.visible_count();
334 if visible == self.rows.len() && self.multiplicity <= 1 {
335 return base;
336 }
337 let per_row = base.saturating_div(self.rows.len().max(1));
338 (visible as u64)
339 .saturating_mul(self.multiplicity)
340 .saturating_mul(per_row as u64)
341 .min(usize::MAX as u64) as usize
342 }
343
344 pub fn len(&self) -> usize {
347 self.rows.len()
348 }
349
350 pub fn is_empty(&self) -> bool {
351 self.rows.is_empty()
352 }
353
354 pub fn num_columns(&self) -> usize {
355 self.schema.column_count()
356 }
357
358 pub fn col_names(&self) -> Vec<String> {
359 self.schema.columns.iter().map(|c| c.name.clone()).collect()
360 }
361
362 pub fn col_name(&self, index: usize) -> Option<String> {
363 self.schema.columns.get(index).map(|c| c.name.clone())
364 }
365
366 pub fn col_name_index(&self) -> std::collections::HashMap<String, usize> {
367 self.schema
368 .columns
369 .iter()
370 .enumerate()
371 .map(|(i, col)| (col.name.clone(), i))
372 .collect()
373 }
374
375 pub fn get_layout(&self) -> Arc<SlotLayout> {
376 Arc::clone(&self.layout)
377 }
378
379 pub fn get_by_slot(&self, row_idx: usize, slot: SlotId) -> Option<Value> {
380 self.rows
381 .get(row_idx)
382 .and_then(|row| row.get(slot).cloned())
383 }
384
385 pub fn get_column(&mut self, slot: SlotId) -> Option<Vec<Value>> {
392 if slot >= self.layout.len() {
393 return None;
394 }
395 self.ensure_typed_columns();
398 if let Some(ref typed) = self.typed_columns {
399 if let Some(col) = typed.get(slot) {
400 return Some(col.to_values());
401 }
402 }
403 Some(self.rows.iter().map(|row| row[slot].clone()).collect())
404 }
405
406 pub fn column_ref(&self, slot: SlotId) -> Option<Vec<&Value>> {
407 if slot >= self.layout.len() {
408 return None;
409 }
410 Some(self.rows.iter().map(|row| &row[slot]).collect())
411 }
412
413 pub fn get_typed_by_slot(&self, row_idx: usize, slot: SlotId) -> Option<Value> {
414 if let Some(ref typed) = self.typed_columns {
415 if let Some(col) = typed.get(slot) {
416 return col.value_at(row_idx);
417 }
418 }
419 self.get_by_slot(row_idx, slot)
420 }
421
422 pub fn row_at(&self, i: usize) -> &[Value] {
423 &self.rows[i]
424 }
425
426 pub fn take_rows_for_reuse(&mut self) -> Vec<Vec<Value>> {
434 self.selection = None;
435 self.columnar_build_deferred = false;
436 if self.typed_columns.take().is_some() {
437 if let Some(stats) = &self.columnar_stats {
440 stats.record_wasted_build();
441 }
442 }
443 self.multiplicity = 1;
444 std::mem::take(&mut self.rows)
445 }
446
447 pub fn build_typed_columns(&mut self, use_columnar: bool) -> usize {
457 if !use_columnar {
458 return 0;
459 }
460 self.columnar_build_deferred = false;
463 if self.typed_columns.is_some() {
464 return 0;
465 }
466 if !linkrs_storage::memory_watermark::pressure().allows_columnar() {
470 return 0;
471 }
472 let num_cols = self.num_columns();
473 if self.rows.is_empty() || num_cols == 0 {
474 return 0;
475 }
476 let num_rows = self.rows.len();
477 let mut typed = Vec::with_capacity(num_cols);
478 let mut extra_bytes = 0usize;
479 for col_idx in 0..num_cols {
480 let first = &self.rows[0][col_idx];
481 let representative = if matches!(first, Value::Null(_)) {
482 super::kind::first_non_null(self.rows.iter().map(|row| &row[col_idx]))
486 .unwrap_or(first)
487 } else {
488 first
489 };
490 let Some(kind) = super::kind::value_to_kind(representative) else {
491 typed.push(TypedColumn::Fallback(
494 self.rows.iter().map(|row| row[col_idx].clone()).collect(),
495 ));
496 continue;
497 };
498 let mut ok = true;
499 let mut has_null = false;
500 let mut bitmap = vec![0u64; num_rows.div_ceil(64)];
501 let mut builder = super::kind::TypedColumnBuilder::with_capacity(kind, num_rows);
502 for (i, row) in self.rows.iter().enumerate() {
503 match builder.push_value(&row[col_idx]) {
504 super::kind::PushOutcome::Value => bitmap[i / 64] |= 1u64 << (i % 64),
505 super::kind::PushOutcome::Null => has_null = true,
506 super::kind::PushOutcome::Mismatch => {
507 ok = false;
508 break;
509 }
510 }
511 }
512 if ok {
513 extra_bytes += builder.estimated_bytes();
514 extra_bytes += bitmap.capacity() * std::mem::size_of::<u64>();
515 typed.push(builder.finish(has_null, bitmap));
516 } else {
517 typed.push(TypedColumn::Fallback(
518 self.rows.iter().map(|row| row[col_idx].clone()).collect(),
519 ));
520 }
521 }
522 self.typed_columns = Some(typed);
523 extra_bytes
524 }
525
526 pub fn typed_column(&self, slot: SlotId) -> Option<&TypedColumn> {
527 self.typed_columns.as_ref().and_then(|cols| cols.get(slot))
528 }
529
530 pub fn typed_len(&self) -> Option<usize> {
538 let cols = self.typed_columns.as_ref()?;
539 let mut len: Option<usize> = None;
540 for col in cols {
541 let arm = col.len();
542 debug_assert!(
543 len.is_none_or(|n| n == arm),
544 "typed arms must stay in lockstep like rows"
545 );
546 len = Some(len.unwrap_or(arm));
547 }
548 len
549 }
550
551 pub fn ensure_typed_columns(&mut self) -> usize {
558 if self.typed_columns.is_none() && self.columnar_build_deferred {
559 return self.build_typed_columns(true);
560 }
561 0
562 }
563
564 pub(super) fn count_columnar(&self, hit: bool) {
567 if let Some(stats) = &self.columnar_stats {
568 if hit {
569 stats.record_hit();
570 } else {
571 stats.record_miss();
572 }
573 }
574 }
575
576 pub(crate) fn count_typed_hit(&self) {
577 if let Some(stats) = &self.columnar_stats {
578 stats.record_typed_hit();
579 }
580 }
581
582 pub(super) fn count_selection_pushed(&self) {
585 if let Some(stats) = &self.columnar_stats {
586 stats.record_selection_pushed();
587 }
588 }
589}