vortex_scalar/
null.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Null scalar conversion implementations.
5//!
6//! This module provides conversions between Scalar values and the unit type `()`.
7//!
8//! # Conversion Behavior
9//!
10//! The `TryFrom<&Scalar>` implementation for `()` succeeds in two cases:
11//!
12//! 1. **Pure null scalars**: Scalars with `DType::Null` always convert successfully to `()`.
13//!
14//! 2. **Typed null scalars**: Scalars of any type (primitive, string, binary, etc.) that
15//!    have a null value also convert successfully to `()`. This includes scalars created
16//!    with methods like `Scalar::null_typed::<T>()`.
17//!
18//! This behavior means that typed null scalars (e.g., a null i32 or null string) are
19//! treated equivalently to pure null scalars for the purpose of unit type conversion.
20//!
21//! # Examples
22//!
23//! ```ignore
24//! use vortex_scalar::Scalar;
25//! use vortex_dtype::DType;
26//!
27//! // Pure null scalar converts to ()
28//! let null_scalar = Scalar::null(DType::Null);
29//! let unit: () = null_scalar.try_into().unwrap();
30//!
31//! // Typed null scalar also converts to ()
32//! let null_int = Scalar::null_typed::<i32>();
33//! let unit: () = null_int.try_into().unwrap();
34//!
35//! // Non-null scalar fails conversion
36//! let int_scalar = Scalar::primitive(42i32, Nullability::NonNullable);
37//! let result = <()>::try_from(&int_scalar);
38//! assert!(result.is_err());
39//! ```
40
41use vortex_error::VortexError;
42
43use crate::Scalar;
44
45impl TryFrom<&Scalar> for () {
46    type Error = VortexError;
47
48    fn try_from(scalar: &Scalar) -> Result<Self, Self::Error> {
49        scalar.value().as_null()
50    }
51}
52
53impl TryFrom<Scalar> for () {
54    type Error = VortexError;
55
56    fn try_from(scalar: Scalar) -> Result<Self, Self::Error> {
57        <()>::try_from(&scalar)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use vortex_dtype::{DType, Nullability};
64
65    use super::*;
66
67    #[test]
68    fn test_null_scalar_try_from_ref() {
69        let null_scalar = Scalar::null(DType::Null);
70
71        let result = <()>::try_from(&null_scalar);
72        assert!(result.is_ok());
73    }
74
75    #[test]
76    fn test_null_scalar_try_from_owned() {
77        let null_scalar = Scalar::null(DType::Null);
78
79        let result = <()>::try_from(null_scalar);
80        assert!(result.is_ok());
81    }
82
83    #[test]
84    fn test_non_null_scalar_fails_ref() {
85        let int_scalar = Scalar::primitive(42i32, Nullability::NonNullable);
86
87        let result = <()>::try_from(&int_scalar);
88        assert!(result.is_err());
89    }
90
91    #[test]
92    fn test_non_null_scalar_fails_owned() {
93        let int_scalar = Scalar::primitive(42i32, Nullability::NonNullable);
94
95        let result = <()>::try_from(int_scalar);
96        assert!(result.is_err());
97    }
98
99    #[test]
100    fn test_nullable_primitive_with_null_value() {
101        let null_int = Scalar::null_typed::<i32>();
102
103        // NOTE: Unexpected behavior - TryFrom succeeds for typed null scalars
104        let result = <()>::try_from(&null_int);
105        assert!(result.is_ok());
106    }
107
108    #[test]
109    fn test_null_string() {
110        let null_string = Scalar::null_typed::<String>();
111
112        // NOTE: Unexpected behavior - TryFrom succeeds for typed null scalars
113        let result = <()>::try_from(&null_string);
114        assert!(result.is_ok());
115    }
116
117    #[test]
118    fn test_null_bool() {
119        let null_bool = Scalar::null_typed::<bool>();
120
121        // NOTE: Unexpected behavior - TryFrom succeeds for typed null scalars
122        let result = <()>::try_from(&null_bool);
123        assert!(result.is_ok());
124    }
125
126    #[test]
127    fn test_null_list() {
128        use std::sync::Arc;
129
130        use vortex_dtype::PType;
131
132        let element_dtype = Arc::new(DType::Primitive(PType::I32, Nullability::Nullable));
133        let null_list = Scalar::list_empty(element_dtype, Nullability::Nullable);
134
135        // NOTE: Unexpected behavior - TryFrom succeeds for typed null scalars
136        let result = <()>::try_from(&null_list);
137        assert!(result.is_ok());
138    }
139
140    #[test]
141    fn test_null_struct() {
142        use vortex_dtype::{FieldDType, StructFields};
143
144        let struct_dtype = DType::Struct(
145            StructFields::from_iter([("field1", FieldDType::from(DType::Null))]),
146            Nullability::Nullable,
147        );
148
149        let null_struct = Scalar::struct_(struct_dtype, vec![Scalar::null(DType::Null)]);
150
151        // This should fail because it's a struct, not a pure null type
152        let result = <()>::try_from(&null_struct);
153        assert!(result.is_err());
154    }
155
156    #[test]
157    fn test_null_binary() {
158        let null_binary = Scalar::null(DType::Binary(Nullability::Nullable));
159
160        // NOTE: Unexpected behavior - TryFrom succeeds for typed null scalars
161        let result = <()>::try_from(&null_binary);
162        assert!(result.is_ok());
163    }
164}