llkv_expr/
literal.rs

1use arrow::datatypes::ArrowPrimitiveType;
2use std::ops::Bound;
3
4/// A literal value that has not yet been coerced into a specific native
5/// type. This allows for type inference to be deferred until the column
6/// type is known.
7#[derive(Debug, Clone, PartialEq)]
8pub enum Literal {
9    Integer(i128),
10    Float(f64),
11    String(String),
12    // Other types like Bool, Bytes can be added here.
13}
14
15macro_rules! impl_from_for_literal {
16    ($variant:ident, $($t:ty),*) => {
17        $(
18            impl From<$t> for Literal {
19                fn from(v: $t) -> Self {
20                    Literal::$variant(v.into())
21                }
22            }
23        )*
24    };
25}
26
27impl_from_for_literal!(Integer, i8, i16, i32, i64, i128, u8, u16, u32, u64);
28impl_from_for_literal!(Float, f32, f64);
29
30impl From<&str> for Literal {
31    fn from(v: &str) -> Self {
32        Literal::String(v.to_string())
33    }
34}
35
36/// Error converting a `Literal` into a concrete native type.
37#[derive(Debug, Clone, PartialEq)]
38pub enum LiteralCastError {
39    /// Tried to coerce a non-integer literal into an integer native type.
40    TypeMismatch {
41        expected: &'static str,
42        got: &'static str,
43    },
44    /// Integer value does not fit in the destination type.
45    OutOfRange { target: &'static str, value: i128 },
46    /// Float value does not fit in the destination type.
47    FloatOutOfRange { target: &'static str, value: f64 },
48}
49
50impl std::fmt::Display for LiteralCastError {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        match self {
53            LiteralCastError::TypeMismatch { expected, got } => {
54                write!(f, "expected {}, got {}", expected, got)
55            }
56            LiteralCastError::OutOfRange { target, value } => {
57                write!(f, "value {} out of range for {}", value, target)
58            }
59            LiteralCastError::FloatOutOfRange { target, value } => {
60                write!(f, "value {} out of range for {}", value, target)
61            }
62        }
63    }
64}
65
66impl std::error::Error for LiteralCastError {}
67
68/// Helper trait implemented for primitive types that can be produced from a `Literal`.
69pub trait FromLiteral: Sized {
70    fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError>;
71}
72
73macro_rules! impl_from_literal_int {
74    ($($ty:ty),* $(,)?) => {
75        $(
76            impl FromLiteral for $ty {
77                fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
78                    match lit {
79                        Literal::Integer(i) => <$ty>::try_from(*i).map_err(|_| {
80                            LiteralCastError::OutOfRange {
81                                target: std::any::type_name::<$ty>(),
82                                value: *i,
83                            }
84                        }),
85                        Literal::Float(_) => Err(LiteralCastError::TypeMismatch {
86                            expected: "integer",
87                            got: "float",
88                        }),
89                        Literal::String(_) => Err(LiteralCastError::TypeMismatch {
90                            expected: "integer",
91                            got: "string",
92                        }),
93                    }
94                }
95            }
96        )*
97    };
98}
99
100impl_from_literal_int!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, usize);
101
102impl FromLiteral for f32 {
103    fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
104        let value = match lit {
105            Literal::Float(f) => *f,
106            Literal::Integer(i) => *i as f64,
107            Literal::String(_) => {
108                return Err(LiteralCastError::TypeMismatch {
109                    expected: "float",
110                    got: "string",
111                });
112            }
113        };
114        let cast = value as f32;
115        if value.is_finite() && !cast.is_finite() {
116            return Err(LiteralCastError::FloatOutOfRange {
117                target: "f32",
118                value,
119            });
120        }
121        Ok(cast)
122    }
123}
124
125impl FromLiteral for bool {
126    fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
127        match lit {
128            Literal::Integer(i) => match *i {
129                0 => Ok(false),
130                1 => Ok(true),
131                value => Err(LiteralCastError::OutOfRange {
132                    target: "bool",
133                    value,
134                }),
135            },
136            Literal::Float(_) => Err(LiteralCastError::TypeMismatch {
137                expected: "bool",
138                got: "float",
139            }),
140            Literal::String(s) => {
141                let normalized = s.trim().to_ascii_lowercase();
142                match normalized.as_str() {
143                    "true" | "t" | "1" => Ok(true),
144                    "false" | "f" | "0" => Ok(false),
145                    _ => Err(LiteralCastError::TypeMismatch {
146                        expected: "bool",
147                        got: "string",
148                    }),
149                }
150            }
151        }
152    }
153}
154
155impl FromLiteral for f64 {
156    fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
157        match lit {
158            Literal::Float(f) => Ok(*f),
159            Literal::Integer(i) => Ok(*i as f64),
160            Literal::String(_) => Err(LiteralCastError::TypeMismatch {
161                expected: "float",
162                got: "string",
163            }),
164        }
165    }
166}
167
168fn literal_type_name(lit: &Literal) -> &'static str {
169    match lit {
170        Literal::Integer(_) => "integer",
171        Literal::Float(_) => "float",
172        Literal::String(_) => "string",
173    }
174}
175
176/// Convert a `Literal` into an owned `String`.
177pub fn literal_to_string(lit: &Literal) -> Result<String, LiteralCastError> {
178    match lit {
179        Literal::String(s) => Ok(s.clone()),
180        _ => Err(LiteralCastError::TypeMismatch {
181            expected: "string",
182            got: literal_type_name(lit),
183        }),
184    }
185}
186
187/// Convert a `Literal` into a concrete native type `T`.
188pub fn literal_to_native<T>(lit: &Literal) -> Result<T, LiteralCastError>
189where
190    T: FromLiteral + Copy + 'static,
191{
192    T::from_literal(lit)
193}
194
195/// Convert a bound of `Literal` into a bound of `T::Native`.
196///
197/// Kept generic over `T: ArrowPrimitiveType` so callers (like the table
198/// crate) can use `bound_to_native::<T>()` with `T::Native` inferred.
199/// Error type is `LiteralCastError` since this crate is independent of
200/// table-layer errors.
201pub fn bound_to_native<T>(bound: &Bound<Literal>) -> Result<Bound<T::Native>, LiteralCastError>
202where
203    T: ArrowPrimitiveType,
204    T::Native: FromLiteral + Copy,
205{
206    Ok(match bound {
207        Bound::Unbounded => Bound::Unbounded,
208        Bound::Included(l) => Bound::Included(literal_to_native::<T::Native>(l)?),
209        Bound::Excluded(l) => Bound::Excluded(literal_to_native::<T::Native>(l)?),
210    })
211}