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}