1use std::fmt;
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum Value {
6 Null,
7 Integer(i64),
8 Real(f64),
9 Text(String),
10 Boolean(bool),
11}
12
13impl Value {
14 pub fn type_name(&self) -> &'static str {
15 match self {
16 Value::Null => "NULL",
17 Value::Integer(_) => "INTEGER",
18 Value::Real(_) => "REAL",
19 Value::Text(_) => "TEXT",
20 Value::Boolean(_) => "BOOLEAN",
21 }
22 }
23
24 pub fn cmp_value(&self, other: &Value) -> std::cmp::Ordering {
26 use std::cmp::Ordering;
27 fn rank(v: &Value) -> u8 {
28 match v {
29 Value::Null => 0,
30 Value::Boolean(_) => 1,
31 Value::Integer(_) | Value::Real(_) => 2,
32 Value::Text(_) => 3,
33 }
34 }
35 match (self, other) {
36 (Value::Null, Value::Null) => Ordering::Equal,
37 (Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
38 (Value::Integer(a), Value::Integer(b)) => a.cmp(b),
39 (Value::Real(a), Value::Real(b)) => cmp_real(*a, *b),
40 (Value::Integer(a), Value::Real(b)) => cmp_real(*a as f64, *b),
41 (Value::Real(a), Value::Integer(b)) => cmp_real(*a, *b as f64),
42 (Value::Text(a), Value::Text(b)) => a.cmp(b),
43 _ => rank(self).cmp(&rank(other)),
44 }
45 }
46
47 pub fn is_truthy(&self) -> Option<bool> {
49 match self {
50 Value::Null => None,
51 Value::Boolean(b) => Some(*b),
52 Value::Integer(i) => Some(*i != 0),
53 Value::Real(f) => Some(*f != 0.0),
54 Value::Text(_) => None,
55 }
56 }
57
58 pub fn coerce_to(&self, ty: &ColumnType) -> Result<Value, String> {
59 match (ty, self) {
60 (ColumnType::Null | ColumnType::Any, _) => Ok(self.clone()),
61 (ColumnType::Integer, Value::Integer(_)) | (ColumnType::Integer, Value::Null) => {
62 Ok(self.clone())
63 }
64 (ColumnType::Integer, Value::Boolean(b)) => Ok(Value::Integer(*b as i64)),
65 (ColumnType::Integer, Value::Real(f)) => {
66 if !f.is_finite() || *f < i64::MIN as f64 || *f >= i64::MAX as f64 {
67 Err("cannot convert non-finite or out-of-range REAL to INTEGER".to_string())
68 } else {
69 Ok(Value::Integer(*f as i64))
70 }
71 }
72 (ColumnType::Integer, Value::Text(t)) => t
73 .parse::<i64>()
74 .map(Value::Integer)
75 .map_err(|_| format!("cannot convert {t:?} to INTEGER")),
76 (ColumnType::Real, Value::Real(f)) if f.is_finite() => Ok(self.clone()),
77 (ColumnType::Real, Value::Real(_)) => Err("cannot store a non-finite REAL".to_string()),
78 (ColumnType::Real, Value::Null) => Ok(self.clone()),
79 (ColumnType::Real, Value::Integer(i)) => Ok(Value::Real(*i as f64)),
80 (ColumnType::Real, Value::Boolean(_)) => {
81 Err("cannot convert BOOLEAN to REAL".to_string())
82 }
83 (ColumnType::Real, Value::Text(t)) => {
84 let value = t
85 .parse::<f64>()
86 .map_err(|_| format!("cannot convert {t:?} to REAL"))?;
87 if value.is_finite() {
88 Ok(Value::Real(value))
89 } else {
90 Err(format!("cannot convert {t:?} to REAL"))
91 }
92 }
93 (ColumnType::Text, Value::Text(_)) | (ColumnType::Text, Value::Null) => {
94 Ok(self.clone())
95 }
96 (ColumnType::Text, v) => Ok(Value::Text(v.to_string())),
97 (ColumnType::Boolean, Value::Boolean(_)) | (ColumnType::Boolean, Value::Null) => {
98 Ok(self.clone())
99 }
100 (ColumnType::Boolean, Value::Integer(i)) => Ok(Value::Boolean(*i != 0)),
101 (ColumnType::Boolean, Value::Real(_)) | (ColumnType::Boolean, Value::Text(_)) => {
102 Err(format!("cannot convert {} to BOOLEAN", self.type_name()))
103 }
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn rejects_non_finite_and_out_of_range_real_conversions() {
114 assert!(
115 Value::Real(f64::INFINITY)
116 .coerce_to(&ColumnType::Real)
117 .is_err()
118 );
119 assert!(
120 Value::Text("NaN".into())
121 .coerce_to(&ColumnType::Real)
122 .is_err()
123 );
124 assert!(
125 Value::Real(i64::MAX as f64)
126 .coerce_to(&ColumnType::Integer)
127 .is_err()
128 );
129 assert_eq!(
130 Value::Real(i64::MIN as f64)
131 .coerce_to(&ColumnType::Integer)
132 .unwrap(),
133 Value::Integer(i64::MIN)
134 );
135 }
136}
137
138fn cmp_real(left: f64, right: f64) -> std::cmp::Ordering {
139 left.partial_cmp(&right)
140 .unwrap_or_else(|| left.total_cmp(&right))
141}
142
143impl fmt::Display for Value {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Value::Null => write!(f, "NULL"),
147 Value::Integer(i) => write!(f, "{i}"),
148 Value::Real(x) => write!(f, "{x}"),
149 Value::Text(s) => write!(f, "'{s}'"),
150 Value::Boolean(b) => write!(f, "{b}"),
151 }
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
156pub enum ColumnType {
157 Integer,
158 Real,
159 Text,
160 Boolean,
161 Any,
163 Null,
165}
166
167impl ColumnType {
168 pub fn parse(name: &str) -> Option<ColumnType> {
169 match name.to_uppercase().as_str() {
170 "INTEGER" | "INT" | "BIGINT" | "SMALLINT" => Some(ColumnType::Integer),
171 "REAL" | "FLOAT" | "DOUBLE" => Some(ColumnType::Real),
172 "TEXT" | "VARCHAR" | "CHAR" | "STRING" => Some(ColumnType::Text),
173 "BOOLEAN" | "BOOL" => Some(ColumnType::Boolean),
174 _ => None,
175 }
176 }
177}