Skip to main content

clickhouse_native_client/column/
nullable.rs

1//! Nullable column implementation
2//!
3//! **ClickHouse Documentation:** <https://clickhouse.com/docs/en/sql-reference/data-types/nullable>
4//!
5//! ## Important Nesting Restrictions
6//!
7//! ClickHouse does NOT allow wrapping certain types in Nullable:
8//! - ❌ `Nullable(Array(...))` - NOT allowed (Error code 43)
9//! - ❌ `Nullable(LowCardinality(...))` - NOT allowed
10//!
11//! **Correct usage:**
12//! - ✅ `Array(Nullable(...))` - Nullable elements inside array
13//! - ✅ `LowCardinality(Nullable(...))` - Nullable values with dictionary
14//!   encoding
15//!
16//! See: <https://github.com/ClickHouse/ClickHouse/issues/1062>
17
18use super::{
19    numeric::ColumnUInt8,
20    Column,
21    ColumnRef,
22};
23use crate::{
24    types::Type,
25    Error,
26    Result,
27};
28use bytes::BytesMut;
29use std::{
30    marker::PhantomData,
31    sync::Arc,
32};
33
34/// Column for nullable values
35///
36/// Stores a nested column and a ColumnUInt8 for null flags (1 = null, 0 = not
37/// null).
38///
39/// **Wire Format:**
40/// ```text
41/// [null_bitmap: UInt8 * num_rows][nested_column_data]
42/// ```
43///
44/// **ClickHouse Reference:**
45/// - Documentation: <https://clickhouse.com/docs/en/sql-reference/data-types/nullable>
46/// - Best Practices: <https://clickhouse.com/docs/en/cloud/bestpractices/avoid-nullable-columns>
47pub struct ColumnNullable {
48    type_: Type,
49    nested: ColumnRef,
50    nulls: ColumnRef, // ColumnUInt8
51}
52
53impl ColumnNullable {
54    /// Create a new nullable column from a nullable type
55    pub fn new(type_: Type) -> Self {
56        // Extract nested type and create nested column
57        let nested = match &type_ {
58            Type::Nullable { nested_type } => {
59                crate::io::block_stream::create_column(nested_type)
60                    .expect("Failed to create nested column")
61            }
62            _ => panic!("ColumnNullable requires Nullable type"),
63        };
64
65        let nulls = Arc::new(ColumnUInt8::new());
66        Self { type_, nested, nulls }
67    }
68
69    /// Create a new nullable column wrapping an existing nested column
70    pub fn with_nested(nested: ColumnRef) -> Self {
71        let nested_type = nested.column_type().clone();
72        let nulls = Arc::new(ColumnUInt8::new());
73        Self { type_: Type::nullable(nested_type), nested, nulls }
74    }
75
76    /// Create with both nested and nulls columns
77    pub fn from_parts(nested: ColumnRef, nulls: ColumnRef) -> Result<Self> {
78        // Validate nulls is ColumnUInt8
79        if nulls.column_type().name() != "UInt8" {
80            return Err(Error::InvalidArgument(
81                "nulls column must be UInt8".to_string(),
82            ));
83        }
84
85        // Validate same size
86        if nested.size() != nulls.size() {
87            return Err(Error::InvalidArgument(format!(
88                "nested and nulls must have same size: nested={}, nulls={}",
89                nested.size(),
90                nulls.size()
91            )));
92        }
93
94        let nested_type = nested.column_type().clone();
95        Ok(Self { type_: Type::nullable(nested_type), nested, nulls })
96    }
97
98    /// Create with reserved capacity
99    pub fn with_capacity(type_: Type, capacity: usize) -> Self {
100        let nested = match &type_ {
101            Type::Nullable { nested_type } => {
102                crate::io::block_stream::create_column(nested_type)
103                    .expect("Failed to create nested column")
104            }
105            _ => panic!("ColumnNullable requires Nullable type"),
106        };
107
108        let mut nulls = ColumnUInt8::new();
109        nulls.reserve(capacity);
110        Self { type_, nested, nulls: Arc::new(nulls) }
111    }
112
113    /// Append a null flag (matches C++ API)
114    pub fn append(&mut self, isnull: bool) {
115        let nulls_mut = Arc::get_mut(&mut self.nulls)
116            .expect("Cannot append to shared nulls column")
117            .as_any_mut()
118            .downcast_mut::<ColumnUInt8>()
119            .expect("nulls must be ColumnUInt8");
120        nulls_mut.append(if isnull { 1 } else { 0 });
121    }
122
123    /// Append a null value
124    pub fn append_null(&mut self) {
125        self.append(true);
126    }
127
128    /// Append a non-null value (the nested column should be updated
129    /// separately)
130    pub fn append_non_null(&mut self) {
131        self.append(false);
132    }
133
134    /// Check if value at index is null (matches C++ IsNull)
135    pub fn is_null(&self, index: usize) -> bool {
136        if index >= self.nulls.size() {
137            return false;
138        }
139        let nulls_col = self
140            .nulls
141            .as_any()
142            .downcast_ref::<ColumnUInt8>()
143            .expect("nulls must be ColumnUInt8");
144        nulls_col.at(index) != 0
145    }
146
147    /// Get a reference to the nested column as a specific type
148    ///
149    /// # Example
150    /// ```ignore
151    /// let col: ColumnNullable = /* ... */;
152    /// let nested: &ColumnUInt32 = col.nested();
153    /// ```
154    pub fn nested<T: Column + 'static>(&self) -> &T {
155        self.nested
156            .as_any()
157            .downcast_ref::<T>()
158            .expect("Failed to downcast nested column to requested type")
159    }
160
161    /// Get mutable reference to the nested column as a specific type
162    ///
163    /// # Example
164    /// ```ignore
165    /// let mut col: ColumnNullable = /* ... */;
166    /// let nested_mut: &mut ColumnUInt32 = col.nested_mut();
167    /// ```
168    pub fn nested_mut<T: Column + 'static>(&mut self) -> &mut T {
169        Arc::get_mut(&mut self.nested)
170            .expect("Cannot get mutable reference to shared nested column")
171            .as_any_mut()
172            .downcast_mut::<T>()
173            .expect("Failed to downcast nested column to requested type")
174    }
175
176    /// Get the nested column as a `ColumnRef` (`Arc<dyn Column>`)
177    pub fn nested_ref(&self) -> ColumnRef {
178        self.nested.clone()
179    }
180
181    /// Get mutable access to the nested ColumnRef for dynamic dispatch
182    /// scenarios
183    ///
184    /// This is useful when you need to modify the nested column but don't know
185    /// its concrete type at compile time.
186    pub fn nested_ref_mut(&mut self) -> &mut ColumnRef {
187        &mut self.nested
188    }
189
190    /// Get the nulls column (matches C++ Nulls)
191    pub fn nulls(&self) -> ColumnRef {
192        self.nulls.clone()
193    }
194
195    /// Append a nullable UInt32 value (convenience method for tests)
196    pub fn append_nullable(&mut self, value: Option<u32>) {
197        use crate::column::numeric::ColumnUInt32;
198
199        match value {
200            None => {
201                self.append_null();
202                // Still need to add a placeholder to nested column to keep
203                // indices aligned
204                let nested_mut = Arc::get_mut(&mut self.nested)
205                    .expect("Cannot append to shared nullable column - column has multiple references");
206                let col = nested_mut
207                    .as_any_mut()
208                    .downcast_mut::<ColumnUInt32>()
209                    .expect("Nullable nested column is not UInt32");
210                col.append(0); // Placeholder value (ignored due to null flag)
211            }
212            Some(val) => {
213                self.append_non_null();
214                let nested_mut = Arc::get_mut(&mut self.nested)
215                    .expect("Cannot append to shared nullable column - column has multiple references");
216                let col = nested_mut
217                    .as_any_mut()
218                    .downcast_mut::<ColumnUInt32>()
219                    .expect("Nullable nested column is not UInt32");
220                col.append(val);
221            }
222        }
223    }
224
225    /// Check if value at index is null (alias for is_null)
226    pub fn is_null_at(&self, index: usize) -> bool {
227        self.is_null(index)
228    }
229
230    /// Get a reference to the value at the given index
231    /// Returns the nested column for accessing the value (check is_null
232    /// first!)
233    pub fn at(&self, _index: usize) -> ColumnRef {
234        self.nested_ref()
235    }
236
237    /// Get the number of elements (alias for size())
238    pub fn len(&self) -> usize {
239        self.nulls.size()
240    }
241
242    /// Check if the nullable column is empty
243    pub fn is_empty(&self) -> bool {
244        self.nulls.size() == 0
245    }
246}
247
248impl Column for ColumnNullable {
249    fn column_type(&self) -> &Type {
250        &self.type_
251    }
252
253    fn size(&self) -> usize {
254        self.nulls.size()
255    }
256
257    fn clear(&mut self) {
258        // Clear both columns
259        let nulls_mut = Arc::get_mut(&mut self.nulls)
260            .expect("Cannot clear shared nulls column");
261        nulls_mut.clear();
262
263        let nested_mut = Arc::get_mut(&mut self.nested)
264            .expect("Cannot clear shared nested column");
265        nested_mut.clear();
266    }
267
268    fn reserve(&mut self, new_cap: usize) {
269        let nulls_mut = Arc::get_mut(&mut self.nulls)
270            .expect("Cannot reserve in shared nulls column");
271        nulls_mut.reserve(new_cap);
272
273        let nested_mut = Arc::get_mut(&mut self.nested)
274            .expect("Cannot reserve in shared nested column");
275        nested_mut.reserve(new_cap);
276    }
277
278    fn append_column(&mut self, other: ColumnRef) -> Result<()> {
279        let other = other
280            .as_any()
281            .downcast_ref::<ColumnNullable>()
282            .ok_or_else(|| Error::TypeMismatch {
283                expected: self.type_.name(),
284                actual: other.column_type().name(),
285            })?;
286
287        // Check that nested types match
288        if self.nested.column_type().name()
289            != other.nested.column_type().name()
290        {
291            return Err(Error::TypeMismatch {
292                expected: self.nested.column_type().name(),
293                actual: other.nested.column_type().name(),
294            });
295        }
296
297        // Append nulls column
298        let nulls_mut = Arc::get_mut(&mut self.nulls).ok_or_else(|| {
299            Error::Protocol("Cannot append to shared nulls column".to_string())
300        })?;
301        nulls_mut.append_column(other.nulls.clone())?;
302
303        // Append nested data
304        let nested_mut = Arc::get_mut(&mut self.nested).ok_or_else(|| {
305            Error::Protocol(
306                "Cannot append to shared nested column".to_string(),
307            )
308        })?;
309        nested_mut.append_column(other.nested.clone())?;
310
311        Ok(())
312    }
313
314    fn load_from_buffer(
315        &mut self,
316        buffer: &mut &[u8],
317        rows: usize,
318    ) -> Result<()> {
319        // Load null bitmap
320        if rows > 0 {
321            let nulls_mut =
322                Arc::get_mut(&mut self.nulls).ok_or_else(|| {
323                    Error::Protocol(
324                        "Cannot load into shared nulls column".to_string(),
325                    )
326                })?;
327            nulls_mut.load_from_buffer(buffer, rows)?;
328
329            // Load nested column data
330            let nested_mut =
331                Arc::get_mut(&mut self.nested).ok_or_else(|| {
332                    Error::Protocol(
333                        "Cannot load into shared nested column".to_string(),
334                    )
335                })?;
336            nested_mut.load_from_buffer(buffer, rows)?;
337        }
338
339        Ok(())
340    }
341
342    fn save_prefix(&self, buffer: &mut BytesMut) -> Result<()> {
343        // Delegate to nested column's save_prefix
344        self.nested.save_prefix(buffer)
345    }
346
347    fn save_to_buffer(&self, buffer: &mut BytesMut) -> Result<()> {
348        // Write null bitmap
349        self.nulls.save_to_buffer(buffer)?;
350
351        // Write nested column data
352        self.nested.save_to_buffer(buffer)?;
353
354        Ok(())
355    }
356
357    fn clone_empty(&self) -> ColumnRef {
358        Arc::new(
359            ColumnNullable::from_parts(
360                self.nested.clone_empty(),
361                self.nulls.clone_empty(),
362            )
363            .expect("clone_empty should succeed"),
364        )
365    }
366
367    fn slice(&self, begin: usize, len: usize) -> Result<ColumnRef> {
368        if begin + len > self.size() {
369            return Err(Error::InvalidArgument(format!(
370                "Slice out of bounds: begin={}, len={}, size={}",
371                begin,
372                len,
373                self.size()
374            )));
375        }
376
377        let sliced_nulls = self.nulls.slice(begin, len)?;
378        let sliced_nested = self.nested.slice(begin, len)?;
379
380        Ok(Arc::new(
381            ColumnNullable::from_parts(sliced_nested, sliced_nulls)
382                .expect("slice should create valid ColumnNullable"),
383        ))
384    }
385
386    fn as_any(&self) -> &dyn std::any::Any {
387        self
388    }
389
390    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
391        self
392    }
393}
394
395/// Typed nullable column wrapper (matches C++ ColumnNullableT)
396///
397/// Provides typed access to nullable columns with methods that work
398/// with `Option<T>` instead of raw column operations.
399pub struct ColumnNullableT<T: Column> {
400    inner: ColumnNullable,
401    _phantom: PhantomData<T>,
402}
403
404impl<T: Column + 'static> ColumnNullableT<T> {
405    /// Create a new typed nullable column from parts
406    pub fn from_parts(nested: Arc<T>, nulls: ColumnRef) -> Result<Self> {
407        let inner = ColumnNullable::from_parts(nested, nulls)?;
408        Ok(Self { inner, _phantom: PhantomData })
409    }
410
411    /// Create from nested column only (all non-null initially)
412    pub fn from_nested(nested: Arc<T>) -> Self {
413        let size = nested.size();
414        let mut nulls = ColumnUInt8::new();
415        for _ in 0..size {
416            nulls.append(0);
417        }
418        Self {
419            inner: ColumnNullable::from_parts(nested, Arc::new(nulls))
420                .expect("from_nested should succeed"),
421            _phantom: PhantomData,
422        }
423    }
424
425    /// Create with type
426    pub fn new(type_: Type) -> Self {
427        let inner = ColumnNullable::new(type_);
428        Self { inner, _phantom: PhantomData }
429    }
430
431    /// Wrap a ColumnNullable (matches C++ Wrap)
432    pub fn wrap(col: ColumnNullable) -> Self {
433        Self { inner: col, _phantom: PhantomData }
434    }
435
436    /// Wrap from ColumnRef
437    pub fn wrap_ref(col: ColumnRef) -> Result<Self> {
438        let nullable = col
439            .as_any()
440            .downcast_ref::<ColumnNullable>()
441            .ok_or_else(|| Error::TypeMismatch {
442                expected: "ColumnNullable".to_string(),
443                actual: "unknown".to_string(),
444            })?;
445
446        // Clone the inner data to create owned ColumnNullable
447        Ok(Self::wrap(ColumnNullable::from_parts(
448            nullable.nested_ref(),
449            nullable.nulls(),
450        )?))
451    }
452
453    /// Get the typed nested column
454    pub fn typed_nested(&self) -> Result<Arc<T>> {
455        self.inner
456            .nested_ref()
457            .as_any()
458            .downcast_ref::<T>()
459            .map(|_| {
460                // We need to clone the Arc with the right type
461                // This is safe because we just verified the type
462                unsafe {
463                    let ptr = Arc::into_raw(self.inner.nested_ref());
464                    let typed_ptr = ptr as *const T;
465                    Arc::from_raw(typed_ptr)
466                }
467            })
468            .ok_or_else(|| Error::TypeMismatch {
469                expected: std::any::type_name::<T>().to_string(),
470                actual: "unknown".to_string(),
471            })
472    }
473
474    /// Check if value at index is null
475    pub fn is_null(&self, index: usize) -> bool {
476        self.inner.is_null(index)
477    }
478
479    /// Get the inner ColumnNullable
480    pub fn inner(&self) -> &ColumnNullable {
481        &self.inner
482    }
483
484    /// Get mutable inner ColumnNullable
485    pub fn inner_mut(&mut self) -> &mut ColumnNullable {
486        &mut self.inner
487    }
488
489    /// Get size
490    pub fn len(&self) -> usize {
491        self.inner.len()
492    }
493
494    /// Check if empty
495    pub fn is_empty(&self) -> bool {
496        self.inner.is_empty()
497    }
498}
499
500impl<T: Column + 'static> Column for ColumnNullableT<T> {
501    fn column_type(&self) -> &Type {
502        self.inner.column_type()
503    }
504
505    fn size(&self) -> usize {
506        self.inner.size()
507    }
508
509    fn clear(&mut self) {
510        self.inner.clear()
511    }
512
513    fn reserve(&mut self, new_cap: usize) {
514        self.inner.reserve(new_cap)
515    }
516
517    fn append_column(&mut self, other: ColumnRef) -> Result<()> {
518        self.inner.append_column(other)
519    }
520
521    fn load_from_buffer(
522        &mut self,
523        buffer: &mut &[u8],
524        rows: usize,
525    ) -> Result<()> {
526        self.inner.load_from_buffer(buffer, rows)
527    }
528
529    fn save_prefix(&self, buffer: &mut BytesMut) -> Result<()> {
530        self.inner.save_prefix(buffer)
531    }
532
533    fn save_to_buffer(&self, buffer: &mut BytesMut) -> Result<()> {
534        self.inner.save_to_buffer(buffer)
535    }
536
537    fn clone_empty(&self) -> ColumnRef {
538        Arc::new(Self::wrap(
539            self.inner
540                .clone_empty()
541                .as_any()
542                .downcast_ref::<ColumnNullable>()
543                .expect("clone_empty must return ColumnNullable")
544                .clone(),
545        ))
546    }
547
548    fn slice(&self, begin: usize, len: usize) -> Result<ColumnRef> {
549        let sliced = self.inner.slice(begin, len)?;
550        Ok(Arc::new(Self::wrap(
551            sliced
552                .as_any()
553                .downcast_ref::<ColumnNullable>()
554                .expect("slice must return ColumnNullable")
555                .clone(),
556        )))
557    }
558
559    fn as_any(&self) -> &dyn std::any::Any {
560        self
561    }
562
563    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
564        self
565    }
566}
567
568// Implement Clone for ColumnNullable
569impl Clone for ColumnNullable {
570    fn clone(&self) -> Self {
571        Self {
572            type_: self.type_.clone(),
573            nested: self.nested.clone(),
574            nulls: self.nulls.clone(),
575        }
576    }
577}
578
579#[cfg(test)]
580#[cfg_attr(coverage_nightly, coverage(off))]
581mod tests {
582    use super::*;
583    use crate::{
584        column::{
585            numeric::{
586                ColumnUInt32,
587                ColumnUInt64,
588            },
589            string::ColumnString,
590        },
591        types::Type,
592    };
593
594    #[test]
595    fn test_nullable_creation() {
596        let nested = Arc::new(ColumnUInt64::new());
597        let col = ColumnNullable::with_nested(nested);
598        assert_eq!(col.size(), 0);
599    }
600
601    #[test]
602    fn test_nullable_append() {
603        let nested = Arc::new(ColumnUInt64::new());
604        let mut col = ColumnNullable::with_nested(nested);
605
606        col.append_non_null();
607        col.append_null();
608        col.append_non_null();
609
610        assert_eq!(col.size(), 3);
611        assert!(!col.is_null(0));
612        assert!(col.is_null(1));
613        assert!(!col.is_null(2));
614    }
615
616    #[test]
617    fn test_nullable_nulls_bitmap() {
618        let nested = Arc::new(ColumnUInt64::new());
619        let mut col = ColumnNullable::with_nested(nested);
620
621        col.append_non_null();
622        col.append_null();
623        col.append_null();
624        col.append_non_null();
625
626        let nulls_ref = col.nulls();
627        let nulls_col =
628            nulls_ref.as_any().downcast_ref::<ColumnUInt8>().unwrap();
629        assert_eq!(nulls_col.at(0), 0);
630        assert_eq!(nulls_col.at(1), 1);
631        assert_eq!(nulls_col.at(2), 1);
632        assert_eq!(nulls_col.at(3), 0);
633    }
634
635    #[test]
636    fn test_nullable_save_load() {
637        let mut nested = ColumnUInt64::new();
638        nested.append(10);
639        nested.append(20);
640        nested.append(30);
641
642        let mut col = ColumnNullable::with_nested(Arc::new(nested));
643        col.append_non_null();
644        col.append_null();
645        col.append_non_null();
646
647        let mut buffer = BytesMut::new();
648        col.save_to_buffer(&mut buffer).unwrap();
649
650        // Verify buffer contains null bitmap + nested data
651        let nulls_len = 3; // 3 rows
652        assert!(buffer.len() >= nulls_len);
653        assert_eq!(&buffer[..nulls_len], &[0, 1, 0]);
654    }
655
656    #[test]
657    fn test_nullable_load_null_bitmap() {
658        use bytes::{
659            BufMut,
660            BytesMut,
661        };
662
663        let nested = Arc::new(ColumnUInt64::new());
664        let mut col = ColumnNullable::with_nested(nested);
665
666        // Null bitmap: [1, 0, 1, 0, 1] (5 bytes)
667        let mut data = BytesMut::new();
668        data.extend_from_slice(&[1u8, 0, 1, 0, 1]);
669
670        // Must also include nested data (5 UInt64 values)
671        for i in 0..5u64 {
672            data.put_u64_le(i);
673        }
674
675        let mut reader = &data[..];
676        col.load_from_buffer(&mut reader, 5).unwrap();
677
678        assert_eq!(col.size(), 5);
679        assert!(col.is_null(0));
680        assert!(!col.is_null(1));
681        assert!(col.is_null(2));
682        assert!(!col.is_null(3));
683        assert!(col.is_null(4));
684    }
685
686    #[test]
687    fn test_nullable_slice() {
688        let mut nested = ColumnUInt64::new();
689        // Add data to the nested column
690        for i in 0..10 {
691            nested.append(i);
692        }
693        let mut col = ColumnNullable::with_nested(Arc::new(nested));
694
695        for i in 0..10 {
696            if i % 2 == 0 {
697                col.append_null();
698            } else {
699                col.append_non_null();
700            }
701        }
702
703        let sliced = col.slice(2, 5).unwrap();
704        let sliced_col =
705            sliced.as_any().downcast_ref::<ColumnNullable>().unwrap();
706
707        assert_eq!(sliced_col.size(), 5);
708        assert!(sliced_col.is_null(0)); // index 2 in original
709        assert!(!sliced_col.is_null(1)); // index 3 in original
710        assert!(sliced_col.is_null(2)); // index 4 in original
711    }
712
713    #[test]
714    fn test_nullable_with_string() {
715        let nested = Arc::new(ColumnString::new(Type::string()));
716        let mut col = ColumnNullable::with_nested(nested);
717
718        col.append_non_null();
719        col.append_null();
720        col.append_non_null();
721
722        assert_eq!(col.size(), 3);
723        assert!(!col.is_null(0));
724        assert!(col.is_null(1));
725        assert!(!col.is_null(2));
726    }
727
728    #[test]
729    fn test_nullable_type_mismatch() {
730        let nested1 = Arc::new(ColumnUInt64::new());
731        let mut col1 = ColumnNullable::with_nested(nested1);
732
733        let nested2 = Arc::new(ColumnString::new(Type::string()));
734        let col2 = ColumnNullable::with_nested(nested2);
735
736        let result = col1.append_column(Arc::new(col2));
737        assert!(result.is_err());
738    }
739
740    #[test]
741    fn test_nullable_out_of_bounds() {
742        let nested = Arc::new(ColumnUInt64::new());
743        let mut col = ColumnNullable::with_nested(nested);
744
745        col.append_null();
746        col.append_non_null();
747
748        // Out of bounds should return false (not null)
749        assert!(!col.is_null(100));
750    }
751
752    #[test]
753    fn test_nullable_append_column() {
754        // Create first nullable column: [Some(1), None, Some(3)]
755        let mut col1 =
756            ColumnNullable::with_nested(Arc::new(ColumnUInt32::new()));
757        col1.append_nullable(Some(1));
758        col1.append_nullable(None);
759        col1.append_nullable(Some(3));
760
761        // Create second nullable column: [None, Some(5)]
762        let mut col2 =
763            ColumnNullable::with_nested(Arc::new(ColumnUInt32::new()));
764        col2.append_nullable(None);
765        col2.append_nullable(Some(5));
766
767        // Append col2 to col1
768        col1.append_column(Arc::new(col2))
769            .expect("append_column should succeed");
770
771        // Verify we have 5 elements total
772        assert_eq!(col1.size(), 5, "Should have 5 elements after append");
773
774        // Verify null flags are correct
775        assert!(!col1.is_null(0), "Element 0 should not be null (value=1)");
776        assert!(col1.is_null(1), "Element 1 should be null");
777        assert!(!col1.is_null(2), "Element 2 should not be null (value=3)");
778        assert!(col1.is_null(3), "Element 3 should be null");
779        assert!(!col1.is_null(4), "Element 4 should not be null (value=5)");
780
781        // Verify nested data was actually appended
782        let nested: &ColumnUInt32 = col1.nested();
783        assert_eq!(
784            nested.size(),
785            5,
786            "Nested column should have 5 total elements"
787        );
788    }
789
790    #[test]
791    #[should_panic(expected = "Cannot clear shared nulls column")]
792    fn test_nullable_clear_panics_on_shared_nulls() {
793        use crate::column::numeric::ColumnUInt32;
794
795        // Create a nullable column and add data
796        let mut col =
797            ColumnNullable::with_nested(Arc::new(ColumnUInt32::new()));
798        col.append_nullable(Some(1));
799        col.append_nullable(None);
800        col.append_nullable(Some(3));
801
802        // Create a second reference to the nulls column (share it)
803        let _shared_ref = col.nulls();
804
805        // Now nulls has multiple Arc references, so clear() MUST panic
806        col.clear();
807    }
808
809    #[test]
810    fn test_nullable_roundtrip_nested_data() {
811        use bytes::BytesMut;
812
813        // Create nullable column with data: [Some(1), None, Some(3)]
814        let mut col =
815            ColumnNullable::with_nested(Arc::new(ColumnUInt32::new()));
816        col.append_nullable(Some(1));
817        col.append_nullable(None);
818        col.append_nullable(Some(3));
819
820        assert_eq!(col.size(), 3, "Original should have 3 elements");
821
822        // Save to buffer
823        let mut buffer = BytesMut::new();
824        col.save_to_buffer(&mut buffer).expect("save should succeed");
825
826        // Load into new nullable column
827        let nested_empty = Arc::new(ColumnUInt32::new());
828        let mut col_loaded = ColumnNullable::with_nested(nested_empty);
829
830        let mut buf_slice = &buffer[..];
831        col_loaded
832            .load_from_buffer(&mut buf_slice, 3)
833            .expect("load should succeed");
834
835        // Verify structure
836        assert_eq!(col_loaded.size(), 3, "Loaded should have 3 elements");
837
838        // Verify null flags
839        assert!(!col_loaded.is_null(0), "Element 0 should not be null");
840        assert!(col_loaded.is_null(1), "Element 1 should be null");
841        assert!(!col_loaded.is_null(2), "Element 2 should not be null");
842
843        // Verify nested data was actually loaded
844        let nested_loaded: &ColumnUInt32 = col_loaded.nested();
845        assert_eq!(
846            nested_loaded.size(),
847            3,
848            "Nested should have 3 elements after load"
849        );
850    }
851
852    #[test]
853    fn test_nullable_t_creation() {
854        let nested = Arc::new(ColumnUInt64::new());
855        let col = ColumnNullableT::<ColumnUInt64>::from_nested(nested);
856        assert_eq!(col.size(), 0);
857    }
858
859    #[test]
860    fn test_nullable_t_wrap() {
861        let nested = Arc::new(ColumnUInt64::new());
862        let nullable = ColumnNullable::with_nested(nested);
863        let col_t = ColumnNullableT::<ColumnUInt64>::wrap(nullable);
864        assert_eq!(col_t.size(), 0);
865    }
866
867    #[test]
868    fn test_nullable_t_typed_nested() {
869        let mut nested = ColumnUInt64::new();
870        nested.append(42);
871        let col =
872            ColumnNullableT::<ColumnUInt64>::from_nested(Arc::new(nested));
873
874        let typed = col.typed_nested().unwrap();
875        assert_eq!(typed.size(), 1);
876        assert_eq!(typed.at(0), 42);
877    }
878}