1use crate::value::Value;
17use std::collections::hash_map::DefaultHasher;
18use std::hash::{Hash, Hasher};
19use std::sync::Arc;
20
21pub type RowKey = Vec<Value>;
26
27pub const COLUMNAR_HASH_SEED: u64 = 0xdeadbeefcafe;
33
34const FNV1A_64_INIT: u64 = 0xcbf29ce484222325;
35const FNV1A_64_PRIME: u64 = 0x100000001b3;
36
37fn fnv1a_64_update(mut hash: u64, data: &[u8]) -> u64 {
38 for &byte in data {
39 hash ^= byte as u64;
40 hash = hash.wrapping_mul(FNV1A_64_PRIME);
41 }
42 hash
43}
44
45pub fn column_name_fingerprint(col_names: &[String]) -> u64 {
50 let mut hash = FNV1A_64_INIT;
51 for name in col_names {
52 hash = fnv1a_64_update(hash, name.as_bytes());
53 hash = fnv1a_64_update(hash, &[0]);
54 }
55 hash
56}
57
58#[derive(Debug, Clone, Default)]
63pub struct ColumnBuffer {
64 pub values: Vec<Value>,
66}
67
68impl ColumnBuffer {
69 pub fn new() -> Self {
71 ColumnBuffer { values: Vec::new() }
72 }
73
74 pub fn with_capacity(cap: usize) -> Self {
76 ColumnBuffer {
77 values: Vec::with_capacity(cap),
78 }
79 }
80
81 pub fn push(&mut self, v: Value) {
83 self.values.push(v);
84 }
85
86 pub fn len(&self) -> usize {
88 self.values.len()
89 }
90
91 pub fn is_empty(&self) -> bool {
93 self.values.is_empty()
94 }
95
96 pub fn clear(&mut self) {
98 self.values.clear();
99 }
100
101 pub fn as_slice(&self) -> &[Value] {
103 &self.values
104 }
105}
106
107#[derive(Debug, Clone, Default)]
113pub struct SelectionVector {
114 pub indices: Vec<usize>,
116}
117
118impl SelectionVector {
119 pub fn new() -> Self {
121 SelectionVector {
122 indices: Vec::new(),
123 }
124 }
125
126 pub fn identity(len: usize) -> Self {
128 SelectionVector {
129 indices: (0..len).collect(),
130 }
131 }
132
133 pub fn is_identity(&self) -> bool {
135 self.indices.iter().enumerate().all(|(i, &p)| i == p)
136 }
137
138 pub fn len(&self) -> usize {
140 self.indices.len()
141 }
142
143 pub fn is_empty(&self) -> bool {
145 self.indices.is_empty()
146 }
147}
148
149#[derive(Debug, Clone)]
155pub struct MaterializedBatch {
156 columns: Vec<ColumnBuffer>,
158 num_rows: usize,
160 selection: Option<SelectionVector>,
162 schema_fingerprint: u64,
167 schema_names: Option<Arc<[String]>>,
170}
171
172impl MaterializedBatch {
173 pub fn new(num_columns: usize, schema_fingerprint: u64) -> Self {
175 MaterializedBatch {
176 columns: (0..num_columns).map(|_| ColumnBuffer::new()).collect(),
177 num_rows: 0,
178 selection: None,
179 schema_fingerprint,
180 schema_names: None,
181 }
182 }
183
184 pub fn with_schema_names(names: Arc<[String]>) -> Self {
190 let fingerprint = column_name_fingerprint(&names);
191 MaterializedBatch {
192 columns: (0..names.len()).map(|_| ColumnBuffer::new()).collect(),
193 num_rows: 0,
194 selection: None,
195 schema_fingerprint: fingerprint,
196 schema_names: Some(names),
197 }
198 }
199
200 pub fn set_schema_names(&mut self, names: Arc<[String]>) {
202 self.schema_fingerprint = column_name_fingerprint(&names);
203 self.schema_names = Some(names);
204 }
205
206 pub fn schema_names(&self) -> Option<&Arc<[String]>> {
208 self.schema_names.as_ref()
209 }
210
211 pub fn set_fingerprint(&mut self, fingerprint: u64) {
213 self.schema_fingerprint = fingerprint;
214 }
215
216 pub fn from_rows(rows: impl IntoIterator<Item = Vec<Value>>) -> Self {
222 let mut batch = MaterializedBatch::new(0, 0);
223 for row in rows {
224 batch.append_row(row);
225 }
226 batch.refresh_fingerprint();
227 batch
228 }
229
230 pub fn num_columns(&self) -> usize {
232 self.columns.len()
233 }
234
235 pub fn num_rows(&self) -> usize {
237 match &self.selection {
238 Some(sel) => sel.indices.len(),
239 None => self.num_rows,
240 }
241 }
242
243 pub fn is_empty(&self) -> bool {
245 self.num_rows() == 0
246 }
247
248 pub fn schema_fingerprint(&self) -> u64 {
250 self.schema_fingerprint
251 }
252
253 pub fn append_row(&mut self, row: Vec<Value>) {
259 if self.columns.is_empty() {
260 self.columns = (0..row.len()).map(|_| ColumnBuffer::new()).collect();
261 }
262 assert_eq!(
263 row.len(),
264 self.columns.len(),
265 "MaterializedBatch: row arity {} != column count {}",
266 row.len(),
267 self.columns.len()
268 );
269 for (col, v) in row.into_iter().enumerate() {
270 self.columns[col].push(v);
271 }
272 self.num_rows += 1;
273 }
274
275 pub fn append_column(&mut self, values: Vec<Value>) {
280 if self.columns.is_empty() {
281 self.columns.push(ColumnBuffer { values });
282 self.num_rows = self.columns[0].len();
283 return;
284 }
285 assert_eq!(
286 values.len(),
287 self.num_rows,
288 "MaterializedBatch: column length {} != row count {}",
289 values.len(),
290 self.num_rows
291 );
292 self.columns.push(ColumnBuffer { values });
293 }
294
295 pub fn column(&self, i: usize) -> &ColumnBuffer {
297 &self.columns[i]
298 }
299
300 pub fn row(&self, i: usize) -> Vec<Value> {
302 match &self.selection {
303 Some(sel) => {
304 let phys = sel.indices[i];
305 self.columns
306 .iter()
307 .map(|c| c.values[phys].clone())
308 .collect()
309 }
310 None => self.columns.iter().map(|c| c.values[i].clone()).collect(),
311 }
312 }
313
314 pub fn rows(&self) -> impl Iterator<Item = Vec<Value>> + '_ {
316 let n = self.num_rows();
317 (0..n).map(move |i| self.row(i))
318 }
319
320 pub fn to_rows(&self) -> Vec<Vec<Value>> {
322 self.rows().collect()
323 }
324
325 pub fn into_rows(&mut self) -> Vec<Vec<Value>> {
330 let logical = self.num_rows();
331 let columns = std::mem::take(&mut self.columns);
332 let mut rows = vec![Vec::with_capacity(columns.len()); logical];
333 match &self.selection {
334 Some(sel) => {
335 for (logical_idx, &phys) in sel.indices.iter().enumerate() {
336 for col in columns.iter() {
337 rows[logical_idx].push(col.values[phys].clone());
338 }
339 }
340 }
341 None => {
342 for col in columns.into_iter() {
343 for (r, v) in col.values.into_iter().enumerate() {
344 rows[r].push(v);
345 }
346 }
347 }
348 }
349 self.num_rows = 0;
350 rows
351 }
352
353 pub fn memory_size(&self) -> usize {
360 let values: usize = self.columns.iter().map(|c| c.values.len()).sum();
361 values * std::mem::size_of::<Value>() + self.columns.len() * 24
362 }
363
364 pub fn set_selection(&mut self, sel: SelectionVector) {
369 self.selection = Some(sel);
370 }
371
372 pub fn clear_selection(&mut self) {
374 self.selection = None;
375 }
376
377 pub fn refresh_fingerprint(&mut self) {
382 if let Some(names) = self.schema_names.clone() {
383 self.schema_fingerprint = column_name_fingerprint(&names);
384 return;
385 }
386 let mut h = DefaultHasher::new();
387 self.columns.len().hash(&mut h);
388 self.schema_fingerprint = h.finish();
389 }
390
391 pub fn refresh_fingerprint_from_names(&mut self, names: &[String]) {
393 self.schema_fingerprint = column_name_fingerprint(names);
394 }
395
396 pub fn clear(&mut self) {
398 for col in &mut self.columns {
399 col.clear();
400 }
401 self.num_rows = 0;
402 self.selection = None;
403 }
404
405 pub fn column_slice(&self, i: usize) -> &[Value] {
407 self.columns[i].as_slice()
408 }
409
410 pub fn slice_rows(&self, offset: usize, len: usize) -> MaterializedBatch {
415 let total = self.num_rows();
416 if offset >= total || len == 0 {
417 let mut out = MaterializedBatch::new(self.columns.len(), self.schema_fingerprint);
418 out.schema_names = self.schema_names.clone();
419 return out;
420 }
421 let end = (offset + len).min(total);
422 let mut out = MaterializedBatch::new(0, self.schema_fingerprint);
423 out.schema_names = self.schema_names.clone();
424 out.columns = self.columns.iter().map(|_| ColumnBuffer::new()).collect();
425 match &self.selection {
426 Some(sel) => {
427 for logical in offset..end {
428 let phys = sel.indices[logical];
429 for (dst, src) in out.columns.iter_mut().zip(self.columns.iter()) {
430 dst.push(src.values[phys].clone());
431 }
432 }
433 }
434 None => {
435 for (dst, src) in out.columns.iter_mut().zip(self.columns.iter()) {
436 dst.values.extend_from_slice(&src.values[offset..end]);
437 }
440 }
441 }
442 out.num_rows = end - offset;
443 out
444 }
445
446 pub fn gather(&self, indices: &[usize]) -> MaterializedBatch {
448 let mut out = MaterializedBatch::new(0, self.schema_fingerprint);
449 out.schema_names = self.schema_names.clone();
450 out.columns = self.columns.iter().map(|_| ColumnBuffer::new()).collect();
451 for &logical in indices {
454 let phys = match &self.selection {
455 Some(sel) => sel.indices[logical],
456 None => logical,
457 };
458 for (dst, src) in out.columns.iter_mut().zip(self.columns.iter()) {
459 dst.push(src.values[phys].clone());
460 }
461 }
462 out.num_rows = indices.len();
463 out
464 }
465
466 pub fn hash_rows(&self, cols: &[usize]) -> Vec<u64> {
473 let n = self.num_rows();
474 let mut out = Vec::with_capacity(n);
475 let use_all = cols.is_empty();
476 for logical in 0..n {
477 let phys = match &self.selection {
478 Some(sel) => sel.indices[logical],
479 None => logical,
480 };
481 let mut hash = COLUMNAR_HASH_SEED;
482 if use_all {
483 for col in &self.columns {
484 let bytes = postcard::to_allocvec(&col.values[phys]).unwrap_or_default();
485 hash = fnv1a_64_update(hash, &bytes);
486 hash = fnv1a_64_update(hash, &[0xff]);
487 }
488 } else {
489 for &c in cols {
490 let bytes =
491 postcard::to_allocvec(&self.columns[c].values[phys]).unwrap_or_default();
492 hash = fnv1a_64_update(hash, &bytes);
493 hash = fnv1a_64_update(hash, &[0xff]);
494 }
495 }
496 out.push(hash);
497 }
498 out
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::value::Value;
506
507 fn row_i64(v: i64) -> Vec<Value> {
508 vec![Value::BigInt(v), Value::string(format!("k{v}"))]
509 }
510
511 #[test]
512 fn row_round_trip_preserves_order_and_arity() {
513 let rows = vec![row_i64(1), row_i64(2), row_i64(3)];
514 let batch = MaterializedBatch::from_rows(rows.clone());
515 assert_eq!(batch.num_rows(), 3);
516 assert_eq!(batch.num_columns(), 2);
517 assert_eq!(batch.to_rows(), rows);
518 }
519
520 #[test]
521 fn column_major_layout_is_contiguous() {
522 let batch = MaterializedBatch::from_rows(vec![row_i64(1), row_i64(2)]);
523 let c0 = batch.column(0).as_slice();
524 assert_eq!(c0, &[Value::BigInt(1), Value::BigInt(2)]);
525 let c1 = batch.column(1).as_slice();
526 assert_eq!(c1, &[Value::string("k1"), Value::string("k2")]);
527 }
528
529 #[test]
530 fn into_rows_moves_without_clone_and_preserves_values() {
531 let rows = vec![row_i64(10), row_i64(20)];
532 let mut batch = MaterializedBatch::from_rows(rows.clone());
533 assert_eq!(batch.into_rows(), rows);
534 }
535
536 #[test]
537 fn empty_batch_is_empty() {
538 let batch = MaterializedBatch::new(2, 0);
539 assert!(batch.is_empty());
540 assert_eq!(batch.num_rows(), 0);
541 assert_eq!(batch.to_rows(), Vec::<Vec<Value>>::new());
542 }
543
544 #[test]
545 fn selection_vector_reorders_logical_rows() {
546 let batch = MaterializedBatch::from_rows(vec![row_i64(1), row_i64(2), row_i64(3)]);
547 let mut batch = batch;
548 batch.set_selection(SelectionVector {
549 indices: vec![2, 0],
550 });
551 assert_eq!(batch.num_rows(), 2);
552 assert_eq!(batch.row(0), row_i64(3));
553 assert_eq!(batch.row(1), row_i64(1));
554 let out = batch.into_rows();
556 assert_eq!(out, vec![row_i64(3), row_i64(1)]);
557 }
558
559 #[test]
560 fn memory_size_is_positive() {
561 let batch = MaterializedBatch::from_rows(vec![row_i64(1), row_i64(2)]);
562 assert!(batch.memory_size() > 0);
563 }
564
565 #[test]
566 fn fingerprint_is_column_name_sensitive() {
567 let a: Arc<[String]> = Arc::from(vec!["id".to_string(), "name".to_string()]);
568 let b: Arc<[String]> = Arc::from(vec!["id".to_string(), "other".to_string()]);
569 let ba = MaterializedBatch::with_schema_names(Arc::clone(&a));
570 let bb = MaterializedBatch::with_schema_names(Arc::clone(&b));
571 assert_ne!(ba.schema_fingerprint(), bb.schema_fingerprint());
572 assert_eq!(ba.schema_fingerprint(), column_name_fingerprint(&a));
573
574 let mut legacy = MaterializedBatch::from_rows(vec![row_i64(1)]);
577 legacy.refresh_fingerprint();
578 let mut named = MaterializedBatch::from_rows(vec![row_i64(1)]);
579 named.set_schema_names(Arc::from(vec!["x".to_string(), "y".to_string()]));
580 named.refresh_fingerprint();
581 assert_eq!(
582 named.schema_fingerprint(),
583 column_name_fingerprint(&["x".to_string(), "y".to_string()])
584 );
585 }
586
587 #[test]
588 fn slice_and_gather_respect_selection() {
589 let mut batch = MaterializedBatch::from_rows(vec![row_i64(1), row_i64(2), row_i64(3)]);
590 batch.set_selection(SelectionVector {
591 indices: vec![2, 0, 1],
592 });
593 let sliced = batch.slice_rows(1, 5);
594 assert_eq!(sliced.to_rows(), vec![row_i64(1), row_i64(2)]);
595 let gathered = batch.gather(&[0, 0, 2]);
596 assert_eq!(gathered.to_rows(), vec![row_i64(3), row_i64(3), row_i64(2)]);
597 let empty = batch.slice_rows(10, 3);
598 assert!(empty.is_empty());
599 }
600
601 #[test]
602 fn append_column_extends_schema() {
603 let mut batch = MaterializedBatch::new(0, 0);
604 batch.append_column(vec![Value::BigInt(1), Value::BigInt(2)]);
605 batch.append_column(vec![Value::string("a"), Value::string("b")]);
606 assert_eq!(batch.num_columns(), 2);
607 assert_eq!(batch.num_rows(), 2);
608 assert_eq!(
609 batch.to_rows(),
610 vec![
611 vec![Value::BigInt(1), Value::string("a")],
612 vec![Value::BigInt(2), Value::string("b")],
613 ]
614 );
615 }
616
617 #[test]
618 fn hash_rows_partition_matches_row_set_equality() {
619 use crate::value::NullType;
620 use std::collections::HashSet;
621 let rows = vec![
622 vec![Value::BigInt(1), Value::string("k")],
623 vec![Value::BigInt(1), Value::string("k")],
624 vec![Value::BigInt(2), Value::string("k")],
625 vec![Value::Null(NullType::Null), Value::string("k")],
626 vec![Value::Null(NullType::Null), Value::string("k")],
627 vec![Value::Double(f64::NAN), Value::Int(1)],
628 vec![Value::Int(1), Value::BigInt(1)],
629 ];
630 let batch = MaterializedBatch::from_rows(rows.clone());
631 let hashes = batch.hash_rows(&[]);
632 assert_eq!(hashes[0], hashes[1]);
634 assert_eq!(hashes[3], hashes[4]);
635 assert_ne!(hashes[0], hashes[2]);
638 let dup = MaterializedBatch::from_rows(vec![vec![Value::Double(f64::NAN), Value::Int(1)]]);
639 assert_eq!(dup.hash_rows(&[])[0], hashes[5]);
640 let sub = batch.hash_rows(&[0]);
642 assert_eq!(sub[0], sub[1]);
643 let mut set: HashSet<Vec<Value>> = HashSet::new();
646 for (i, row) in rows.iter().enumerate() {
647 if !set.contains(row) {
648 set.insert(row.clone());
649 for (j, other) in rows.iter().enumerate() {
650 if row == other {
651 assert_eq!(hashes[i], hashes[j], "equal rows must share hash");
652 }
653 }
654 }
655 }
656 }
657}