rbdc_sqlite/
type_info.rs

1use std::fmt::{self, Display, Formatter};
2use std::os::raw::c_int;
3use std::str::FromStr;
4
5use libsqlite3_sys::{SQLITE_BLOB, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_NULL, SQLITE_TEXT};
6use rbdc::Error;
7use rbs::Value;
8
9pub trait Type {
10    fn type_info(&self) -> SqliteTypeInfo;
11}
12
13// for optionals, the underlying SQL type is identical
14impl<T: Type> Type for Option<T> {
15    fn type_info(&self) -> SqliteTypeInfo {
16        match self {
17            None => SqliteTypeInfo(DataType::Null),
18            Some(v) => v.type_info(),
19        }
20    }
21}
22
23#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
24pub(crate) enum DataType {
25    Null,
26    Int,
27    Float,
28    Text,
29    Blob,
30
31    // TODO: Support NUMERIC
32    #[allow(dead_code)]
33    Numeric,
34
35    // non-standard extensions
36    Bool,
37    Int64,
38    Date,
39    Time,
40    Datetime,
41}
42
43/// Type information for a SQLite type.
44#[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
45pub struct SqliteTypeInfo(pub(crate) DataType);
46
47impl SqliteTypeInfo {
48    pub fn null() -> Self {
49        SqliteTypeInfo(DataType::Null)
50    }
51}
52
53impl Display for SqliteTypeInfo {
54    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
55        f.pad(self.name())
56    }
57}
58
59impl SqliteTypeInfo {
60    pub fn is_null(&self) -> bool {
61        matches!(self.0, DataType::Null)
62    }
63
64    pub fn name(&self) -> &str {
65        match self.0 {
66            DataType::Null => "NULL",
67            DataType::Text => "TEXT",
68            DataType::Float => "REAL",
69            DataType::Blob => "BLOB",
70            DataType::Int | DataType::Int64 => "INTEGER",
71            DataType::Numeric => "NUMERIC",
72
73            // non-standard extensions
74            DataType::Bool => "BOOLEAN",
75            DataType::Date => "DATE",
76            DataType::Time => "TIME",
77            DataType::Datetime => "DATETIME",
78        }
79    }
80}
81
82impl DataType {
83    pub(crate) fn from_code(code: c_int) -> Self {
84        match code {
85            SQLITE_INTEGER => DataType::Int,
86            SQLITE_FLOAT => DataType::Float,
87            SQLITE_BLOB => DataType::Blob,
88            SQLITE_NULL => DataType::Null,
89            SQLITE_TEXT => DataType::Text,
90
91            // https://sqlite.org/c3ref/c_blob.html
92            _ => panic!("unknown data type code {}", code),
93        }
94    }
95}
96
97// note: this implementation is particularly important as this is how the macros determine
98//       what Rust type maps to what *declared* SQL type
99// <https://www.sqlite.org/datatype3.html#affname>
100impl FromStr for DataType {
101    type Err = Error;
102
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        let s = s.to_ascii_lowercase();
105        Ok(match &*s {
106            "int4" => DataType::Int,
107            "int8" => DataType::Int64,
108            "boolean" | "bool" => DataType::Bool,
109
110            "date" => DataType::Date,
111            "time" => DataType::Time,
112            "datetime" | "timestamp" => DataType::Datetime,
113
114            _ if s.contains("int") => DataType::Int64,
115
116            _ if s.contains("char") || s.contains("clob") || s.contains("text") => DataType::Text,
117
118            _ if s.contains("blob") => DataType::Blob,
119
120            _ if s.contains("real") || s.contains("floa") || s.contains("doub") => DataType::Float,
121
122            _ => {
123                return Err(format!("unknown type: `{}`", s).into());
124            }
125        })
126    }
127}
128
129impl Type for Value {
130    fn type_info(&self) -> SqliteTypeInfo {
131        match self {
132            Value::Null => SqliteTypeInfo::null(),
133            Value::Bool(_) => SqliteTypeInfo(DataType::Bool),
134            Value::I32(_) => SqliteTypeInfo(DataType::Int),
135            Value::I64(_) => SqliteTypeInfo(DataType::Int64),
136            Value::U32(_) => SqliteTypeInfo(DataType::Int),
137            Value::U64(_) => SqliteTypeInfo(DataType::Int64),
138            Value::F32(_) => SqliteTypeInfo(DataType::Float),
139            Value::F64(_) => SqliteTypeInfo(DataType::Float),
140            Value::String(_) => SqliteTypeInfo(DataType::Text),
141            Value::Binary(_) => SqliteTypeInfo(DataType::Blob),
142            Value::Array(_) => SqliteTypeInfo(DataType::Null),
143            Value::Map(_) => SqliteTypeInfo(DataType::Null),
144            Value::Ext(t, _) => match *t {
145                "Date" => SqliteTypeInfo(DataType::Text),
146                "DateTime" => SqliteTypeInfo(DataType::Text),
147                "Time" => SqliteTypeInfo(DataType::Text),
148                "Timestamp" => SqliteTypeInfo(DataType::Int64),
149                "Decimal" => SqliteTypeInfo(DataType::Numeric),
150                "Json" => SqliteTypeInfo(DataType::Blob),
151                "Uuid" => SqliteTypeInfo(DataType::Text),
152                _ => SqliteTypeInfo(DataType::Null),
153            },
154        }
155    }
156}
157
158#[test]
159fn test_data_type_from_str() -> Result<(), Error> {
160    assert_eq!(DataType::Int, "INT4".parse()?);
161
162    assert_eq!(DataType::Int64, "INT".parse()?);
163    assert_eq!(DataType::Int64, "INTEGER".parse()?);
164    assert_eq!(DataType::Int64, "INTBIG".parse()?);
165    assert_eq!(DataType::Int64, "MEDIUMINT".parse()?);
166
167    assert_eq!(DataType::Int64, "BIGINT".parse()?);
168    assert_eq!(DataType::Int64, "UNSIGNED BIG INT".parse()?);
169    assert_eq!(DataType::Int64, "INT8".parse()?);
170
171    assert_eq!(DataType::Text, "CHARACTER(20)".parse()?);
172    assert_eq!(DataType::Text, "NCHAR(55)".parse()?);
173    assert_eq!(DataType::Text, "TEXT".parse()?);
174    assert_eq!(DataType::Text, "CLOB".parse()?);
175
176    assert_eq!(DataType::Blob, "BLOB".parse()?);
177
178    assert_eq!(DataType::Float, "REAL".parse()?);
179    assert_eq!(DataType::Float, "FLOAT".parse()?);
180    assert_eq!(DataType::Float, "DOUBLE PRECISION".parse()?);
181
182    assert_eq!(DataType::Bool, "BOOLEAN".parse()?);
183    assert_eq!(DataType::Bool, "BOOL".parse()?);
184
185    assert_eq!(DataType::Datetime, "DATETIME".parse()?);
186    assert_eq!(DataType::Time, "TIME".parse()?);
187    assert_eq!(DataType::Date, "DATE".parse()?);
188
189    Ok(())
190}