Skip to main content

hawdb_executor/
columnar.rs

1// Copyright 2026 Nowledge
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Typed columnar batches used by vectorized executor fragments.
16
17use 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/// The semantic value type or executor-private role assigned to one slot.
37///
38/// Query-visible values use the shared [`LogicalType`]. Physical identifiers
39/// and row locators remain explicit executor roles and cannot accidentally
40/// escape as logical schema types.
41#[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}