Skip to main content

drizzle_types/sqlite/
sql_type.rs

1//! `SQLite` column type definitions
2//!
3//! Defines the core `SQLite` storage classes and type affinities.
4
5/// Enum representing supported `SQLite` column types.
6///
7/// These correspond to the [SQLite storage classes](https://sqlite.org/datatype3.html#storage_classes_and_datatypes).
8/// Each type maps to specific Rust types and has different capabilities for constraints and features.
9///
10/// # Examples
11///
12/// ```
13/// use drizzle_types::sqlite::SQLiteType;
14///
15/// let int_type = SQLiteType::Integer;
16/// assert_eq!(int_type.to_sql_type(), "INTEGER");
17/// assert!(int_type.is_valid_flag("autoincrement"));
18///
19/// let text_type = SQLiteType::Text;
20/// assert!(text_type.is_valid_flag("json"));
21/// assert!(!text_type.is_valid_flag("autoincrement"));
22/// ```
23#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[cfg_attr(feature = "serde", serde(rename_all = "UPPERCASE"))]
26pub enum SQLiteType {
27    /// `SQLite` INTEGER type - stores signed integers up to 8 bytes.
28    ///
29    /// See: <https://sqlite.org/datatype3.html#integer_datatype>
30    ///
31    /// Supports: primary keys, autoincrement, enums (discriminant storage)
32    Integer,
33
34    /// `SQLite` TEXT type - stores text in UTF-8, UTF-16BE, or UTF-16LE encoding.
35    ///
36    /// See: <https://sqlite.org/datatype3.html#text_datatype>
37    ///
38    /// Supports: enums (variant name storage), JSON serialization
39    Text,
40
41    /// `SQLite` BLOB type - stores binary data exactly as input.
42    ///
43    /// See: <https://sqlite.org/datatype3.html#blob_datatype>
44    ///
45    /// Supports: JSON serialization, UUID storage
46    Blob,
47
48    /// `SQLite` REAL type - stores floating point values as 8-byte IEEE floating point numbers.
49    ///
50    /// See: <https://sqlite.org/datatype3.html#real_datatype>
51    Real,
52
53    /// `SQLite` NUMERIC type - stores values as INTEGER, REAL, or TEXT depending on the value.
54    ///
55    /// See: <https://sqlite.org/datatype3.html#numeric_datatype>
56    Numeric,
57
58    /// `SQLite` ANY type - no type affinity, can store any type of data.
59    ///
60    /// See: <https://sqlite.org/datatype3.html#type_affinity>
61    #[default]
62    Any,
63}
64
65/// `SQLite` type affinity classification.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68#[cfg_attr(feature = "serde", serde(rename_all = "UPPERCASE"))]
69pub enum SQLiteAffinity {
70    Integer,
71    Text,
72    Blob,
73    Real,
74    Numeric,
75    Any,
76}
77
78impl SQLiteType {
79    /// Convert from attribute name to enum variant
80    ///
81    /// Handles common attribute names used in the macro system.
82    #[must_use]
83    pub const fn from_attribute_name(name: &str) -> Option<Self> {
84        if name.eq_ignore_ascii_case("integer") {
85            Some(Self::Integer)
86        } else if name.eq_ignore_ascii_case("text") {
87            Some(Self::Text)
88        } else if name.eq_ignore_ascii_case("blob") {
89            Some(Self::Blob)
90        } else if name.eq_ignore_ascii_case("real") {
91            Some(Self::Real)
92        } else if name.eq_ignore_ascii_case("number") || name.eq_ignore_ascii_case("numeric") {
93            Some(Self::Numeric)
94        } else if name.eq_ignore_ascii_case("boolean") {
95            Some(Self::Integer) // Store booleans as integers (0/1)
96        } else if name.eq_ignore_ascii_case("any") {
97            Some(Self::Any)
98        } else {
99            None
100        }
101    }
102
103    /// Get the SQL type string for this type
104    #[must_use]
105    pub const fn to_sql_type(&self) -> &'static str {
106        match self {
107            Self::Integer => "INTEGER",
108            Self::Text => "TEXT",
109            Self::Blob => "BLOB",
110            Self::Real => "REAL",
111            Self::Numeric => "NUMERIC",
112            Self::Any => "ANY",
113        }
114    }
115
116    /// Returns this type's `SQLite` affinity.
117    #[must_use]
118    pub const fn affinity(&self) -> SQLiteAffinity {
119        match self {
120            Self::Integer => SQLiteAffinity::Integer,
121            Self::Text => SQLiteAffinity::Text,
122            Self::Blob => SQLiteAffinity::Blob,
123            Self::Real => SQLiteAffinity::Real,
124            Self::Numeric => SQLiteAffinity::Numeric,
125            Self::Any => SQLiteAffinity::Any,
126        }
127    }
128
129    /// Whether this type is allowed for STRICT tables.
130    ///
131    /// `SQLite` STRICT supports: INTEGER, REAL, TEXT, BLOB, ANY.
132    #[must_use]
133    pub const fn is_strict_allowed(&self) -> bool {
134        matches!(
135            self,
136            Self::Integer | Self::Real | Self::Text | Self::Blob | Self::Any
137        )
138    }
139
140    /// Check if a flag is valid for this column type
141    ///
142    /// # Valid Flags per Type
143    ///
144    /// - `INTEGER`: `primary`, `primary_key`, `unique`, `autoincrement`, `enum`
145    /// - `TEXT`: `primary`, `primary_key`, `unique`, `json`, `enum`
146    /// - `BLOB`: `primary`, `primary_key`, `unique`, `json`
147    /// - `REAL`: `primary`, `primary_key`, `unique`
148    /// - `NUMERIC`: `primary`, `primary_key`, `unique`
149    /// - `ANY`: `primary`, `primary_key`, `unique`
150    #[must_use]
151    pub fn is_valid_flag(&self, flag: &str) -> bool {
152        matches!(flag, "primary" | "primary_key" | "unique")
153            || matches!(
154                (self, flag),
155                (Self::Integer, "autoincrement")
156                    | (Self::Text | Self::Blob, "json")
157                    | (Self::Text | Self::Integer, "enum")
158            )
159    }
160}
161
162impl core::fmt::Display for SQLiteType {
163    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164        f.write_str(self.to_sql_type())
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn test_from_attribute_name() {
174        assert_eq!(
175            SQLiteType::from_attribute_name("integer"),
176            Some(SQLiteType::Integer)
177        );
178        assert_eq!(
179            SQLiteType::from_attribute_name("INTEGER"),
180            Some(SQLiteType::Integer)
181        );
182        assert_eq!(
183            SQLiteType::from_attribute_name("text"),
184            Some(SQLiteType::Text)
185        );
186        assert_eq!(
187            SQLiteType::from_attribute_name("blob"),
188            Some(SQLiteType::Blob)
189        );
190        assert_eq!(
191            SQLiteType::from_attribute_name("boolean"),
192            Some(SQLiteType::Integer)
193        );
194        assert_eq!(SQLiteType::from_attribute_name("unknown"), None);
195    }
196
197    #[test]
198    fn test_to_sql_type() {
199        assert_eq!(SQLiteType::Integer.to_sql_type(), "INTEGER");
200        assert_eq!(SQLiteType::Text.to_sql_type(), "TEXT");
201        assert_eq!(SQLiteType::Blob.to_sql_type(), "BLOB");
202        assert_eq!(SQLiteType::Real.to_sql_type(), "REAL");
203        assert_eq!(SQLiteType::Numeric.to_sql_type(), "NUMERIC");
204        assert_eq!(SQLiteType::Any.to_sql_type(), "ANY");
205    }
206
207    #[test]
208    fn test_affinity_mapping() {
209        assert_eq!(SQLiteType::Integer.affinity(), SQLiteAffinity::Integer);
210        assert_eq!(SQLiteType::Text.affinity(), SQLiteAffinity::Text);
211        assert_eq!(SQLiteType::Blob.affinity(), SQLiteAffinity::Blob);
212        assert_eq!(SQLiteType::Real.affinity(), SQLiteAffinity::Real);
213        assert_eq!(SQLiteType::Numeric.affinity(), SQLiteAffinity::Numeric);
214        assert_eq!(SQLiteType::Any.affinity(), SQLiteAffinity::Any);
215    }
216
217    #[test]
218    fn test_strict_allowed_types() {
219        assert!(SQLiteType::Integer.is_strict_allowed());
220        assert!(SQLiteType::Text.is_strict_allowed());
221        assert!(SQLiteType::Blob.is_strict_allowed());
222        assert!(SQLiteType::Real.is_strict_allowed());
223        assert!(SQLiteType::Any.is_strict_allowed());
224        assert!(!SQLiteType::Numeric.is_strict_allowed());
225    }
226
227    #[test]
228    fn test_is_valid_flag() {
229        // Autoincrement only valid for INTEGER
230        assert!(SQLiteType::Integer.is_valid_flag("autoincrement"));
231        assert!(!SQLiteType::Text.is_valid_flag("autoincrement"));
232        assert!(!SQLiteType::Blob.is_valid_flag("autoincrement"));
233
234        // JSON valid for TEXT and BLOB
235        assert!(SQLiteType::Text.is_valid_flag("json"));
236        assert!(SQLiteType::Blob.is_valid_flag("json"));
237        assert!(!SQLiteType::Integer.is_valid_flag("json"));
238
239        // Enum valid for TEXT and INTEGER
240        assert!(SQLiteType::Text.is_valid_flag("enum"));
241        assert!(SQLiteType::Integer.is_valid_flag("enum"));
242        assert!(!SQLiteType::Blob.is_valid_flag("enum"));
243
244        // Primary/unique valid for all
245        assert!(SQLiteType::Integer.is_valid_flag("primary"));
246        assert!(SQLiteType::Text.is_valid_flag("unique"));
247        assert!(SQLiteType::Blob.is_valid_flag("primary_key"));
248    }
249}