sql_middleware/
types.rs

1use chrono::NaiveDateTime;
2use clap::ValueEnum;
3use serde_json::Value as JsonValue;
4
5use crate::error::SqlMiddlewareDbError;
6
7/// Values that can be stored in a database row or used as query parameters.
8///
9/// Reuse the same enum across backends so helper functions do not need to branch on driver
10/// types:
11/// ```rust
12/// use sql_middleware::prelude::*;
13///
14/// let params = vec![
15///     RowValues::Int(1),
16///     RowValues::Text("alice".into()),
17///     RowValues::Bool(true),
18/// ];
19/// # let _ = params;
20/// ```
21#[derive(Debug, Clone, PartialEq)]
22pub enum RowValues {
23    /// Integer value (64-bit)
24    Int(i64),
25    /// Floating point value (64-bit)
26    Float(f64),
27    /// Text/string value
28    Text(String),
29    /// Boolean value
30    Bool(bool),
31    /// Timestamp value
32    Timestamp(NaiveDateTime),
33    /// NULL value
34    Null,
35    /// JSON value
36    JSON(JsonValue),
37    /// Binary data
38    Blob(Vec<u8>),
39}
40
41impl RowValues {
42    /// Check if this value is NULL
43    #[must_use]
44    pub fn is_null(&self) -> bool {
45        matches!(self, Self::Null)
46    }
47
48    #[must_use]
49    pub fn as_int(&self) -> Option<&i64> {
50        if let RowValues::Int(value) = self {
51            Some(value)
52        } else {
53            None
54        }
55    }
56
57    #[must_use]
58    pub fn as_text(&self) -> Option<&str> {
59        if let RowValues::Text(value) = self {
60            Some(value)
61        } else {
62            None
63        }
64    }
65
66    #[must_use]
67    pub fn as_bool(&self) -> Option<&bool> {
68        if let RowValues::Bool(value) = self {
69            return Some(value);
70        } else if let Some(i) = self.as_int() {
71            if *i == 1 {
72                return Some(&true);
73            } else if *i == 0 {
74                return Some(&false);
75            }
76        }
77        None
78    }
79
80    #[must_use]
81    pub fn as_timestamp(&self) -> Option<chrono::NaiveDateTime> {
82        if let RowValues::Timestamp(value) = self {
83            return Some(*value);
84        } else if let Some(s) = self.as_text() {
85            // Try "YYYY-MM-DD HH:MM:SS"
86            if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") {
87                return Some(dt);
88            }
89            // Try "YYYY-MM-DD HH:MM:SS.SSS"
90            if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S.%3f") {
91                return Some(dt);
92            }
93        }
94        None
95    }
96
97    #[must_use]
98    pub fn as_float(&self) -> Option<f64> {
99        if let RowValues::Float(value) = self {
100            Some(*value)
101        } else {
102            None
103        }
104    }
105
106    #[must_use]
107    pub fn as_blob(&self) -> Option<&[u8]> {
108        if let RowValues::Blob(bytes) = self {
109            Some(bytes)
110        } else {
111            None
112        }
113    }
114}
115
116/// The database type supported by this middleware
117#[derive(Debug, Clone, PartialEq, Eq, Hash, ValueEnum)]
118pub enum DatabaseType {
119    /// `PostgreSQL` database
120    #[cfg(feature = "postgres")]
121    Postgres,
122    /// `SQLite` database
123    #[cfg(feature = "sqlite")]
124    Sqlite,
125    /// SQL Server database
126    #[cfg(feature = "mssql")]
127    Mssql,
128    /// `LibSQL` database
129    #[cfg(feature = "libsql")]
130    Libsql,
131    /// Turso (SQLite-compatible, in-process) database
132    #[cfg(feature = "turso")]
133    Turso,
134}
135
136/// The conversion "mode".
137#[derive(Debug, Clone, Copy, PartialEq)]
138pub enum ConversionMode {
139    /// When the converted parameters will be used in a query (SELECT)
140    Query,
141    /// When the converted parameters will be used for statement execution (INSERT/UPDATE/etc.)
142    Execute,
143}
144
145/// Convert a slice of `RowValues` into database-specific parameters.
146/// This trait provides a unified interface for converting generic `RowValues`
147/// to database-specific parameter types.
148pub trait ParamConverter<'a> {
149    type Converted;
150
151    /// Convert a slice of `RowValues` into the backend's parameter type.
152    ///
153    /// # Errors
154    ///
155    /// Returns `SqlMiddlewareDbError` if the conversion fails for any parameter.
156    fn convert_sql_params(
157        params: &'a [RowValues],
158        mode: ConversionMode,
159    ) -> Result<Self::Converted, SqlMiddlewareDbError>;
160
161    /// Check if this converter supports the given mode
162    ///
163    /// # Arguments
164    /// * `mode` - The conversion mode to check
165    ///
166    /// # Returns
167    /// * `bool` - Whether this converter supports the mode
168    #[must_use]
169    fn supports_mode(_mode: ConversionMode) -> bool {
170        true // By default, support both modes
171    }
172}