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