1use hawdb_core::{HawDBError, LogicalType, Result, Value, ValueRef};
18use hawdb_plan_cypher::ComparisonOp;
19use hawdb_storage::relational::{RelationalKey, RelationalValue};
20use std::sync::Arc;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct SlotId(pub u32);
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ColumnType {
27 Bool,
28 Int64,
29 Float64,
30 Utf8,
31 NodeId,
32 RelationalRowLocator,
33 Dynamic,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum SlotType {
43 Logical(LogicalType),
44 NodeId,
45 RelationalRowLocator,
46}
47
48impl SlotType {
49 pub const fn logical(logical_type: LogicalType) -> Self {
50 Self::Logical(logical_type)
51 }
52
53 pub const fn logical_type(self) -> Option<LogicalType> {
54 match self {
55 Self::Logical(logical_type) => Some(logical_type),
56 Self::NodeId => Some(LogicalType::Int64),
57 Self::RelationalRowLocator => None,
58 }
59 }
60
61 pub const fn accepts(self, column_type: ColumnType) -> bool {
62 match (self, column_type) {
63 (
64 Self::Logical(LogicalType::Any),
65 ColumnType::Bool
66 | ColumnType::Int64
67 | ColumnType::Float64
68 | ColumnType::Utf8
69 | ColumnType::Dynamic,
70 )
71 | (Self::Logical(LogicalType::Boolean), ColumnType::Bool)
72 | (Self::Logical(LogicalType::Int64), ColumnType::Int64)
73 | (Self::Logical(LogicalType::Float64), ColumnType::Float64)
74 | (Self::Logical(LogicalType::String | LogicalType::Text), ColumnType::Utf8)
75 | (Self::NodeId, ColumnType::NodeId)
76 | (Self::RelationalRowLocator, ColumnType::RelationalRowLocator) => true,
77 (Self::Logical(logical_type), ColumnType::Dynamic) => {
78 !matches!(logical_type, LogicalType::Binary)
79 }
80 _ => false,
81 }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct RelationalRowLocator {
87 table_id: u32,
88 primary_key: RelationalKey,
89}
90
91impl RelationalRowLocator {
92 pub fn new(table_id: u32, primary_key: RelationalKey) -> Self {
93 Self {
94 table_id,
95 primary_key,
96 }
97 }
98
99 pub fn table_id(&self) -> u32 {
100 self.table_id
101 }
102
103 pub fn primary_key(&self) -> &RelationalKey {
104 &self.primary_key
105 }
106
107 pub fn allocated_bytes(&self) -> usize {
108 self.primary_key
109 .0
110 .capacity()
111 .saturating_mul(std::mem::size_of::<RelationalValue>())
112 .saturating_add(
113 self.primary_key
114 .0
115 .iter()
116 .map(|value| match value {
117 RelationalValue::Text(value) => value.capacity(),
118 RelationalValue::Bytea(value) => value.capacity(),
119 RelationalValue::Null
120 | RelationalValue::Boolean(_)
121 | RelationalValue::BigInt(_)
122 | RelationalValue::DoublePrecision(_)
123 | RelationalValue::Uuid(_)
124 | RelationalValue::Overflow(_) => 0,
125 })
126 .sum::<usize>(),
127 )
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct SlotDescriptor {
133 pub id: SlotId,
134 pub name: String,
135 pub slot_type: SlotType,
136}
137
138impl SlotDescriptor {
139 pub const fn logical_type(&self) -> Option<LogicalType> {
140 self.slot_type.logical_type()
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct BindingSchema {
146 slots: Arc<[SlotDescriptor]>,
147}
148
149impl BindingSchema {
150 pub fn try_new(slots: Vec<SlotDescriptor>) -> Result<Self> {
151 for (index, slot) in slots.iter().enumerate() {
152 if slot.id.0 as usize != index {
153 return Err(HawDBError::Execution(format!(
154 "columnar schema slot ids must be dense: expected {index}, got {}",
155 slot.id.0
156 )));
157 }
158 }
159 Ok(Self {
160 slots: slots.into(),
161 })
162 }
163
164 pub fn slots(&self) -> &[SlotDescriptor] {
165 &self.slots
166 }
167
168 pub fn slot(&self, id: SlotId) -> Option<&SlotDescriptor> {
169 self.slots.get(id.0 as usize)
170 }
171
172 pub fn slot_by_name(&self, name: &str) -> Option<&SlotDescriptor> {
173 self.slots.iter().find(|slot| slot.name == name)
174 }
175
176 pub fn len(&self) -> usize {
177 self.slots.len()
178 }
179
180 pub fn is_empty(&self) -> bool {
181 self.slots.is_empty()
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub enum Validity {
187 All { len: usize },
188 Bitmap { len: usize, words: Arc<[u64]> },
189}
190
191impl Validity {
192 pub fn all(len: usize) -> Self {
193 Self::All { len }
194 }
195
196 pub fn len(&self) -> usize {
197 match self {
198 Self::All { len } | Self::Bitmap { len, .. } => *len,
199 }
200 }
201
202 pub fn is_empty(&self) -> bool {
203 self.len() == 0
204 }
205
206 pub fn is_valid(&self, row: usize) -> bool {
207 if row >= self.len() {
208 return false;
209 }
210 match self {
211 Self::All { .. } => true,
212 Self::Bitmap { words, .. } => words
213 .get(row / u64::BITS as usize)
214 .is_some_and(|word| word & (1u64 << (row % u64::BITS as usize)) != 0),
215 }
216 }
217
218 pub fn valid_count(&self) -> usize {
219 match self {
220 Self::All { len } => *len,
221 Self::Bitmap { len, words } => count_bitmap_rows(words, *len),
222 }
223 }
224
225 pub fn view(&self) -> ValidityView<'_> {
226 match self {
227 Self::All { len } => ValidityView::All { len: *len },
228 Self::Bitmap { len, words } => ValidityView::Bitmap { len: *len, words },
229 }
230 }
231}
232
233#[derive(Debug, Clone, Copy)]
234pub enum ValidityView<'a> {
235 All { len: usize },
236 Bitmap { len: usize, words: &'a [u64] },
237}
238
239impl ValidityView<'_> {
240 pub fn len(self) -> usize {
241 match self {
242 Self::All { len } | Self::Bitmap { len, .. } => len,
243 }
244 }
245
246 pub fn is_empty(self) -> bool {
247 self.len() == 0
248 }
249
250 pub fn is_valid(self, row: usize) -> bool {
251 if row >= self.len() {
252 return false;
253 }
254 match self {
255 Self::All { .. } => true,
256 Self::Bitmap { words, .. } => words
257 .get(row / u64::BITS as usize)
258 .is_some_and(|word| word & (1u64 << (row % u64::BITS as usize)) != 0),
259 }
260 }
261
262 pub fn valid_count(self) -> usize {
263 match self {
264 Self::All { len } => len,
265 Self::Bitmap { len, words } => count_bitmap_rows(words, len),
266 }
267 }
268
269 pub fn to_owned(self) -> Validity {
270 match self {
271 Self::All { len } => Validity::All { len },
272 Self::Bitmap { len, words } => Validity::Bitmap {
273 len,
274 words: words.to_vec().into(),
275 },
276 }
277 }
278}
279
280#[derive(Debug, Default)]
281pub struct ValidityBuilder {
282 len: usize,
283 words: Vec<u64>,
284 word_capacity: usize,
285}
286
287impl ValidityBuilder {
288 pub fn with_capacity(rows: usize) -> Self {
289 Self {
290 len: 0,
291 words: Vec::new(),
292 word_capacity: rows.div_ceil(u64::BITS as usize),
293 }
294 }
295
296 pub fn push(&mut self, valid: bool) {
297 let word_index = self.len / u64::BITS as usize;
298 let bit_index = self.len % u64::BITS as usize;
299 if self.words.is_empty() {
300 if !valid {
301 if self.words.capacity() < self.word_capacity {
302 self.words.reserve(self.word_capacity);
303 }
304 self.words.resize(word_index + 1, u64::MAX);
305 self.words[word_index] = if bit_index == 0 {
306 0
307 } else {
308 (1u64 << bit_index) - 1
309 };
310 }
311 } else {
312 if word_index == self.words.len() {
313 self.words.push(0);
314 }
315 if valid {
316 self.words[word_index] |= 1u64 << bit_index;
317 }
318 }
319 self.len = self.len.saturating_add(1);
320 }
321
322 pub fn clear(&mut self) {
323 self.len = 0;
324 self.words.clear();
325 }
326
327 pub fn view(&self) -> ValidityView<'_> {
328 if self.words.is_empty() {
329 ValidityView::All { len: self.len }
330 } else {
331 ValidityView::Bitmap {
332 len: self.len,
333 words: &self.words,
334 }
335 }
336 }
337
338 pub fn finish(self) -> Validity {
339 if self.words.is_empty() {
340 Validity::All { len: self.len }
341 } else {
342 Validity::Bitmap {
343 len: self.len,
344 words: self.words.into(),
345 }
346 }
347 }
348}
349
350#[derive(Debug, Clone, PartialEq)]
351pub enum ColumnVector {
352 Bool {
353 values: Arc<[u8]>,
354 validity: Validity,
355 },
356 Int64 {
357 values: Arc<[i64]>,
358 validity: Validity,
359 },
360 Float64 {
361 values: Arc<[f64]>,
362 validity: Validity,
363 },
364 Utf8 {
365 values: Arc<[String]>,
366 validity: Validity,
367 },
368 NodeId(Arc<[u64]>),
369 RelationalRowLocator(Arc<[RelationalRowLocator]>),
370 Dynamic(Arc<[Value]>),
371}
372
373impl ColumnVector {
374 pub fn boolean(values: Vec<bool>, validity: Validity) -> Result<Self> {
375 Self::boolean_bytes(values.into_iter().map(u8::from).collect(), validity)
376 }
377
378 pub fn boolean_bytes(values: Vec<u8>, validity: Validity) -> Result<Self> {
379 ensure_column_len("Bool", values.len(), validity.len())?;
380 if values.iter().any(|value| *value > 1) {
381 return Err(HawDBError::Execution(
382 "Bool column contains a value other than 0 or 1".to_string(),
383 ));
384 }
385 Ok(Self::Bool {
386 values: values.into(),
387 validity,
388 })
389 }
390
391 pub fn int64(values: Vec<i64>, validity: Validity) -> Result<Self> {
392 ensure_column_len("Int64", values.len(), validity.len())?;
393 Ok(Self::Int64 {
394 values: values.into(),
395 validity,
396 })
397 }
398
399 pub fn float64(values: Vec<f64>, validity: Validity) -> Result<Self> {
400 ensure_column_len("Float64", values.len(), validity.len())?;
401 Ok(Self::Float64 {
402 values: values.into(),
403 validity,
404 })
405 }
406
407 pub fn node_ids(values: Vec<u64>) -> Self {
408 Self::NodeId(values.into())
409 }
410
411 pub fn relational_row_locators(values: Vec<RelationalRowLocator>) -> Self {
412 Self::RelationalRowLocator(values.into())
413 }
414
415 pub fn len(&self) -> usize {
416 match self {
417 Self::Bool { values, .. } => values.len(),
418 Self::Int64 { values, .. } => values.len(),
419 Self::Float64 { values, .. } => values.len(),
420 Self::Utf8 { values, .. } => values.len(),
421 Self::NodeId(values) => values.len(),
422 Self::RelationalRowLocator(values) => values.len(),
423 Self::Dynamic(values) => values.len(),
424 }
425 }
426
427 pub fn is_empty(&self) -> bool {
428 self.len() == 0
429 }
430
431 pub fn column_type(&self) -> ColumnType {
432 match self {
433 Self::Bool { .. } => ColumnType::Bool,
434 Self::Int64 { .. } => ColumnType::Int64,
435 Self::Float64 { .. } => ColumnType::Float64,
436 Self::Utf8 { .. } => ColumnType::Utf8,
437 Self::NodeId(_) => ColumnType::NodeId,
438 Self::RelationalRowLocator(_) => ColumnType::RelationalRowLocator,
439 Self::Dynamic(_) => ColumnType::Dynamic,
440 }
441 }
442
443 pub fn is_valid(&self, row: usize) -> bool {
444 match self {
445 Self::Bool { validity, .. }
446 | Self::Int64 { validity, .. }
447 | Self::Float64 { validity, .. }
448 | Self::Utf8 { validity, .. } => validity.is_valid(row),
449 Self::NodeId(values) => row < values.len(),
450 Self::RelationalRowLocator(values) => row < values.len(),
451 Self::Dynamic(values) => values.get(row).is_some_and(|value| *value != Value::Null),
452 }
453 }
454
455 pub fn value_ref(&self, row: usize) -> Option<ValueRef<'_>> {
456 match self {
457 Self::Bool { values, validity } if validity.is_valid(row) => {
458 values.get(row).map(|value| ValueRef::Bool(*value != 0))
459 }
460 Self::Int64 { values, validity } if validity.is_valid(row) => {
461 values.get(row).copied().map(ValueRef::Int)
462 }
463 Self::Float64 { values, validity } if validity.is_valid(row) => {
464 values.get(row).copied().map(ValueRef::Float)
465 }
466 Self::Utf8 { values, validity } if validity.is_valid(row) => {
467 values.get(row).map(|value| ValueRef::String(value))
468 }
469 Self::Bool { .. } | Self::Int64 { .. } | Self::Float64 { .. } | Self::Utf8 { .. }
470 if row < self.len() =>
471 {
472 Some(ValueRef::Null)
473 }
474 Self::Bool { .. } | Self::Int64 { .. } | Self::Float64 { .. } | Self::Utf8 { .. } => {
475 None
476 }
477 Self::NodeId(values) => values.get(row).map(|value| ValueRef::Int(*value as i64)),
478 Self::RelationalRowLocator(_) => None,
479 Self::Dynamic(values) => values.get(row).map(Value::as_ref),
480 }
481 }
482
483 pub fn value(&self, row: usize) -> Option<Value> {
484 self.value_ref(row).map(ValueRef::to_owned_value)
485 }
486
487 pub fn relational_row_locator(&self, row: usize) -> Option<&RelationalRowLocator> {
488 let Self::RelationalRowLocator(values) = self else {
489 return None;
490 };
491 values.get(row)
492 }
493
494 pub fn estimated_memory_bytes(&self) -> usize {
495 let validity_bytes = match self {
496 Self::Bool { validity, .. }
497 | Self::Int64 { validity, .. }
498 | Self::Float64 { validity, .. }
499 | Self::Utf8 { validity, .. } => match validity {
500 Validity::All { .. } => 0,
501 Validity::Bitmap { words, .. } => words.len() * std::mem::size_of::<u64>(),
502 },
503 Self::NodeId(_) | Self::RelationalRowLocator(_) | Self::Dynamic(_) => 0,
504 };
505 validity_bytes.saturating_add(match self {
506 Self::Bool { values, .. } => values.len(),
507 Self::Int64 { values, .. } => values.len() * std::mem::size_of::<i64>(),
508 Self::Float64 { values, .. } => values.len() * std::mem::size_of::<f64>(),
509 Self::Utf8 { values, .. } => values.iter().fold(
510 values.len() * std::mem::size_of::<String>(),
511 |total, value| total.saturating_add(value.len()),
512 ),
513 Self::NodeId(values) => values.len() * std::mem::size_of::<u64>(),
514 Self::RelationalRowLocator(values) => values.iter().fold(
515 values
516 .len()
517 .saturating_mul(std::mem::size_of::<RelationalRowLocator>()),
518 |total, locator| total.saturating_add(locator.allocated_bytes()),
519 ),
520 Self::Dynamic(values) => values.len() * std::mem::size_of::<Value>(),
521 })
522 }
523}
524
525fn ensure_column_len(kind: &str, values: usize, validity: usize) -> Result<()> {
526 if values != validity {
527 return Err(HawDBError::Execution(format!(
528 "{kind} column has {values} values but {validity} validity entries"
529 )));
530 }
531 Ok(())
532}
533
534#[derive(Debug, Clone, PartialEq, Eq)]
535pub enum Selection {
536 All {
537 len: usize,
538 },
539 Bitmap {
540 len: usize,
541 words: Arc<[u64]>,
542 selected: usize,
543 },
544 Indices {
545 len: usize,
546 rows: Arc<[u32]>,
547 },
548}
549
550impl Selection {
551 pub fn all(len: usize) -> Self {
552 Self::All { len }
553 }
554
555 pub fn none(len: usize) -> Self {
556 Self::Indices {
557 len,
558 rows: Arc::from([]),
559 }
560 }
561
562 pub fn len(&self) -> usize {
563 match self {
564 Self::All { len } | Self::Bitmap { len, .. } | Self::Indices { len, .. } => *len,
565 }
566 }
567
568 pub fn selected_count(&self) -> usize {
569 match self {
570 Self::All { len } => *len,
571 Self::Bitmap { selected, .. } => *selected,
572 Self::Indices { rows, .. } => rows.len(),
573 }
574 }
575
576 pub fn is_empty(&self) -> bool {
577 self.selected_count() == 0
578 }
579
580 pub fn iter(&self) -> SelectionIter<'_> {
581 SelectionIter {
582 selection: self,
583 cursor: 0,
584 }
585 }
586
587 pub fn limit(&self, offset: usize, max_rows: usize) -> Self {
588 let mut output = SelectionBuilder::new(self.len());
589 for row in self.iter().skip(offset).take(max_rows) {
590 output.select(row);
591 }
592 output.finish()
593 }
594
595 pub fn estimated_memory_bytes(&self) -> usize {
596 match self {
597 Self::All { .. } => 0,
598 Self::Bitmap { words, .. } => words.len() * std::mem::size_of::<u64>(),
599 Self::Indices { rows, .. } => rows.len() * std::mem::size_of::<u32>(),
600 }
601 }
602}
603
604struct SelectionBuilder {
605 len: usize,
606 selected: usize,
607 sparse_rows: Vec<u32>,
608 bitmap_words: Option<Vec<u64>>,
609}
610
611impl SelectionBuilder {
612 fn new(len: usize) -> Self {
613 Self {
614 len,
615 selected: 0,
616 sparse_rows: Vec::new(),
617 bitmap_words: (len > u32::MAX as usize)
618 .then(|| vec![0; len.div_ceil(u64::BITS as usize)]),
619 }
620 }
621
622 fn select(&mut self, row: usize) {
623 self.selected = self.selected.saturating_add(1);
624 if let Some(words) = &mut self.bitmap_words {
625 words[row / u64::BITS as usize] |= 1u64 << (row % u64::BITS as usize);
626 return;
627 }
628
629 self.sparse_rows.push(row as u32);
630 if self.selected.saturating_mul(8) > self.len {
631 let rows = std::mem::take(&mut self.sparse_rows);
632 let mut words = vec![0; self.len.div_ceil(u64::BITS as usize)];
633 for row in rows {
634 let row = row as usize;
635 words[row / u64::BITS as usize] |= 1u64 << (row % u64::BITS as usize);
636 }
637 self.bitmap_words = Some(words);
638 }
639 }
640
641 fn finish(self) -> Selection {
642 if self.selected == self.len {
643 return Selection::all(self.len);
644 }
645 if self.selected == 0 {
646 return Selection::none(self.len);
647 }
648 match self.bitmap_words {
649 Some(words) => Selection::Bitmap {
650 len: self.len,
651 words: words.into(),
652 selected: self.selected,
653 },
654 None => Selection::Indices {
655 len: self.len,
656 rows: self.sparse_rows.into(),
657 },
658 }
659 }
660}
661
662pub struct SelectionIter<'a> {
663 selection: &'a Selection,
664 cursor: usize,
665}
666
667impl Iterator for SelectionIter<'_> {
668 type Item = usize;
669
670 fn next(&mut self) -> Option<Self::Item> {
671 match self.selection {
672 Selection::All { len } => {
673 let row = self.cursor;
674 if row == *len {
675 return None;
676 }
677 self.cursor = self.cursor.saturating_add(1);
678 Some(row)
679 }
680 Selection::Indices { rows, .. } => {
681 let row = rows.get(self.cursor).copied()? as usize;
682 self.cursor = self.cursor.saturating_add(1);
683 Some(row)
684 }
685 Selection::Bitmap { len, words, .. } => {
686 while self.cursor < *len {
687 let row = self.cursor;
688 self.cursor = self.cursor.saturating_add(1);
689 if words[row / u64::BITS as usize] & (1u64 << (row % u64::BITS as usize)) != 0 {
690 return Some(row);
691 }
692 }
693 None
694 }
695 }
696 }
697}
698
699#[derive(Debug, Clone, PartialEq)]
700pub struct ColumnarBatch {
701 schema: Arc<BindingSchema>,
702 columns: Vec<Arc<ColumnVector>>,
703 selection: Selection,
704 row_count: usize,
705}
706
707#[derive(Debug, Clone, Copy)]
708pub struct ColumnarRowRef<'a> {
709 batch: &'a ColumnarBatch,
710 row: usize,
711}
712
713impl<'a> ColumnarRowRef<'a> {
714 pub fn row_index(self) -> usize {
715 self.row
716 }
717
718 pub fn schema(self) -> &'a BindingSchema {
719 self.batch.schema()
720 }
721
722 pub fn value(self, slot: SlotId) -> Option<ValueRef<'a>> {
723 self.batch.value_ref(slot, self.row)
724 }
725
726 pub fn get(self, name: &str) -> Option<ValueRef<'a>> {
727 let slot = self.schema().slot_by_name(name)?;
728 self.value(slot.id)
729 }
730
731 pub fn column(self, index: usize) -> Option<(&'a str, ValueRef<'a>)> {
732 let slot = self.schema().slots().get(index)?;
733 self.value(slot.id).map(|value| (slot.name.as_str(), value))
734 }
735}
736
737impl ColumnarBatch {
738 pub fn try_new(schema: Arc<BindingSchema>, columns: Vec<Arc<ColumnVector>>) -> Result<Self> {
739 if schema.len() != columns.len() {
740 return Err(HawDBError::Execution(format!(
741 "columnar batch has {} slots but {} columns",
742 schema.len(),
743 columns.len()
744 )));
745 }
746 let row_count = columns.first().map_or(0, |column| column.len());
747 for (slot, column) in schema.slots().iter().zip(&columns) {
748 if column.len() != row_count {
749 return Err(HawDBError::Execution(format!(
750 "columnar slot '{}' has {} rows, expected {row_count}",
751 slot.name,
752 column.len()
753 )));
754 }
755 if !slot.slot_type.accepts(column.column_type()) {
756 return Err(HawDBError::Execution(format!(
757 "columnar slot '{}' expects {:?}, got {:?}",
758 slot.name,
759 slot.slot_type,
760 column.column_type()
761 )));
762 }
763 }
764 Ok(Self {
765 schema,
766 columns,
767 selection: Selection::all(row_count),
768 row_count,
769 })
770 }
771
772 pub fn row_count(&self) -> usize {
773 self.row_count
774 }
775
776 pub fn selected_count(&self) -> usize {
777 self.selection.selected_count()
778 }
779
780 pub fn schema(&self) -> &BindingSchema {
781 &self.schema
782 }
783
784 pub fn column(&self, slot: SlotId) -> Option<&Arc<ColumnVector>> {
785 self.columns.get(slot.0 as usize)
786 }
787
788 pub fn value_ref(&self, slot: SlotId, row: usize) -> Option<ValueRef<'_>> {
789 self.column(slot)?.value_ref(row)
790 }
791
792 pub fn selection(&self) -> &Selection {
793 &self.selection
794 }
795
796 pub fn rows(&self) -> impl Iterator<Item = ColumnarRowRef<'_>> {
797 self.selection
798 .iter()
799 .map(|row| ColumnarRowRef { batch: self, row })
800 }
801
802 pub fn with_selection(mut self, selection: Selection) -> Result<Self> {
803 if selection.len() != self.row_count {
804 return Err(HawDBError::Execution(format!(
805 "columnar selection has {} rows, expected {}",
806 selection.len(),
807 self.row_count
808 )));
809 }
810 self.selection = selection;
811 Ok(self)
812 }
813
814 pub fn project(&self, slots: &[SlotId]) -> Result<Self> {
815 let mut descriptors = Vec::with_capacity(slots.len());
816 let mut columns = Vec::with_capacity(slots.len());
817 for (output_index, slot) in slots.iter().copied().enumerate() {
818 let descriptor = self.schema.slot(slot).ok_or_else(|| {
819 HawDBError::Execution(format!("unknown columnar slot {}", slot.0))
820 })?;
821 descriptors.push(SlotDescriptor {
822 id: SlotId(output_index as u32),
823 name: descriptor.name.clone(),
824 slot_type: descriptor.slot_type,
825 });
826 columns.push(Arc::clone(
827 self.column(slot).expect("schema and columns align"),
828 ));
829 }
830 Ok(Self {
831 schema: Arc::new(BindingSchema::try_new(descriptors)?),
832 columns,
833 selection: self.selection.clone(),
834 row_count: self.row_count,
835 })
836 }
837
838 pub fn filter_numeric(
839 &self,
840 slot: SlotId,
841 predicate: NumericPredicate,
842 expected: NumericLiteral,
843 ) -> Result<Self> {
844 let column = self.column(slot).ok_or_else(|| {
845 HawDBError::Execution(format!("unknown columnar filter slot {}", slot.0))
846 })?;
847 let selection = filter_numeric_column(column, &self.selection, predicate, expected)?;
848 self.clone().with_selection(selection)
849 }
850
851 pub fn filter_boolean(&self, slot: SlotId, expected: bool) -> Result<Self> {
852 let column = self.column(slot).ok_or_else(|| {
853 HawDBError::Execution(format!("unknown columnar filter slot {}", slot.0))
854 })?;
855 let selection = filter_boolean_column(column, &self.selection, expected)?;
856 self.clone().with_selection(selection)
857 }
858
859 pub fn limit(&self, offset: usize, max_rows: usize) -> Self {
860 Self {
861 schema: Arc::clone(&self.schema),
862 columns: self.columns.clone(),
863 selection: self.selection.limit(offset, max_rows),
864 row_count: self.row_count,
865 }
866 }
867
868 pub fn count_selected(&self) -> usize {
869 self.selection.selected_count()
870 }
871
872 pub fn count_valid(&self, slot: SlotId) -> Result<usize> {
873 let column = self.column(slot).ok_or_else(|| {
874 HawDBError::Execution(format!("unknown columnar aggregate slot {}", slot.0))
875 })?;
876 Ok(self
877 .selection
878 .iter()
879 .filter(|row| column.is_valid(*row))
880 .count())
881 }
882
883 pub fn sum_int64(&self, slot: SlotId) -> Result<Option<i64>> {
884 let column = self.column(slot).ok_or_else(|| {
885 HawDBError::Execution(format!("unknown columnar aggregate slot {}", slot.0))
886 })?;
887 let ColumnVector::Int64 { values, validity } = column.as_ref() else {
888 return Err(HawDBError::Execution(format!(
889 "columnar SUM requires Int64, got {:?}",
890 column.column_type()
891 )));
892 };
893 let mut sum = None::<i64>;
894 for row in self.selection.iter() {
895 if validity.is_valid(row) {
896 sum =
897 Some(sum.unwrap_or(0).checked_add(values[row]).ok_or_else(|| {
898 HawDBError::Execution("columnar Int64 SUM overflow".into())
899 })?);
900 }
901 }
902 Ok(sum)
903 }
904
905 pub fn estimated_memory_bytes(&self) -> usize {
906 let schema_bytes = self.schema.slots().iter().fold(
907 self.schema.len() * std::mem::size_of::<SlotDescriptor>(),
908 |total, slot| total.saturating_add(slot.name.len()),
909 );
910 self.columns
911 .iter()
912 .fold(schema_bytes, |total, column| {
913 total.saturating_add(column.estimated_memory_bytes())
914 })
915 .saturating_add(self.selection.estimated_memory_bytes())
916 }
917}
918
919#[derive(Debug, Clone, Copy, PartialEq)]
920pub enum NumericLiteral {
921 Int(i64),
922 Float(f64),
923}
924
925#[derive(Debug, Clone, Copy, PartialEq, Eq)]
926pub enum NumericPredicate {
927 Eq,
928 Compare(ComparisonOp),
929}
930
931impl NumericLiteral {
932 pub fn from_value(value: &Value) -> Option<Self> {
933 Self::from_value_ref(value.as_ref())
934 }
935
936 pub fn from_value_ref(value: ValueRef<'_>) -> Option<Self> {
937 match value {
938 ValueRef::Int(value) => Some(Self::Int(value)),
939 ValueRef::Float(value) => Some(Self::Float(value)),
940 _ => None,
941 }
942 }
943}
944
945pub fn filter_numeric_column(
946 column: &ColumnVector,
947 input: &Selection,
948 predicate: NumericPredicate,
949 expected: NumericLiteral,
950) -> Result<Selection> {
951 if column.len() != input.len() {
952 return Err(HawDBError::Execution(format!(
953 "numeric filter column has {} rows but input selection has {}",
954 column.len(),
955 input.len()
956 )));
957 }
958 match column {
959 ColumnVector::Int64 { values, validity } => {
960 filter_int64_values(values, validity, input, predicate, expected)
961 }
962 ColumnVector::Float64 { values, validity } => {
963 filter_float64_values(values, validity, input, predicate, expected)
964 }
965 other => Err(HawDBError::Execution(format!(
966 "numeric filter requires Int64 or Float64, got {:?}",
967 other.column_type()
968 ))),
969 }
970}
971
972pub fn filter_boolean_column(
973 column: &ColumnVector,
974 input: &Selection,
975 expected: bool,
976) -> Result<Selection> {
977 if column.len() != input.len() {
978 return Err(HawDBError::Execution(format!(
979 "boolean filter column has {} rows but input selection has {}",
980 column.len(),
981 input.len()
982 )));
983 }
984 let ColumnVector::Bool { values, validity } = column else {
985 return Err(HawDBError::Execution(format!(
986 "boolean filter requires Bool, got {:?}",
987 column.column_type()
988 )));
989 };
990 let expected = u8::from(expected);
991 let mut output = SelectionBuilder::new(input.len());
992 for row in input.iter() {
993 if validity.is_valid(row) && values[row] == expected {
994 output.select(row);
995 }
996 }
997 Ok(output.finish())
998}
999
1000pub fn filter_int64_values(
1001 values: &[i64],
1002 validity: &Validity,
1003 input: &Selection,
1004 predicate: NumericPredicate,
1005 expected: NumericLiteral,
1006) -> Result<Selection> {
1007 filter_int64_values_view(values, validity.view(), input, predicate, expected)
1008}
1009
1010pub fn filter_int64_values_view(
1011 values: &[i64],
1012 validity: ValidityView<'_>,
1013 input: &Selection,
1014 predicate: NumericPredicate,
1015 expected: NumericLiteral,
1016) -> Result<Selection> {
1017 match predicate {
1018 NumericPredicate::Eq => match expected {
1019 NumericLiteral::Int(expected) => {
1020 filter_numeric_values(values, validity, input, |actual| actual == expected)
1021 }
1022 NumericLiteral::Float(_) => filter_numeric_values(values, validity, input, |_| false),
1023 },
1024 NumericPredicate::Compare(op) => match expected {
1025 NumericLiteral::Int(expected) => {
1026 filter_ordered_values(values, validity, input, op, expected, |actual| actual)
1027 }
1028 NumericLiteral::Float(expected) => filter_ordered_values(
1029 values,
1030 validity,
1031 input,
1032 op,
1033 float_total_order_key(expected),
1034 |actual| float_total_order_key(actual as f64),
1035 ),
1036 },
1037 }
1038}
1039
1040pub fn filter_float64_values(
1041 values: &[f64],
1042 validity: &Validity,
1043 input: &Selection,
1044 predicate: NumericPredicate,
1045 expected: NumericLiteral,
1046) -> Result<Selection> {
1047 filter_float64_values_view(values, validity.view(), input, predicate, expected)
1048}
1049
1050pub fn filter_float64_values_view(
1051 values: &[f64],
1052 validity: ValidityView<'_>,
1053 input: &Selection,
1054 predicate: NumericPredicate,
1055 expected: NumericLiteral,
1056) -> Result<Selection> {
1057 match predicate {
1058 NumericPredicate::Eq => match expected {
1059 NumericLiteral::Int(_) => filter_numeric_values(values, validity, input, |_| false),
1060 NumericLiteral::Float(expected) => {
1061 filter_numeric_values(values, validity, input, |actual| {
1062 actual.total_cmp(&expected).is_eq()
1063 })
1064 }
1065 },
1066 NumericPredicate::Compare(op) => {
1067 let expected = match expected {
1068 NumericLiteral::Int(expected) => expected as f64,
1069 NumericLiteral::Float(expected) => expected,
1070 };
1071 filter_ordered_values(
1072 values,
1073 validity,
1074 input,
1075 op,
1076 float_total_order_key(expected),
1077 float_total_order_key,
1078 )
1079 }
1080 }
1081}
1082
1083pub fn select_int64_values_view(
1084 values: &[i64],
1085 validity: ValidityView<'_>,
1086 predicate: NumericPredicate,
1087 expected: NumericLiteral,
1088 selected_rows: &mut Vec<u32>,
1089) -> Result<()> {
1090 match predicate {
1091 NumericPredicate::Eq => match expected {
1092 NumericLiteral::Int(expected) => {
1093 select_numeric_values(values, validity, selected_rows, |actual| actual == expected)
1094 }
1095 NumericLiteral::Float(_) => {
1096 select_numeric_values(values, validity, selected_rows, |_| false)
1097 }
1098 },
1099 NumericPredicate::Compare(op) => match expected {
1100 NumericLiteral::Int(expected) => {
1101 select_ordered_values(values, validity, selected_rows, op, expected, |actual| {
1102 actual
1103 })
1104 }
1105 NumericLiteral::Float(expected) => select_ordered_values(
1106 values,
1107 validity,
1108 selected_rows,
1109 op,
1110 float_total_order_key(expected),
1111 |actual| float_total_order_key(actual as f64),
1112 ),
1113 },
1114 }
1115}
1116
1117pub fn select_float64_values_view(
1118 values: &[f64],
1119 validity: ValidityView<'_>,
1120 predicate: NumericPredicate,
1121 expected: NumericLiteral,
1122 selected_rows: &mut Vec<u32>,
1123) -> Result<()> {
1124 match predicate {
1125 NumericPredicate::Eq => match expected {
1126 NumericLiteral::Int(_) => {
1127 select_numeric_values(values, validity, selected_rows, |_| false)
1128 }
1129 NumericLiteral::Float(expected) => {
1130 select_numeric_values(values, validity, selected_rows, |actual| {
1131 actual.total_cmp(&expected).is_eq()
1132 })
1133 }
1134 },
1135 NumericPredicate::Compare(op) => {
1136 let expected = match expected {
1137 NumericLiteral::Int(expected) => expected as f64,
1138 NumericLiteral::Float(expected) => expected,
1139 };
1140 select_ordered_values(
1141 values,
1142 validity,
1143 selected_rows,
1144 op,
1145 float_total_order_key(expected),
1146 float_total_order_key,
1147 )
1148 }
1149 }
1150}
1151
1152fn filter_ordered_values<T, K>(
1153 values: &[T],
1154 validity: ValidityView<'_>,
1155 input: &Selection,
1156 op: ComparisonOp,
1157 expected: K,
1158 key: impl Fn(T) -> K + Copy,
1159) -> Result<Selection>
1160where
1161 T: Copy,
1162 K: Copy + Ord,
1163{
1164 match op {
1165 ComparisonOp::Lt => {
1166 filter_numeric_values(values, validity, input, |actual| key(actual) < expected)
1167 }
1168 ComparisonOp::Lte => {
1169 filter_numeric_values(values, validity, input, |actual| key(actual) <= expected)
1170 }
1171 ComparisonOp::Gt => {
1172 filter_numeric_values(values, validity, input, |actual| key(actual) > expected)
1173 }
1174 ComparisonOp::Gte => {
1175 filter_numeric_values(values, validity, input, |actual| key(actual) >= expected)
1176 }
1177 }
1178}
1179
1180fn select_ordered_values<T, K>(
1181 values: &[T],
1182 validity: ValidityView<'_>,
1183 selected_rows: &mut Vec<u32>,
1184 op: ComparisonOp,
1185 expected: K,
1186 key: impl Fn(T) -> K + Copy,
1187) -> Result<()>
1188where
1189 T: Copy,
1190 K: Copy + Ord,
1191{
1192 match op {
1193 ComparisonOp::Lt => select_numeric_values(values, validity, selected_rows, |actual| {
1194 key(actual) < expected
1195 }),
1196 ComparisonOp::Lte => select_numeric_values(values, validity, selected_rows, |actual| {
1197 key(actual) <= expected
1198 }),
1199 ComparisonOp::Gt => select_numeric_values(values, validity, selected_rows, |actual| {
1200 key(actual) > expected
1201 }),
1202 ComparisonOp::Gte => select_numeric_values(values, validity, selected_rows, |actual| {
1203 key(actual) >= expected
1204 }),
1205 }
1206}
1207
1208fn select_numeric_values<T: Copy>(
1209 values: &[T],
1210 validity: ValidityView<'_>,
1211 selected_rows: &mut Vec<u32>,
1212 mut matches: impl FnMut(T) -> bool,
1213) -> Result<()> {
1214 if values.len() != validity.len() {
1215 return Err(HawDBError::Execution(format!(
1216 "numeric selection has {} values and {} validity entries",
1217 values.len(),
1218 validity.len()
1219 )));
1220 }
1221 if values.len() > u32::MAX as usize {
1222 return Err(HawDBError::Execution(format!(
1223 "numeric selection batch has {} rows, exceeding the u32 row index limit",
1224 values.len()
1225 )));
1226 }
1227 selected_rows.clear();
1228 if selected_rows.capacity() < values.len() {
1229 selected_rows.reserve(values.len());
1230 }
1231 match validity {
1232 ValidityView::All { .. } => {
1233 for (row, value) in values.iter().copied().enumerate() {
1234 if matches(value) {
1235 selected_rows.push(row as u32);
1236 }
1237 }
1238 }
1239 ValidityView::Bitmap { .. } => {
1240 for (row, value) in values.iter().copied().enumerate() {
1241 if validity.is_valid(row) && matches(value) {
1242 selected_rows.push(row as u32);
1243 }
1244 }
1245 }
1246 }
1247 Ok(())
1248}
1249
1250fn filter_numeric_values<T: Copy>(
1251 values: &[T],
1252 validity: ValidityView<'_>,
1253 input: &Selection,
1254 mut matches: impl FnMut(T) -> bool,
1255) -> Result<Selection> {
1256 if values.len() != validity.len() || values.len() != input.len() {
1257 return Err(HawDBError::Execution(format!(
1258 "numeric filter has {} values, {} validity entries, and {} selected input rows",
1259 values.len(),
1260 validity.len(),
1261 input.len()
1262 )));
1263 }
1264 let mut output = SelectionBuilder::new(input.len());
1265 for row in input.iter() {
1266 if validity.is_valid(row) && matches(values[row]) {
1267 output.select(row);
1268 }
1269 }
1270 Ok(output.finish())
1271}
1272
1273#[inline]
1274pub fn int64_value_matches(
1275 actual: i64,
1276 predicate: NumericPredicate,
1277 expected: NumericLiteral,
1278) -> bool {
1279 match predicate {
1280 NumericPredicate::Eq => {
1281 matches!(expected, NumericLiteral::Int(expected) if actual == expected)
1282 }
1283 NumericPredicate::Compare(op) => match expected {
1284 NumericLiteral::Int(expected) => compare_ordering(actual.cmp(&expected), op),
1285 NumericLiteral::Float(expected) => compare_ordering(
1286 float_total_order_key(actual as f64).cmp(&float_total_order_key(expected)),
1287 op,
1288 ),
1289 },
1290 }
1291}
1292
1293#[inline]
1294pub fn float64_value_matches(
1295 actual: f64,
1296 predicate: NumericPredicate,
1297 expected: NumericLiteral,
1298) -> bool {
1299 match predicate {
1300 NumericPredicate::Eq => {
1301 matches!(expected, NumericLiteral::Float(expected) if actual.total_cmp(&expected).is_eq())
1302 }
1303 NumericPredicate::Compare(op) => {
1304 let expected = match expected {
1305 NumericLiteral::Int(expected) => expected as f64,
1306 NumericLiteral::Float(expected) => expected,
1307 };
1308 compare_ordering(
1309 float_total_order_key(actual).cmp(&float_total_order_key(expected)),
1310 op,
1311 )
1312 }
1313 }
1314}
1315
1316#[inline(always)]
1317fn float_total_order_key(value: f64) -> i64 {
1318 let bits = value.to_bits() as i64;
1319 bits ^ (((bits >> 63) as u64 >> 1) as i64)
1320}
1321
1322fn compare_ordering(ordering: std::cmp::Ordering, op: ComparisonOp) -> bool {
1323 match op {
1324 ComparisonOp::Lt => ordering == std::cmp::Ordering::Less,
1325 ComparisonOp::Lte => ordering != std::cmp::Ordering::Greater,
1326 ComparisonOp::Gt => ordering == std::cmp::Ordering::Greater,
1327 ComparisonOp::Gte => ordering != std::cmp::Ordering::Less,
1328 }
1329}
1330
1331fn count_bitmap_rows(words: &[u64], len: usize) -> usize {
1332 words
1333 .iter()
1334 .enumerate()
1335 .fold(0usize, |total, (index, word)| {
1336 let bits = if index + 1 == words.len() && !len.is_multiple_of(u64::BITS as usize) {
1337 word & ((1u64 << (len % u64::BITS as usize)) - 1)
1338 } else {
1339 *word
1340 };
1341 total.saturating_add(bits.count_ones() as usize)
1342 })
1343}
1344
1345#[cfg(test)]
1346mod tests {
1347 use super::*;
1348
1349 #[test]
1350 fn validity_uses_no_bitmap_for_dense_columns() {
1351 let mut builder = ValidityBuilder::with_capacity(3);
1352 builder.push(true);
1353 builder.push(true);
1354 builder.push(true);
1355
1356 assert!(builder.words.is_empty());
1357 assert_eq!(builder.finish(), Validity::All { len: 3 });
1358 }
1359
1360 #[test]
1361 fn validity_materializes_prior_rows_on_first_null() {
1362 let mut builder = ValidityBuilder::with_capacity(130);
1363 for _ in 0..65 {
1364 builder.push(true);
1365 }
1366 builder.push(false);
1367 builder.push(true);
1368
1369 let validity = builder.finish();
1370 assert_eq!(validity.valid_count(), 66);
1371 assert!(validity.is_valid(64));
1372 assert!(!validity.is_valid(65));
1373 assert!(validity.is_valid(66));
1374 }
1375
1376 #[test]
1377 fn value_ref_distinguishes_null_from_out_of_bounds() {
1378 let column = ColumnVector::int64(
1379 vec![1, 0],
1380 Validity::Bitmap {
1381 len: 2,
1382 words: Arc::from([0b01]),
1383 },
1384 )
1385 .unwrap();
1386
1387 assert_eq!(column.value_ref(0), Some(ValueRef::Int(1)));
1388 assert_eq!(column.value_ref(1), Some(ValueRef::Null));
1389 assert_eq!(column.value_ref(2), None);
1390 }
1391
1392 #[test]
1393 fn boolean_bytes_reject_non_canonical_values() {
1394 let error = ColumnVector::boolean_bytes(vec![0, 2], Validity::all(2)).unwrap_err();
1395
1396 assert!(error.to_string().contains("other than 0 or 1"));
1397 }
1398
1399 #[test]
1400 fn validity_builder_reuses_bitmap_storage_between_batches() {
1401 let mut builder = ValidityBuilder::with_capacity(130);
1402 builder.push(false);
1403 let words_ptr = builder.words.as_ptr();
1404 let words_capacity = builder.words.capacity();
1405
1406 builder.clear();
1407 builder.push(true);
1408 builder.push(false);
1409
1410 assert_eq!(builder.words.as_ptr(), words_ptr);
1411 assert_eq!(builder.words.capacity(), words_capacity);
1412 let validity = builder.view();
1413 assert!(!validity.is_empty());
1414 assert!(validity.is_valid(0));
1415 assert!(!validity.is_valid(1));
1416 }
1417
1418 #[test]
1419 fn numeric_filter_preserves_null_and_nan_semantics() {
1420 let mut validity = ValidityBuilder::with_capacity(4);
1421 validity.push(true);
1422 validity.push(false);
1423 validity.push(true);
1424 validity.push(true);
1425 let column =
1426 ColumnVector::float64(vec![1.0, 0.0, f64::NAN, 3.0], validity.finish()).unwrap();
1427
1428 let selection = filter_numeric_column(
1429 &column,
1430 &Selection::all(4),
1431 NumericPredicate::Compare(ComparisonOp::Gte),
1432 NumericLiteral::Float(2.0),
1433 )
1434 .unwrap();
1435
1436 assert_eq!(selection.iter().collect::<Vec<_>>(), vec![2, 3]);
1437 }
1438
1439 #[test]
1440 fn numeric_equality_filter_preserves_type_validity_and_float_ordering_semantics() {
1441 let mut validity = ValidityBuilder::with_capacity(5);
1442 validity.push(true);
1443 validity.push(false);
1444 validity.push(true);
1445 validity.push(true);
1446 validity.push(true);
1447 let nan = f64::from_bits(0x7ff8_0000_0000_0042);
1448 let column =
1449 ColumnVector::float64(vec![5.0, 5.0, nan, -0.0, 0.0], validity.finish()).unwrap();
1450
1451 let nan_selection = filter_numeric_column(
1452 &column,
1453 &Selection::all(5),
1454 NumericPredicate::Eq,
1455 NumericLiteral::Float(nan),
1456 )
1457 .unwrap();
1458 let negative_zero_selection = filter_numeric_column(
1459 &column,
1460 &Selection::all(5),
1461 NumericPredicate::Eq,
1462 NumericLiteral::Float(-0.0),
1463 )
1464 .unwrap();
1465 let cross_type_selection = filter_numeric_column(
1466 &column,
1467 &Selection::all(5),
1468 NumericPredicate::Eq,
1469 NumericLiteral::Int(5),
1470 )
1471 .unwrap();
1472
1473 assert_eq!(nan_selection.iter().collect::<Vec<_>>(), vec![2]);
1474 assert_eq!(negative_zero_selection.iter().collect::<Vec<_>>(), vec![3]);
1475 assert!(cross_type_selection.is_empty());
1476 }
1477
1478 #[test]
1479 fn numeric_equality_selection_reuses_row_index_storage() {
1480 let mut selected_rows = Vec::new();
1481 select_int64_values_view(
1482 &[1, 2, 1, 3],
1483 ValidityView::Bitmap {
1484 len: 4,
1485 words: &[0b1101],
1486 },
1487 NumericPredicate::Eq,
1488 NumericLiteral::Int(1),
1489 &mut selected_rows,
1490 )
1491 .unwrap();
1492 assert_eq!(selected_rows, [0, 2]);
1493 let rows_ptr = selected_rows.as_ptr();
1494
1495 select_int64_values_view(
1496 &[4, 5, 4, 6],
1497 ValidityView::All { len: 4 },
1498 NumericPredicate::Eq,
1499 NumericLiteral::Int(4),
1500 &mut selected_rows,
1501 )
1502 .unwrap();
1503
1504 assert_eq!(selected_rows, [0, 2]);
1505 assert_eq!(selected_rows.as_ptr(), rows_ptr);
1506 }
1507
1508 #[test]
1509 fn specialized_numeric_kernels_match_total_order_reference() {
1510 let int_values = [i64::MIN, -1, 0, 1, i64::MAX];
1511 let float_values = [
1512 f64::from_bits(0xfff8_0000_0000_0001),
1513 f64::NEG_INFINITY,
1514 -0.0,
1515 0.0,
1516 1.0,
1517 f64::INFINITY,
1518 f64::from_bits(0x7ff8_0000_0000_0001),
1519 ];
1520 let int_validity = Validity::Bitmap {
1521 len: int_values.len(),
1522 words: Arc::from([0b1_1101]),
1523 };
1524 let float_validity = Validity::Bitmap {
1525 len: float_values.len(),
1526 words: Arc::from([0b110_1111]),
1527 };
1528 let int_expected = [
1529 NumericLiteral::Int(0),
1530 NumericLiteral::Float(-0.0),
1531 NumericLiteral::Float(f64::INFINITY),
1532 NumericLiteral::Float(f64::from_bits(0x7ff8_0000_0000_0001)),
1533 ];
1534 let float_expected = [
1535 NumericLiteral::Int(0),
1536 NumericLiteral::Float(-0.0),
1537 NumericLiteral::Float(0.0),
1538 NumericLiteral::Float(f64::from_bits(0xfff8_0000_0000_0001)),
1539 NumericLiteral::Float(f64::from_bits(0x7ff8_0000_0000_0001)),
1540 ];
1541
1542 assert_specialized_numeric_kernel(
1543 &int_values,
1544 &int_validity,
1545 &int_expected,
1546 filter_int64_values,
1547 select_int64_values_view,
1548 int64_value_matches,
1549 reference_int64_matches,
1550 );
1551 assert_specialized_numeric_kernel(
1552 &float_values,
1553 &float_validity,
1554 &float_expected,
1555 filter_float64_values,
1556 select_float64_values_view,
1557 float64_value_matches,
1558 reference_float64_matches,
1559 );
1560 }
1561
1562 fn assert_specialized_numeric_kernel<T: Copy>(
1563 values: &[T],
1564 validity: &Validity,
1565 expected_literals: &[NumericLiteral],
1566 filter: impl Fn(
1567 &[T],
1568 &Validity,
1569 &Selection,
1570 NumericPredicate,
1571 NumericLiteral,
1572 ) -> Result<Selection>,
1573 select: impl for<'a> Fn(
1574 &[T],
1575 ValidityView<'a>,
1576 NumericPredicate,
1577 NumericLiteral,
1578 &mut Vec<u32>,
1579 ) -> Result<()>,
1580 point_matches: impl Fn(T, NumericPredicate, NumericLiteral) -> bool,
1581 reference_matches: impl Fn(T, ComparisonOp, NumericLiteral) -> bool,
1582 ) {
1583 for op in [
1584 ComparisonOp::Lt,
1585 ComparisonOp::Lte,
1586 ComparisonOp::Gt,
1587 ComparisonOp::Gte,
1588 ] {
1589 for &expected in expected_literals {
1590 let predicate = NumericPredicate::Compare(op);
1591 let reference = values
1592 .iter()
1593 .copied()
1594 .enumerate()
1595 .filter_map(|(row, actual)| {
1596 assert_eq!(
1597 point_matches(actual, predicate, expected),
1598 reference_matches(actual, op, expected)
1599 );
1600 (validity.is_valid(row) && reference_matches(actual, op, expected))
1601 .then_some(row)
1602 })
1603 .collect::<Vec<_>>();
1604 let filtered = filter(
1605 values,
1606 validity,
1607 &Selection::all(values.len()),
1608 predicate,
1609 expected,
1610 )
1611 .unwrap();
1612 let mut selected = Vec::new();
1613 select(values, validity.view(), predicate, expected, &mut selected).unwrap();
1614
1615 assert_eq!(filtered.iter().collect::<Vec<_>>(), reference);
1616 assert_eq!(
1617 selected,
1618 reference.iter().map(|row| *row as u32).collect::<Vec<_>>()
1619 );
1620 }
1621 }
1622 }
1623
1624 fn reference_int64_matches(actual: i64, op: ComparisonOp, expected: NumericLiteral) -> bool {
1625 let ordering = match expected {
1626 NumericLiteral::Int(expected) => actual.cmp(&expected),
1627 NumericLiteral::Float(expected) => (actual as f64).total_cmp(&expected),
1628 };
1629 compare_ordering(ordering, op)
1630 }
1631
1632 fn reference_float64_matches(actual: f64, op: ComparisonOp, expected: NumericLiteral) -> bool {
1633 let expected = match expected {
1634 NumericLiteral::Int(expected) => expected as f64,
1635 NumericLiteral::Float(expected) => expected,
1636 };
1637 compare_ordering(actual.total_cmp(&expected), op)
1638 }
1639
1640 #[test]
1641 fn numeric_selection_reuses_row_index_storage() {
1642 let mut selected_rows = Vec::new();
1643 select_int64_values_view(
1644 &[1, 2, 3, 4],
1645 ValidityView::All { len: 4 },
1646 NumericPredicate::Compare(ComparisonOp::Gte),
1647 NumericLiteral::Int(3),
1648 &mut selected_rows,
1649 )
1650 .unwrap();
1651 assert_eq!(selected_rows, [2, 3]);
1652 let rows_ptr = selected_rows.as_ptr();
1653
1654 select_int64_values_view(
1655 &[5, 6, 7, 8],
1656 ValidityView::Bitmap {
1657 len: 4,
1658 words: &[0b1101],
1659 },
1660 NumericPredicate::Compare(ComparisonOp::Lt),
1661 NumericLiteral::Int(8),
1662 &mut selected_rows,
1663 )
1664 .unwrap();
1665
1666 assert_eq!(selected_rows, [0, 2]);
1667 assert_eq!(selected_rows.as_ptr(), rows_ptr);
1668 }
1669
1670 #[test]
1671 fn sparse_filter_uses_index_selection() {
1672 let column = ColumnVector::int64((0..64).collect(), Validity::all(64)).unwrap();
1673 let selection = filter_numeric_column(
1674 &column,
1675 &Selection::all(64),
1676 NumericPredicate::Compare(ComparisonOp::Gte),
1677 NumericLiteral::Int(63),
1678 )
1679 .unwrap();
1680
1681 assert!(matches!(selection, Selection::Indices { .. }));
1682 assert_eq!(selection.iter().collect::<Vec<_>>(), vec![63]);
1683 }
1684
1685 #[test]
1686 fn dense_filter_uses_bitmap_selection() {
1687 let column = ColumnVector::int64((0..64).collect(), Validity::all(64)).unwrap();
1688 let selection = filter_numeric_column(
1689 &column,
1690 &Selection::all(64),
1691 NumericPredicate::Compare(ComparisonOp::Gte),
1692 NumericLiteral::Int(32),
1693 )
1694 .unwrap();
1695
1696 assert!(matches!(selection, Selection::Bitmap { .. }));
1697 assert_eq!(selection.selected_count(), 32);
1698 }
1699
1700 #[test]
1701 fn projection_reuses_column_storage() {
1702 let schema = Arc::new(
1703 BindingSchema::try_new(vec![SlotDescriptor {
1704 id: SlotId(0),
1705 name: "score".to_string(),
1706 slot_type: SlotType::logical(LogicalType::Int64),
1707 }])
1708 .unwrap(),
1709 );
1710 let column = Arc::new(ColumnVector::int64(vec![1, 2], Validity::all(2)).unwrap());
1711 let batch = ColumnarBatch::try_new(Arc::clone(&schema), vec![Arc::clone(&column)]).unwrap();
1712 let projected = batch.project(&[SlotId(0)]).unwrap();
1713
1714 assert!(Arc::ptr_eq(projected.column(SlotId(0)).unwrap(), &column));
1715 }
1716
1717 #[test]
1718 fn utf8_value_ref_borrows_column_storage_until_materialization() {
1719 let schema = Arc::new(
1720 BindingSchema::try_new(vec![SlotDescriptor {
1721 id: SlotId(0),
1722 name: "content".to_string(),
1723 slot_type: SlotType::logical(LogicalType::Text),
1724 }])
1725 .unwrap(),
1726 );
1727 let values: Arc<[String]> = vec!["borrowed content".to_string()].into();
1728 let source_ptr = values[0].as_ptr();
1729 let column = Arc::new(ColumnVector::Utf8 {
1730 values,
1731 validity: Validity::all(1),
1732 });
1733 let batch = ColumnarBatch::try_new(schema, vec![column]).unwrap();
1734
1735 let value = batch.value_ref(SlotId(0), 0).unwrap();
1736 assert_eq!(value.logical_type(), Some(LogicalType::String));
1737 assert_eq!(value.as_str().unwrap().as_ptr(), source_ptr);
1738 assert_eq!(
1739 value.to_owned_value(),
1740 Value::String("borrowed content".into())
1741 );
1742 }
1743
1744 #[test]
1745 fn logical_and_physical_slot_types_are_validated_separately() {
1746 assert!(SlotType::logical(LogicalType::Any).accepts(ColumnType::Bool));
1747 assert!(SlotType::logical(LogicalType::Any).accepts(ColumnType::Utf8));
1748 assert!(!SlotType::logical(LogicalType::Any).accepts(ColumnType::NodeId));
1749 assert!(SlotType::logical(LogicalType::String).accepts(ColumnType::Utf8));
1750 assert!(SlotType::logical(LogicalType::Text).accepts(ColumnType::Utf8));
1751 assert!(SlotType::NodeId.accepts(ColumnType::NodeId));
1752 assert!(!SlotType::NodeId.accepts(ColumnType::Int64));
1753 assert_eq!(SlotType::NodeId.logical_type(), Some(LogicalType::Int64));
1754 assert_eq!(SlotType::RelationalRowLocator.logical_type(), None);
1755 }
1756
1757 #[test]
1758 fn relational_locator_projection_preserves_compact_identity_storage() {
1759 let schema = Arc::new(
1760 BindingSchema::try_new(vec![SlotDescriptor {
1761 id: SlotId(0),
1762 name: "row_locator".to_string(),
1763 slot_type: SlotType::RelationalRowLocator,
1764 }])
1765 .unwrap(),
1766 );
1767 let locator = RelationalRowLocator::new(
1768 3,
1769 RelationalKey(vec![
1770 RelationalValue::BigInt(7),
1771 RelationalValue::Text("thread-1".to_string()),
1772 ]),
1773 );
1774 let nested_bytes = locator.allocated_bytes();
1775 let column = Arc::new(ColumnVector::relational_row_locators(vec![locator]));
1776 let batch = ColumnarBatch::try_new(schema, vec![Arc::clone(&column)]).unwrap();
1777 let projected = batch.project(&[SlotId(0)]).unwrap();
1778
1779 assert!(Arc::ptr_eq(projected.column(SlotId(0)).unwrap(), &column));
1780 let actual = projected
1781 .column(SlotId(0))
1782 .unwrap()
1783 .relational_row_locator(0)
1784 .unwrap();
1785 assert_eq!(actual.table_id(), 3);
1786 assert_eq!(actual.primary_key().0.len(), 2);
1787 assert_eq!(column.value(0), None);
1788 assert_eq!(
1789 column.estimated_memory_bytes(),
1790 std::mem::size_of::<RelationalRowLocator>() + nested_bytes
1791 );
1792 }
1793
1794 #[test]
1795 fn boolean_filter_limit_and_projection_share_storage() {
1796 let schema = Arc::new(
1797 BindingSchema::try_new(vec![
1798 SlotDescriptor {
1799 id: SlotId(0),
1800 name: "visible".to_string(),
1801 slot_type: SlotType::logical(LogicalType::Boolean),
1802 },
1803 SlotDescriptor {
1804 id: SlotId(1),
1805 name: "id".to_string(),
1806 slot_type: SlotType::NodeId,
1807 },
1808 ])
1809 .unwrap(),
1810 );
1811 let visible = Arc::new(
1812 ColumnVector::boolean(
1813 vec![true, false, true, true, false],
1814 Validity::Bitmap {
1815 len: 5,
1816 words: Arc::from([0b1_1011]),
1817 },
1818 )
1819 .unwrap(),
1820 );
1821 let ids = Arc::new(ColumnVector::node_ids(vec![10, 11, 12, 13, 14]));
1822 let batch = ColumnarBatch::try_new(schema, vec![Arc::clone(&visible), Arc::clone(&ids)])
1823 .unwrap()
1824 .filter_boolean(SlotId(0), true)
1825 .unwrap()
1826 .limit(1, 1)
1827 .project(&[SlotId(1)])
1828 .unwrap();
1829
1830 assert_eq!(batch.selection().iter().collect::<Vec<_>>(), vec![3]);
1831 assert!(Arc::ptr_eq(batch.column(SlotId(0)).unwrap(), &ids));
1832 }
1833
1834 #[test]
1835 fn count_and_sum_follow_selection_and_validity() {
1836 let schema = Arc::new(
1837 BindingSchema::try_new(vec![SlotDescriptor {
1838 id: SlotId(0),
1839 name: "token_count".to_string(),
1840 slot_type: SlotType::logical(LogicalType::Int64),
1841 }])
1842 .unwrap(),
1843 );
1844 let values = Arc::new(
1845 ColumnVector::int64(
1846 vec![4, 8, 16, 32],
1847 Validity::Bitmap {
1848 len: 4,
1849 words: Arc::from([0b1101]),
1850 },
1851 )
1852 .unwrap(),
1853 );
1854 let batch = ColumnarBatch::try_new(schema, vec![values])
1855 .unwrap()
1856 .filter_numeric(
1857 SlotId(0),
1858 NumericPredicate::Compare(ComparisonOp::Gte),
1859 NumericLiteral::Int(4),
1860 )
1861 .unwrap()
1862 .limit(1, 2);
1863
1864 assert_eq!(batch.count_selected(), 2);
1865 assert_eq!(batch.count_valid(SlotId(0)).unwrap(), 2);
1866 assert_eq!(batch.sum_int64(SlotId(0)).unwrap(), Some(48));
1867 }
1868
1869 #[test]
1870 fn int64_sum_fails_closed_on_overflow() {
1871 let schema = Arc::new(
1872 BindingSchema::try_new(vec![SlotDescriptor {
1873 id: SlotId(0),
1874 name: "value".to_string(),
1875 slot_type: SlotType::logical(LogicalType::Int64),
1876 }])
1877 .unwrap(),
1878 );
1879 let values = Arc::new(ColumnVector::int64(vec![i64::MAX, 1], Validity::all(2)).unwrap());
1880 let batch = ColumnarBatch::try_new(schema, vec![values]).unwrap();
1881
1882 let error = batch.sum_int64(SlotId(0)).unwrap_err();
1883 assert!(error.to_string().contains("SUM overflow"));
1884 }
1885}