1impl SqlValue {
2 pub fn is_null(&self) -> bool {
4 matches!(
5 self,
6 SqlValue::NullBool
7 | SqlValue::NullSmallInt
8 | SqlValue::NullInt
9 | SqlValue::NullBigInt
10 | SqlValue::NullFloat
11 | SqlValue::NullDouble
12 | SqlValue::NullDecimal
13 | SqlValue::NullText
14 | SqlValue::NullBytes
15 | SqlValue::NullUuid
16 | SqlValue::NullDateTime
17 | SqlValue::NullDate
18 | SqlValue::NullTime
19 | SqlValue::NullJson
20 )
21 }
22}
23
24#[derive(Debug, Clone, PartialEq)]
27pub enum SqlValue {
28 NullBool,
30 NullSmallInt,
31 NullInt,
32 NullBigInt,
33 NullFloat,
34 NullDouble,
35 NullDecimal,
36 NullText,
37 NullBytes,
38 NullUuid,
39 NullDateTime,
40 NullDate,
41 NullTime,
42 NullJson,
43 Bool(bool),
45 SmallInt(i16),
46 Int(i32),
47 BigInt(i64),
48 Float(f32),
49 Double(f64),
50 Decimal(rust_decimal::Decimal),
51 Text(String),
52 Bytes(Vec<u8>),
53 Uuid(uuid::Uuid),
54 DateTime(chrono::DateTime<chrono::Utc>),
55 Date(chrono::NaiveDate),
56 Time(chrono::NaiveTime),
57 Json(serde_json::Value),
58}
59
60pub trait ToSqlValue {
62 fn to_sql_value(&self) -> SqlValue;
63}
64
65impl From<&str> for SqlValue {
68 fn from(s: &str) -> Self {
69 SqlValue::Text(s.to_string())
70 }
71}
72impl From<String> for SqlValue {
73 fn from(s: String) -> Self {
74 SqlValue::Text(s)
75 }
76}
77impl From<bool> for SqlValue {
78 fn from(v: bool) -> Self {
79 SqlValue::Bool(v)
80 }
81}
82impl From<i16> for SqlValue {
83 fn from(v: i16) -> Self {
84 SqlValue::SmallInt(v)
85 }
86}
87impl From<i32> for SqlValue {
88 fn from(v: i32) -> Self {
89 SqlValue::Int(v)
90 }
91}
92impl From<i64> for SqlValue {
93 fn from(v: i64) -> Self {
94 SqlValue::BigInt(v)
95 }
96}
97impl From<f32> for SqlValue {
98 fn from(v: f32) -> Self {
99 SqlValue::Float(v)
100 }
101}
102impl From<f64> for SqlValue {
103 fn from(v: f64) -> Self {
104 SqlValue::Double(v)
105 }
106}
107impl From<rust_decimal::Decimal> for SqlValue {
108 fn from(v: rust_decimal::Decimal) -> Self {
109 SqlValue::Decimal(v)
110 }
111}
112impl From<uuid::Uuid> for SqlValue {
113 fn from(v: uuid::Uuid) -> Self {
114 SqlValue::Uuid(v)
115 }
116}
117impl From<chrono::DateTime<chrono::Utc>> for SqlValue {
118 fn from(v: chrono::DateTime<chrono::Utc>) -> Self {
119 SqlValue::DateTime(v)
120 }
121}
122impl From<chrono::NaiveDate> for SqlValue {
123 fn from(v: chrono::NaiveDate) -> Self {
124 SqlValue::Date(v)
125 }
126}
127impl From<chrono::NaiveTime> for SqlValue {
128 fn from(v: chrono::NaiveTime) -> Self {
129 SqlValue::Time(v)
130 }
131}
132impl From<Vec<u8>> for SqlValue {
133 fn from(v: Vec<u8>) -> Self {
134 SqlValue::Bytes(v)
135 }
136}
137impl From<serde_json::Value> for SqlValue {
138 fn from(v: serde_json::Value) -> Self {
139 SqlValue::Json(v)
140 }
141}
142
143pub trait Filterable: Sized {}
146
147#[diagnostic::on_unimplemented(
149 message = "`{Self}` cannot be written to the database",
150 note = "add `#[derive(Model)]` (or `#[rango::model]`) to `{Self}`"
151)]
152pub trait ModelValues {
153 fn field_values(&self) -> Vec<(&'static str, SqlValue)>;
155
156 fn pk_value(&self) -> SqlValue;
158
159 fn pk_column() -> &'static str;
161
162 fn set_generated_pk(&mut self, _id: i64) {}
165}
166
167pub fn insert_values<M: ModelValues>(model: &M) -> Vec<(&'static str, SqlValue)> {
170 let pk = model.pk_value();
171 let fields = model.field_values();
172 let mut values = Vec::with_capacity(fields.len() + 1);
173 if !pk.is_null() {
174 values.push((M::pk_column(), pk));
175 }
176 values.extend(fields);
177 values
178}
179
180pub fn quote_ident(ident: &str) -> String {
183 format!("\"{}\"", ident.replace('"', "\"\""))
184}
185
186pub fn quote_ident_mysql(ident: &str) -> String {
188 format!("`{}`", ident.replace('`', "``"))
189}
190
191#[cfg(test)]
192mod quote_ident_tests {
193 use super::{quote_ident, quote_ident_mysql};
194
195 #[test]
196 fn test_quote_ident_mysql_escapes_backticks() {
197 assert_eq!(quote_ident_mysql("user"), "`user`");
198 assert_eq!(
199 quote_ident_mysql("a`; DROP TABLE x; --"),
200 "`a``; DROP TABLE x; --`"
201 );
202 }
203
204 #[test]
205 fn test_quote_ident_escapes_double_quotes() {
206 assert_eq!(quote_ident("name"), "\"name\"");
207 assert_eq!(
208 quote_ident("a\"; DROP TABLE x; --"),
209 "\"a\"\"; DROP TABLE x; --\""
210 );
211 }
212}