Skip to main content

kyu_storage/
column_chunk.rs

1//! Column chunk: in-memory columnar buffer with typed access.
2//!
3//! `ColumnChunkData` stores fixed-size values in a flat byte buffer with a `NullMask`.
4//! `BoolChunkData` is a 1-bit-per-value specialization.
5//! `ColumnChunk` is the type-erased enum wrapper.
6
7use kyu_common::InternalId;
8use kyu_types::{Interval, LogicalType, PhysicalType};
9
10use crate::null_mask::NullMask;
11use crate::storage_types::{ColumnChunkMetadata, ColumnChunkStats, ResidencyState, StorageValue};
12
13/// Trait for fixed-size types that can be stored in a column chunk.
14/// Uses safe `from_ne_bytes`/`to_ne_bytes` patterns — no `unsafe`.
15pub trait FixedSizeValue: Copy + Sized {
16    const SIZE: usize = std::mem::size_of::<Self>();
17    fn to_bytes(&self) -> Vec<u8>;
18    fn from_bytes(bytes: &[u8]) -> Self;
19    fn to_storage_value(&self) -> Option<StorageValue>;
20}
21
22macro_rules! impl_fixed_size_int {
23    ($ty:ty, $variant:ident) => {
24        impl FixedSizeValue for $ty {
25            fn to_bytes(&self) -> Vec<u8> {
26                self.to_ne_bytes().to_vec()
27            }
28            fn from_bytes(bytes: &[u8]) -> Self {
29                let mut arr = [0u8; std::mem::size_of::<$ty>()];
30                arr.copy_from_slice(&bytes[..std::mem::size_of::<$ty>()]);
31                <$ty>::from_ne_bytes(arr)
32            }
33            fn to_storage_value(&self) -> Option<StorageValue> {
34                Some(StorageValue::$variant(*self as _))
35            }
36        }
37    };
38}
39
40impl_fixed_size_int!(i8, SignedInt);
41impl_fixed_size_int!(i16, SignedInt);
42impl_fixed_size_int!(i32, SignedInt);
43impl_fixed_size_int!(i64, SignedInt);
44impl_fixed_size_int!(u8, UnsignedInt);
45impl_fixed_size_int!(u16, UnsignedInt);
46impl_fixed_size_int!(u32, UnsignedInt);
47impl_fixed_size_int!(u64, UnsignedInt);
48
49impl FixedSizeValue for i128 {
50    fn to_bytes(&self) -> Vec<u8> {
51        self.to_ne_bytes().to_vec()
52    }
53    fn from_bytes(bytes: &[u8]) -> Self {
54        let mut arr = [0u8; 16];
55        arr.copy_from_slice(&bytes[..16]);
56        i128::from_ne_bytes(arr)
57    }
58    fn to_storage_value(&self) -> Option<StorageValue> {
59        Some(StorageValue::SignedInt128(*self))
60    }
61}
62
63impl FixedSizeValue for f32 {
64    fn to_bytes(&self) -> Vec<u8> {
65        self.to_ne_bytes().to_vec()
66    }
67    fn from_bytes(bytes: &[u8]) -> Self {
68        let mut arr = [0u8; 4];
69        arr.copy_from_slice(&bytes[..4]);
70        f32::from_ne_bytes(arr)
71    }
72    fn to_storage_value(&self) -> Option<StorageValue> {
73        Some(StorageValue::Float(*self as f64))
74    }
75}
76
77impl FixedSizeValue for f64 {
78    fn to_bytes(&self) -> Vec<u8> {
79        self.to_ne_bytes().to_vec()
80    }
81    fn from_bytes(bytes: &[u8]) -> Self {
82        let mut arr = [0u8; 8];
83        arr.copy_from_slice(&bytes[..8]);
84        f64::from_ne_bytes(arr)
85    }
86    fn to_storage_value(&self) -> Option<StorageValue> {
87        Some(StorageValue::Float(*self))
88    }
89}
90
91impl FixedSizeValue for InternalId {
92    fn to_bytes(&self) -> Vec<u8> {
93        let mut buf = Vec::with_capacity(16);
94        buf.extend_from_slice(&self.table_id.to_ne_bytes());
95        buf.extend_from_slice(&self.offset.to_ne_bytes());
96        buf
97    }
98    fn from_bytes(bytes: &[u8]) -> Self {
99        let mut tid = [0u8; 8];
100        let mut off = [0u8; 8];
101        tid.copy_from_slice(&bytes[..8]);
102        off.copy_from_slice(&bytes[8..16]);
103        InternalId::new(u64::from_ne_bytes(tid), u64::from_ne_bytes(off))
104    }
105    fn to_storage_value(&self) -> Option<StorageValue> {
106        None
107    }
108}
109
110impl FixedSizeValue for Interval {
111    fn to_bytes(&self) -> Vec<u8> {
112        let mut buf = Vec::with_capacity(16);
113        buf.extend_from_slice(&self.months.to_ne_bytes());
114        buf.extend_from_slice(&self.days.to_ne_bytes());
115        buf.extend_from_slice(&self.micros.to_ne_bytes());
116        buf
117    }
118    fn from_bytes(bytes: &[u8]) -> Self {
119        let mut m = [0u8; 4];
120        let mut d = [0u8; 4];
121        let mut u = [0u8; 8];
122        m.copy_from_slice(&bytes[..4]);
123        d.copy_from_slice(&bytes[4..8]);
124        u.copy_from_slice(&bytes[8..16]);
125        Interval::new(
126            i32::from_ne_bytes(m),
127            i32::from_ne_bytes(d),
128            i64::from_ne_bytes(u),
129        )
130    }
131    fn to_storage_value(&self) -> Option<StorageValue> {
132        None
133    }
134}
135
136/// In-memory columnar buffer for fixed-size values.
137pub struct ColumnChunkData {
138    data_type: LogicalType,
139    num_bytes_per_value: usize,
140    buffer: Vec<u8>,
141    null_data: NullMask,
142    capacity: u64,
143    num_values: u64,
144    stats: ColumnChunkStats,
145    metadata: ColumnChunkMetadata,
146    residency_state: ResidencyState,
147}
148
149impl ColumnChunkData {
150    /// Create a new column chunk for the given type and capacity.
151    pub fn new(data_type: LogicalType, capacity: u64) -> Self {
152        let physical = data_type.physical_type();
153        let num_bytes_per_value = physical.fixed_size().unwrap_or(0);
154        Self {
155            data_type,
156            num_bytes_per_value,
157            buffer: vec![0u8; num_bytes_per_value * capacity as usize],
158            null_data: NullMask::new(capacity),
159            capacity,
160            num_values: 0,
161            stats: ColumnChunkStats::default(),
162            metadata: ColumnChunkMetadata::default(),
163            residency_state: ResidencyState::InMemory,
164        }
165    }
166
167    pub fn data_type(&self) -> &LogicalType {
168        &self.data_type
169    }
170
171    pub fn capacity(&self) -> u64 {
172        self.capacity
173    }
174
175    pub fn num_values(&self) -> u64 {
176        self.num_values
177    }
178
179    pub fn is_null(&self, pos: u64) -> bool {
180        self.null_data.is_null(pos)
181    }
182
183    pub fn set_null(&mut self, pos: u64, is_null: bool) {
184        self.null_data.set_null(pos, is_null);
185    }
186
187    pub fn null_mask(&self) -> &NullMask {
188        &self.null_data
189    }
190
191    pub fn null_mask_mut(&mut self) -> &mut NullMask {
192        &mut self.null_data
193    }
194
195    pub fn stats(&self) -> &ColumnChunkStats {
196        &self.stats
197    }
198
199    pub fn metadata(&self) -> &ColumnChunkMetadata {
200        &self.metadata
201    }
202
203    pub fn set_metadata(&mut self, metadata: ColumnChunkMetadata) {
204        self.metadata = metadata;
205    }
206
207    pub fn residency_state(&self) -> ResidencyState {
208        self.residency_state
209    }
210
211    pub fn set_residency_state(&mut self, state: ResidencyState) {
212        self.residency_state = state;
213    }
214
215    /// Get a typed value at the given position.
216    pub fn get_value<T: FixedSizeValue>(&self, pos: u64) -> T {
217        debug_assert!(pos < self.num_values);
218        let offset = pos as usize * self.num_bytes_per_value;
219        T::from_bytes(&self.buffer[offset..offset + self.num_bytes_per_value])
220    }
221
222    /// Set a typed value at the given position.
223    pub fn set_value<T: FixedSizeValue>(&mut self, pos: u64, val: T) {
224        debug_assert!(pos < self.capacity);
225        let offset = pos as usize * self.num_bytes_per_value;
226        let bytes = val.to_bytes();
227        self.buffer[offset..offset + self.num_bytes_per_value].copy_from_slice(&bytes);
228        self.null_data.set_null(pos, false);
229        if let Some(sv) = val.to_storage_value() {
230            self.stats.update(sv);
231        }
232    }
233
234    /// Append a typed value, incrementing num_values.
235    pub fn append_value<T: FixedSizeValue>(&mut self, val: T) {
236        let pos = self.num_values;
237        debug_assert!(pos < self.capacity);
238        self.set_value(pos, val);
239        self.num_values += 1;
240    }
241
242    /// Append a null value, incrementing num_values.
243    pub fn append_null(&mut self) {
244        let pos = self.num_values;
245        debug_assert!(pos < self.capacity);
246        self.null_data.set_null(pos, true);
247        self.num_values += 1;
248    }
249
250    /// Get raw bytes at a position.
251    pub fn get_raw(&self, pos: u64) -> &[u8] {
252        let offset = pos as usize * self.num_bytes_per_value;
253        &self.buffer[offset..offset + self.num_bytes_per_value]
254    }
255
256    /// Set raw bytes at a position.
257    pub fn set_raw(&mut self, pos: u64, bytes: &[u8]) {
258        debug_assert!(pos < self.capacity);
259        debug_assert_eq!(bytes.len(), self.num_bytes_per_value);
260        let offset = pos as usize * self.num_bytes_per_value;
261        self.buffer[offset..offset + self.num_bytes_per_value].copy_from_slice(bytes);
262        self.null_data.set_null(pos, false);
263    }
264
265    /// Scan a range of typed values, returning `None` for nulls.
266    pub fn scan_range<T: FixedSizeValue>(&self, start: u64, count: u64) -> Vec<Option<T>> {
267        let mut result = Vec::with_capacity(count as usize);
268        for i in start..start + count {
269            if self.null_data.is_null(i) {
270                result.push(None);
271            } else {
272                result.push(Some(self.get_value::<T>(i)));
273            }
274        }
275        result
276    }
277
278    /// Get the byte buffer backing this chunk (read-only).
279    pub fn buffer(&self) -> &[u8] {
280        &self.buffer
281    }
282
283    /// Number of bytes per value.
284    pub fn num_bytes_per_value(&self) -> usize {
285        self.num_bytes_per_value
286    }
287
288    /// Set num_values directly (used when bulk-loading).
289    pub fn set_num_values(&mut self, n: u64) {
290        self.num_values = n;
291    }
292}
293
294/// Packed 1-bit-per-value boolean column chunk.
295pub struct BoolChunkData {
296    data_type: LogicalType,
297    /// Packed boolean values (bit=1 means true).
298    values: NullMask,
299    null_data: NullMask,
300    capacity: u64,
301    num_values: u64,
302    residency_state: ResidencyState,
303}
304
305impl BoolChunkData {
306    pub fn new(capacity: u64) -> Self {
307        Self {
308            data_type: LogicalType::Bool,
309            values: NullMask::new(capacity),
310            null_data: NullMask::new(capacity),
311            capacity,
312            num_values: 0,
313            residency_state: ResidencyState::InMemory,
314        }
315    }
316
317    pub fn data_type(&self) -> &LogicalType {
318        &self.data_type
319    }
320
321    pub fn capacity(&self) -> u64 {
322        self.capacity
323    }
324
325    pub fn num_values(&self) -> u64 {
326        self.num_values
327    }
328
329    pub fn is_null(&self, pos: u64) -> bool {
330        self.null_data.is_null(pos)
331    }
332
333    pub fn set_null(&mut self, pos: u64, is_null: bool) {
334        self.null_data.set_null(pos, is_null);
335    }
336
337    pub fn null_mask(&self) -> &NullMask {
338        &self.null_data
339    }
340
341    /// Access the packed boolean values bitmask (bit=1 = true).
342    pub fn values_mask(&self) -> &NullMask {
343        &self.values
344    }
345
346    pub fn residency_state(&self) -> ResidencyState {
347        self.residency_state
348    }
349
350    /// Get a boolean value at the given position.
351    /// Bit=1 in the values mask means `true`.
352    pub fn get_bool(&self, pos: u64) -> bool {
353        self.values.is_null(pos)
354    }
355
356    /// Set a boolean value at the given position.
357    pub fn set_bool(&mut self, pos: u64, val: bool) {
358        self.values.set_null(pos, val);
359        self.null_data.set_null(pos, false);
360    }
361
362    /// Append a boolean value.
363    pub fn append_bool(&mut self, val: bool) {
364        let pos = self.num_values;
365        debug_assert!(pos < self.capacity);
366        self.set_bool(pos, val);
367        self.num_values += 1;
368    }
369
370    /// Append a null value.
371    pub fn append_null(&mut self) {
372        let pos = self.num_values;
373        debug_assert!(pos < self.capacity);
374        self.null_data.set_null(pos, true);
375        self.num_values += 1;
376    }
377
378    /// Scan a range of boolean values, returning `None` for nulls.
379    pub fn scan_range(&self, start: u64, count: u64) -> Vec<Option<bool>> {
380        let mut result = Vec::with_capacity(count as usize);
381        for i in start..start + count {
382            if self.null_data.is_null(i) {
383                result.push(None);
384            } else {
385                result.push(Some(self.get_bool(i)));
386            }
387        }
388        result
389    }
390
391    /// Set num_values directly (used when bulk-loading).
392    pub fn set_num_values(&mut self, n: u64) {
393        self.num_values = n;
394    }
395}
396
397/// In-memory string column chunk: stores `Option<SmolStr>` per row.
398///
399/// Variable-length strings can't be stored in the fixed-size byte buffer
400/// of `ColumnChunkData`. This variant stores strings directly as `SmolStr`
401/// values, matching kyu-types' string representation.
402pub struct StringChunkData {
403    data: Vec<Option<smol_str::SmolStr>>,
404    capacity: u64,
405    num_values: u64,
406    residency_state: ResidencyState,
407}
408
409impl StringChunkData {
410    pub fn new(capacity: u64) -> Self {
411        let mut data = Vec::with_capacity(capacity as usize);
412        data.resize(capacity as usize, None);
413        Self {
414            data,
415            capacity,
416            num_values: 0,
417            residency_state: ResidencyState::InMemory,
418        }
419    }
420
421    pub fn capacity(&self) -> u64 {
422        self.capacity
423    }
424
425    pub fn num_values(&self) -> u64 {
426        self.num_values
427    }
428
429    pub fn is_null(&self, pos: u64) -> bool {
430        self.data[pos as usize].is_none()
431    }
432
433    pub fn set_null(&mut self, pos: u64, is_null: bool) {
434        if is_null {
435            self.data[pos as usize] = None;
436        }
437    }
438
439    pub fn residency_state(&self) -> ResidencyState {
440        self.residency_state
441    }
442
443    /// Append a string value.
444    pub fn append_string(&mut self, val: smol_str::SmolStr) {
445        let pos = self.num_values as usize;
446        debug_assert!(self.num_values < self.capacity);
447        self.data[pos] = Some(val);
448        self.num_values += 1;
449    }
450
451    /// Append a null value.
452    pub fn append_null(&mut self) {
453        debug_assert!(self.num_values < self.capacity);
454        // data[pos] is already None from initialization
455        self.num_values += 1;
456    }
457
458    /// Set a string value at a specific position.
459    pub fn set_string(&mut self, pos: u64, val: smol_str::SmolStr) {
460        self.data[pos as usize] = Some(val);
461    }
462
463    /// Get a string reference at the given position. Returns `None` for nulls.
464    pub fn get(&self, pos: u64) -> Option<&smol_str::SmolStr> {
465        self.data[pos as usize].as_ref()
466    }
467
468    /// Scan a range of string values.
469    pub fn scan_range(&self, start: u64, count: u64) -> &[Option<smol_str::SmolStr>] {
470        &self.data[start as usize..(start + count) as usize]
471    }
472
473    /// Access the underlying data slice for ValueVector construction.
474    pub fn data_slice(&self) -> &[Option<smol_str::SmolStr>] {
475        &self.data
476    }
477
478    /// Set num_values directly (used when bulk-loading).
479    pub fn set_num_values(&mut self, n: u64) {
480        self.num_values = n;
481    }
482}
483
484/// Type-erased column chunk wrapper.
485pub enum ColumnChunk {
486    Fixed(ColumnChunkData),
487    Bool(BoolChunkData),
488    String(StringChunkData),
489}
490
491impl ColumnChunk {
492    /// Create the appropriate column chunk for a logical type.
493    pub fn new(data_type: LogicalType, capacity: u64) -> Self {
494        let physical = data_type.physical_type();
495        if physical == PhysicalType::Bool {
496            ColumnChunk::Bool(BoolChunkData::new(capacity))
497        } else if physical == PhysicalType::String {
498            ColumnChunk::String(StringChunkData::new(capacity))
499        } else {
500            ColumnChunk::Fixed(ColumnChunkData::new(data_type, capacity))
501        }
502    }
503
504    pub fn data_type(&self) -> &LogicalType {
505        match self {
506            Self::Fixed(c) => c.data_type(),
507            Self::Bool(c) => c.data_type(),
508            Self::String(_) => &LogicalType::String,
509        }
510    }
511
512    pub fn num_values(&self) -> u64 {
513        match self {
514            Self::Fixed(c) => c.num_values(),
515            Self::Bool(c) => c.num_values(),
516            Self::String(c) => c.num_values(),
517        }
518    }
519
520    pub fn capacity(&self) -> u64 {
521        match self {
522            Self::Fixed(c) => c.capacity(),
523            Self::Bool(c) => c.capacity(),
524            Self::String(c) => c.capacity(),
525        }
526    }
527
528    pub fn is_null(&self, pos: u64) -> bool {
529        match self {
530            Self::Fixed(c) => c.is_null(pos),
531            Self::Bool(c) => c.is_null(pos),
532            Self::String(c) => c.is_null(pos),
533        }
534    }
535
536    pub fn set_null(&mut self, pos: u64, is_null: bool) {
537        match self {
538            Self::Fixed(c) => c.set_null(pos, is_null),
539            Self::Bool(c) => c.set_null(pos, is_null),
540            Self::String(c) => c.set_null(pos, is_null),
541        }
542    }
543
544    pub fn append_null(&mut self) {
545        match self {
546            Self::Fixed(c) => c.append_null(),
547            Self::Bool(c) => c.append_null(),
548            Self::String(c) => c.append_null(),
549        }
550    }
551
552    pub fn residency_state(&self) -> ResidencyState {
553        match self {
554            Self::Fixed(c) => c.residency_state(),
555            Self::Bool(c) => c.residency_state(),
556            Self::String(c) => c.residency_state(),
557        }
558    }
559
560    /// Set raw bytes at a position. Only valid for Fixed chunks.
561    pub fn set_raw(&mut self, pos: u64, bytes: &[u8]) {
562        match self {
563            Self::Fixed(c) => c.set_raw(pos, bytes),
564            Self::Bool(_) => panic!("set_raw not supported for BoolChunkData"),
565            Self::String(_) => panic!("set_raw not supported for StringChunkData"),
566        }
567    }
568
569    /// Set num_values directly (used when bulk-loading).
570    pub fn set_num_values(&mut self, n: u64) {
571        match self {
572            Self::Fixed(c) => c.set_num_values(n),
573            Self::Bool(c) => c.set_num_values(n),
574            Self::String(c) => c.set_num_values(n),
575        }
576    }
577}
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582
583    #[test]
584    fn fixed_size_value_i64_roundtrip() {
585        let val: i64 = -42;
586        let bytes = val.to_bytes();
587        assert_eq!(i64::from_bytes(&bytes), val);
588    }
589
590    #[test]
591    fn fixed_size_value_f64_roundtrip() {
592        let val: f64 = 3.14159;
593        let bytes = val.to_bytes();
594        assert_eq!(f64::from_bytes(&bytes), val);
595    }
596
597    #[test]
598    fn fixed_size_value_internal_id_roundtrip() {
599        let val = InternalId::new(3, 42);
600        let bytes = val.to_bytes();
601        assert_eq!(InternalId::from_bytes(&bytes), val);
602    }
603
604    #[test]
605    fn fixed_size_value_interval_roundtrip() {
606        let val = Interval::new(14, 5, 3_723_000_000);
607        let bytes = val.to_bytes();
608        assert_eq!(Interval::from_bytes(&bytes), val);
609    }
610
611    #[test]
612    fn fixed_size_value_i128_roundtrip() {
613        let val: i128 = -170_141_183_460_469_231_731_687_303_715_884_105_728;
614        let bytes = val.to_bytes();
615        assert_eq!(i128::from_bytes(&bytes), val);
616    }
617
618    #[test]
619    fn column_chunk_data_new() {
620        let chunk = ColumnChunkData::new(LogicalType::Int64, 100);
621        assert_eq!(chunk.num_values(), 0);
622        assert_eq!(chunk.capacity(), 100);
623        assert_eq!(chunk.num_bytes_per_value(), 8);
624        assert_eq!(chunk.residency_state(), ResidencyState::InMemory);
625    }
626
627    #[test]
628    fn column_chunk_data_append_and_get() {
629        let mut chunk = ColumnChunkData::new(LogicalType::Int64, 10);
630        chunk.append_value(42i64);
631        chunk.append_value(-7i64);
632        chunk.append_value(100i64);
633
634        assert_eq!(chunk.num_values(), 3);
635        assert_eq!(chunk.get_value::<i64>(0), 42);
636        assert_eq!(chunk.get_value::<i64>(1), -7);
637        assert_eq!(chunk.get_value::<i64>(2), 100);
638    }
639
640    #[test]
641    fn column_chunk_data_set_value() {
642        let mut chunk = ColumnChunkData::new(LogicalType::Int32, 10);
643        chunk.set_num_values(3);
644        chunk.set_value(0, 10i32);
645        chunk.set_value(1, 20i32);
646        chunk.set_value(2, 30i32);
647
648        assert_eq!(chunk.get_value::<i32>(0), 10);
649        assert_eq!(chunk.get_value::<i32>(1), 20);
650        assert_eq!(chunk.get_value::<i32>(2), 30);
651    }
652
653    #[test]
654    fn column_chunk_data_nulls() {
655        let mut chunk = ColumnChunkData::new(LogicalType::Int64, 10);
656        chunk.append_value(42i64);
657        chunk.append_null();
658        chunk.append_value(100i64);
659
660        assert!(!chunk.is_null(0));
661        assert!(chunk.is_null(1));
662        assert!(!chunk.is_null(2));
663    }
664
665    #[test]
666    fn column_chunk_data_scan_range() {
667        let mut chunk = ColumnChunkData::new(LogicalType::Int64, 10);
668        chunk.append_value(1i64);
669        chunk.append_null();
670        chunk.append_value(3i64);
671
672        let result = chunk.scan_range::<i64>(0, 3);
673        assert_eq!(result, vec![Some(1), None, Some(3)]);
674    }
675
676    #[test]
677    fn column_chunk_data_raw_access() {
678        let mut chunk = ColumnChunkData::new(LogicalType::Int32, 10);
679        chunk.set_num_values(1);
680        let val: i32 = 42;
681        chunk.set_raw(0, &val.to_ne_bytes());
682        assert_eq!(chunk.get_value::<i32>(0), 42);
683
684        let raw = chunk.get_raw(0);
685        assert_eq!(raw, &val.to_ne_bytes());
686    }
687
688    #[test]
689    fn column_chunk_data_stats_tracking() {
690        let mut chunk = ColumnChunkData::new(LogicalType::Int64, 10);
691        chunk.append_value(10i64);
692        chunk.append_value(-5i64);
693        chunk.append_value(20i64);
694
695        assert_eq!(chunk.stats().min, Some(StorageValue::SignedInt(-5)));
696        assert_eq!(chunk.stats().max, Some(StorageValue::SignedInt(20)));
697    }
698
699    #[test]
700    fn column_chunk_data_f64_stats() {
701        let mut chunk = ColumnChunkData::new(LogicalType::Double, 10);
702        chunk.append_value(1.5f64);
703        chunk.append_value(3.7f64);
704        chunk.append_value(0.1f64);
705
706        assert_eq!(chunk.stats().min, Some(StorageValue::Float(0.1)));
707        assert_eq!(chunk.stats().max, Some(StorageValue::Float(3.7)));
708    }
709
710    #[test]
711    fn column_chunk_data_internal_id() {
712        let mut chunk = ColumnChunkData::new(LogicalType::InternalId, 10);
713        let id = InternalId::new(3, 42);
714        chunk.append_value(id);
715        assert_eq!(chunk.get_value::<InternalId>(0), id);
716    }
717
718    #[test]
719    fn bool_chunk_data_new() {
720        let chunk = BoolChunkData::new(100);
721        assert_eq!(chunk.num_values(), 0);
722        assert_eq!(chunk.capacity(), 100);
723        assert_eq!(*chunk.data_type(), LogicalType::Bool);
724    }
725
726    #[test]
727    fn bool_chunk_data_append_and_get() {
728        let mut chunk = BoolChunkData::new(10);
729        chunk.append_bool(true);
730        chunk.append_bool(false);
731        chunk.append_bool(true);
732
733        assert_eq!(chunk.num_values(), 3);
734        assert!(chunk.get_bool(0));
735        assert!(!chunk.get_bool(1));
736        assert!(chunk.get_bool(2));
737    }
738
739    #[test]
740    fn bool_chunk_data_nulls() {
741        let mut chunk = BoolChunkData::new(10);
742        chunk.append_bool(true);
743        chunk.append_null();
744        chunk.append_bool(false);
745
746        assert!(!chunk.is_null(0));
747        assert!(chunk.is_null(1));
748        assert!(!chunk.is_null(2));
749    }
750
751    #[test]
752    fn bool_chunk_data_scan_range() {
753        let mut chunk = BoolChunkData::new(10);
754        chunk.append_bool(true);
755        chunk.append_null();
756        chunk.append_bool(false);
757
758        let result = chunk.scan_range(0, 3);
759        assert_eq!(result, vec![Some(true), None, Some(false)]);
760    }
761
762    #[test]
763    fn column_chunk_enum_fixed() {
764        let mut chunk = ColumnChunk::new(LogicalType::Int64, 10);
765        assert_eq!(*chunk.data_type(), LogicalType::Int64);
766        assert_eq!(chunk.capacity(), 10);
767
768        match &mut chunk {
769            ColumnChunk::Fixed(c) => c.append_value(42i64),
770            _ => panic!("expected Fixed"),
771        }
772        assert_eq!(chunk.num_values(), 1);
773    }
774
775    #[test]
776    fn column_chunk_enum_bool() {
777        let mut chunk = ColumnChunk::new(LogicalType::Bool, 10);
778        assert_eq!(*chunk.data_type(), LogicalType::Bool);
779
780        match &mut chunk {
781            ColumnChunk::Bool(c) => c.append_bool(true),
782            _ => panic!("expected Bool"),
783        }
784        assert_eq!(chunk.num_values(), 1);
785    }
786
787    #[test]
788    fn column_chunk_enum_null() {
789        let mut chunk = ColumnChunk::new(LogicalType::Int32, 10);
790        chunk.append_null();
791        assert_eq!(chunk.num_values(), 1);
792        assert!(chunk.is_null(0));
793    }
794
795    #[test]
796    fn column_chunk_set_raw() {
797        let mut chunk = ColumnChunk::new(LogicalType::Int32, 10);
798        chunk.set_num_values(1);
799        let val: i32 = 99;
800        chunk.set_raw(0, &val.to_ne_bytes());
801        match &chunk {
802            ColumnChunk::Fixed(c) => assert_eq!(c.get_value::<i32>(0), 99),
803            _ => panic!("expected Fixed"),
804        }
805    }
806
807    #[test]
808    fn column_chunk_multiple_types() {
809        // Test u8
810        let mut c = ColumnChunkData::new(LogicalType::UInt8, 4);
811        c.append_value(255u8);
812        assert_eq!(c.get_value::<u8>(0), 255);
813
814        // Test u16
815        let mut c = ColumnChunkData::new(LogicalType::UInt16, 4);
816        c.append_value(65535u16);
817        assert_eq!(c.get_value::<u16>(0), 65535);
818
819        // Test u32
820        let mut c = ColumnChunkData::new(LogicalType::UInt32, 4);
821        c.append_value(u32::MAX);
822        assert_eq!(c.get_value::<u32>(0), u32::MAX);
823
824        // Test f32
825        let mut c = ColumnChunkData::new(LogicalType::Float, 4);
826        c.append_value(1.5f32);
827        assert_eq!(c.get_value::<f32>(0), 1.5);
828    }
829}