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