oxide_sql_core/builder/
value.rs1#[derive(Debug, Clone, PartialEq)]
9pub enum SqlValue {
10 Null,
12 Bool(bool),
14 Int(i64),
16 Float(f64),
18 Text(String),
20 Blob(Vec<u8>),
22}
23
24impl SqlValue {
25 #[must_use]
29 pub fn to_sql_inline(&self) -> String {
30 match self {
31 Self::Null => String::from("NULL"),
32 Self::Bool(b) => {
33 if *b {
34 String::from("TRUE")
35 } else {
36 String::from("FALSE")
37 }
38 }
39 Self::Int(n) => format!("{n}"),
40 Self::Float(f) => format!("{f}"),
41 Self::Text(s) => {
42 let escaped = s.replace('\'', "''");
44 format!("'{escaped}'")
45 }
46 Self::Blob(b) => {
47 let hex: String = b.iter().map(|byte| format!("{byte:02X}")).collect();
48 format!("X'{hex}'")
49 }
50 }
51 }
52
53 #[must_use]
55 pub const fn placeholder() -> &'static str {
56 "?"
57 }
58}
59
60pub trait ToSqlValue {
62 fn to_sql_value(self) -> SqlValue;
64}
65
66impl ToSqlValue for SqlValue {
67 fn to_sql_value(self) -> SqlValue {
68 self
69 }
70}
71
72impl ToSqlValue for bool {
73 fn to_sql_value(self) -> SqlValue {
74 SqlValue::Bool(self)
75 }
76}
77
78impl ToSqlValue for i64 {
79 fn to_sql_value(self) -> SqlValue {
80 SqlValue::Int(self)
81 }
82}
83
84impl ToSqlValue for i32 {
85 fn to_sql_value(self) -> SqlValue {
86 SqlValue::Int(i64::from(self))
87 }
88}
89
90impl ToSqlValue for i16 {
91 fn to_sql_value(self) -> SqlValue {
92 SqlValue::Int(i64::from(self))
93 }
94}
95
96impl ToSqlValue for i8 {
97 fn to_sql_value(self) -> SqlValue {
98 SqlValue::Int(i64::from(self))
99 }
100}
101
102impl ToSqlValue for u32 {
103 fn to_sql_value(self) -> SqlValue {
104 SqlValue::Int(i64::from(self))
105 }
106}
107
108impl ToSqlValue for u16 {
109 fn to_sql_value(self) -> SqlValue {
110 SqlValue::Int(i64::from(self))
111 }
112}
113
114impl ToSqlValue for u8 {
115 fn to_sql_value(self) -> SqlValue {
116 SqlValue::Int(i64::from(self))
117 }
118}
119
120impl ToSqlValue for f64 {
121 fn to_sql_value(self) -> SqlValue {
122 SqlValue::Float(self)
123 }
124}
125
126impl ToSqlValue for f32 {
127 fn to_sql_value(self) -> SqlValue {
128 SqlValue::Float(f64::from(self))
129 }
130}
131
132impl ToSqlValue for String {
133 fn to_sql_value(self) -> SqlValue {
134 SqlValue::Text(self)
135 }
136}
137
138impl ToSqlValue for &str {
139 fn to_sql_value(self) -> SqlValue {
140 SqlValue::Text(String::from(self))
141 }
142}
143
144impl<T: ToSqlValue> ToSqlValue for Option<T> {
145 fn to_sql_value(self) -> SqlValue {
146 match self {
147 Some(v) => v.to_sql_value(),
148 None => SqlValue::Null,
149 }
150 }
151}
152
153impl ToSqlValue for Vec<u8> {
154 fn to_sql_value(self) -> SqlValue {
155 SqlValue::Blob(self)
156 }
157}
158
159impl ToSqlValue for &[u8] {
160 fn to_sql_value(self) -> SqlValue {
161 SqlValue::Blob(self.to_vec())
162 }
163}
164
165impl From<bool> for SqlValue {
168 fn from(value: bool) -> Self {
169 Self::Bool(value)
170 }
171}
172
173impl From<i64> for SqlValue {
174 fn from(value: i64) -> Self {
175 Self::Int(value)
176 }
177}
178
179impl From<i32> for SqlValue {
180 fn from(value: i32) -> Self {
181 Self::Int(i64::from(value))
182 }
183}
184
185impl From<i16> for SqlValue {
186 fn from(value: i16) -> Self {
187 Self::Int(i64::from(value))
188 }
189}
190
191impl From<i8> for SqlValue {
192 fn from(value: i8) -> Self {
193 Self::Int(i64::from(value))
194 }
195}
196
197impl From<u32> for SqlValue {
198 fn from(value: u32) -> Self {
199 Self::Int(i64::from(value))
200 }
201}
202
203impl From<u16> for SqlValue {
204 fn from(value: u16) -> Self {
205 Self::Int(i64::from(value))
206 }
207}
208
209impl From<u8> for SqlValue {
210 fn from(value: u8) -> Self {
211 Self::Int(i64::from(value))
212 }
213}
214
215impl From<f64> for SqlValue {
216 fn from(value: f64) -> Self {
217 Self::Float(value)
218 }
219}
220
221impl From<f32> for SqlValue {
222 fn from(value: f32) -> Self {
223 Self::Float(f64::from(value))
224 }
225}
226
227impl From<String> for SqlValue {
228 fn from(value: String) -> Self {
229 Self::Text(value)
230 }
231}
232
233impl From<&str> for SqlValue {
234 fn from(value: &str) -> Self {
235 Self::Text(String::from(value))
236 }
237}
238
239impl<T: Into<SqlValue>> From<Option<T>> for SqlValue {
240 fn from(value: Option<T>) -> Self {
241 match value {
242 Some(v) => v.into(),
243 None => Self::Null,
244 }
245 }
246}
247
248impl From<Vec<u8>> for SqlValue {
249 fn from(value: Vec<u8>) -> Self {
250 Self::Blob(value)
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_sql_value_inline_null() {
260 assert_eq!(SqlValue::Null.to_sql_inline(), "NULL");
261 }
262
263 #[test]
264 fn test_sql_value_inline_bool() {
265 assert_eq!(SqlValue::Bool(true).to_sql_inline(), "TRUE");
266 assert_eq!(SqlValue::Bool(false).to_sql_inline(), "FALSE");
267 }
268
269 #[test]
270 fn test_sql_value_inline_int() {
271 assert_eq!(SqlValue::Int(42).to_sql_inline(), "42");
272 assert_eq!(SqlValue::Int(-100).to_sql_inline(), "-100");
273 }
274
275 #[test]
276 fn test_sql_value_inline_text() {
277 assert_eq!(
278 SqlValue::Text(String::from("hello")).to_sql_inline(),
279 "'hello'"
280 );
281 }
282
283 #[test]
284 fn test_sql_value_inline_text_escaping() {
285 assert_eq!(
287 SqlValue::Text(String::from("it's")).to_sql_inline(),
288 "'it''s'"
289 );
290 assert_eq!(
291 SqlValue::Text(String::from("O'Brien")).to_sql_inline(),
292 "'O''Brien'"
293 );
294 }
295
296 #[test]
297 fn test_sql_injection_prevention() {
298 let malicious = "'; DROP TABLE users; --";
300 let value = SqlValue::Text(String::from(malicious));
301 let escaped = value.to_sql_inline();
302 assert_eq!(escaped, "'''; DROP TABLE users; --'");
304 }
305
306 #[test]
307 fn test_sql_value_inline_blob() {
308 assert_eq!(
309 SqlValue::Blob(vec![0x48, 0x45, 0x4C, 0x4C, 0x4F]).to_sql_inline(),
310 "X'48454C4C4F'"
311 );
312 }
313
314 #[test]
315 fn test_to_sql_value_conversions() {
316 assert_eq!(true.to_sql_value(), SqlValue::Bool(true));
317 assert_eq!(42_i32.to_sql_value(), SqlValue::Int(42));
318 assert_eq!(2.5_f64.to_sql_value(), SqlValue::Float(2.5));
319 assert_eq!(
320 "hello".to_sql_value(),
321 SqlValue::Text(String::from("hello"))
322 );
323 assert_eq!(None::<i32>.to_sql_value(), SqlValue::Null);
324 assert_eq!(Some(42_i32).to_sql_value(), SqlValue::Int(42));
325 }
326}