1use std::sync::Arc;
4
5use crate::BoxFuture;
6use crate::js::{self, Value};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum Dialect {
11 Sqlite,
13 Postgres,
15 Mysql,
17}
18
19#[derive(Clone, Debug, PartialEq)]
21pub enum Param {
22 Null,
24 Int(i64),
26 Float(f64),
28 Text(String),
30}
31
32impl From<i64> for Param {
33 fn from(n: i64) -> Param {
34 Param::Int(n)
35 }
36}
37impl From<i32> for Param {
38 fn from(n: i32) -> Param {
39 Param::Int(i64::from(n))
40 }
41}
42impl From<usize> for Param {
43 fn from(n: usize) -> Param {
44 Param::Int(n as i64)
45 }
46}
47impl From<f64> for Param {
48 fn from(n: f64) -> Param {
50 if js::is_integer(n) && n.abs() < 9.007_199_254_740_992e15 { Param::Int(n as i64) } else { Param::Float(n) }
51 }
52}
53impl From<&str> for Param {
54 fn from(s: &str) -> Param {
55 Param::Text(s.to_string())
56 }
57}
58impl From<String> for Param {
59 fn from(s: String) -> Param {
60 Param::Text(s)
61 }
62}
63impl From<&String> for Param {
64 fn from(s: &String) -> Param {
65 Param::Text(s.clone())
66 }
67}
68impl<T: Into<Param>> From<Option<T>> for Param {
69 fn from(v: Option<T>) -> Param {
70 v.map_or(Param::Null, Into::into)
71 }
72}
73
74#[macro_export]
76macro_rules! params {
77 () => { Vec::<$crate::store::Param>::new() };
78 ($($v:expr),+ $(,)?) => { vec![$($crate::store::Param::from($v)),+] };
79}
80
81#[derive(Clone, Debug, PartialEq)]
83pub enum Cell {
84 Null,
86 Int(i64),
88 Float(f64),
90 Text(String),
92}
93
94impl Cell {
95 pub fn to_value(&self) -> Value {
97 match self {
98 Cell::Null => Value::Null,
99 Cell::Int(n) => Value::Number(*n as f64),
100 Cell::Float(f) => Value::Number(*f),
101 Cell::Text(s) => Value::String(s.clone()),
102 }
103 }
104}
105
106#[derive(Clone, Debug, Default, PartialEq)]
108pub struct Row(pub Vec<(String, Cell)>);
109
110static NULL: Cell = Cell::Null;
111
112impl Row {
113 pub fn get(&self, name: &str) -> &Cell {
115 self.0.iter().find(|(n, _)| n == name).map_or(&NULL, |(_, c)| c)
116 }
117
118 pub fn is_null(&self, name: &str) -> bool {
120 matches!(self.get(name), Cell::Null)
121 }
122
123 pub fn num(&self, name: &str) -> f64 {
125 let n = match self.get(name) {
126 Cell::Null => 0.0,
127 Cell::Int(n) => *n as f64,
128 Cell::Float(f) => *f,
129 Cell::Text(t) => js::text_number(t),
130 };
131 if n.is_finite() { n } else { 0.0 }
132 }
133
134 pub fn int(&self, name: &str) -> i64 {
136 js::to_i64(self.num(name))
137 }
138
139 pub fn opt_int(&self, name: &str) -> Option<i64> {
141 if self.is_null(name) { None } else { Some(self.int(name)) }
142 }
143
144 pub fn text(&self, name: &str) -> String {
146 match self.get(name) {
147 Cell::Null => "null".into(),
148 Cell::Int(n) => n.to_string(),
149 Cell::Float(f) => js::format_number(*f),
150 Cell::Text(t) => t.clone(),
151 }
152 }
153
154 pub fn text_or(&self, name: &str, fallback: &str) -> String {
156 if self.is_null(name) { fallback.to_string() } else { self.text(name) }
157 }
158
159 pub fn to_value(&self) -> Value {
161 let mut o = js::Object::new();
162 for (name, cell) in &self.0 {
163 o.set(name.clone(), cell.to_value());
164 }
165 Value::Object(o)
166 }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq)]
171pub struct DbError(pub String);
172
173impl std::fmt::Display for DbError {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 f.write_str(&self.0)
176 }
177}
178
179impl std::error::Error for DbError {}
180
181#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub enum Hold {
184 Transaction,
186 Exclusive,
190}
191
192pub trait Db: Send + Sync {
196 fn dialect(&self) -> Dialect;
198
199 fn all<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<Vec<Row>, DbError>>;
201
202 fn run<'a>(&'a self, sql: &'a str, params: Vec<Param>) -> BoxFuture<'a, Result<u64, DbError>>;
204
205 fn hold(&self, kind: Hold) -> BoxFuture<'_, Result<Arc<dyn Db>, DbError>>;
209
210 fn finish(&self, _ok: bool) -> BoxFuture<'_, Result<(), DbError>> {
213 Box::pin(async { Ok(()) })
214 }
215
216 fn metered(&self) -> bool {
219 false
220 }
221
222 fn close(&self) -> BoxFuture<'_, ()> {
224 Box::pin(async {})
225 }
226
227 fn prefix(&self) -> Option<&str> {
230 None
231 }
232}
233
234pub fn fill_placeholders(
239 sql: &str,
240 params: &[Param],
241 mysql: bool,
242 fill: impl Fn(&Param) -> String,
243) -> Result<String, DbError> {
244 let mut out = String::with_capacity(sql.len() + params.len() * 8);
245 let mut n = 0;
246 let mut quote: Option<char> = None;
247 for ch in sql.chars() {
248 if let Some(q) = quote {
249 if ch == q {
250 quote = None;
251 out.push(if mysql && ch == '"' { '`' } else { ch });
252 } else if mysql && q == '\'' && ch == '\\' {
253 out.push_str("\\\\");
254 } else if mysql && q == '"' && ch == '`' {
255 out.push_str("``");
256 } else {
257 out.push(ch);
258 }
259 } else if ch == '\'' || ch == '"' || (mysql && ch == '`') {
260 quote = Some(ch);
261 out.push(if mysql && ch == '"' { '`' } else { ch });
262 } else if ch == '?' {
263 let p = params
264 .get(n)
265 .ok_or_else(|| DbError("Runlight: a statement has more placeholders than values".into()))?;
266 out.push_str(&fill(p));
267 n += 1;
268 } else {
269 out.push(ch);
270 }
271 }
272 if n != params.len() {
273 return Err(DbError("Runlight: a statement has more values than placeholders".into()));
274 }
275 Ok(out)
276}
277
278pub fn postgres_literal(p: &Param) -> String {
282 match p {
283 Param::Null => "NULL".into(),
284 Param::Int(n) => format!("'{n}'"),
285 Param::Float(f) => format!("'{}'", js::format_number(*f)),
286 Param::Text(s) => {
287 let mut out = String::with_capacity(s.len() + 3);
288 out.push_str("E'");
289 for c in s.chars() {
290 match c {
291 '\\' => out.push_str("\\\\"),
292 '\'' => out.push_str("''"),
293 _ => out.push(c),
294 }
295 }
296 out.push('\'');
297 out
298 }
299 }
300}
301
302pub fn mysql_literal(p: &Param) -> String {
306 match p {
307 Param::Null => "NULL".into(),
308 Param::Int(n) => n.to_string(),
309 Param::Float(f) => js::format_number(*f),
310 Param::Text(s) => {
311 let mut out = String::with_capacity(s.len() + 2);
312 out.push('\'');
313 for c in s.chars() {
314 match c {
315 '\0' => out.push_str("\\0"),
316 '\x08' => out.push_str("\\b"),
317 '\t' => out.push_str("\\t"),
318 '\x1a' => out.push_str("\\Z"),
319 '\n' => out.push_str("\\n"),
320 '\r' => out.push_str("\\r"),
321 '"' => out.push_str("\\\""),
322 '\'' => out.push_str("''"),
323 '\\' => out.push_str("\\\\"),
324 _ => out.push(c),
325 }
326 }
327 out.push('\'');
328 out
329 }
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn placeholders_are_filled_outside_quotes() {
339 let sql = r#"SELECT "key", 'a?b\c' FROM t WHERE x = ? AND y LIKE ? ESCAPE '\'"#;
340 let params = vec![Param::Int(3), Param::Text("o'k\\".into())];
341 assert_eq!(
342 fill_placeholders(sql, ¶ms, true, mysql_literal).unwrap(),
343 r#"SELECT `key`, 'a?b\\c' FROM t WHERE x = 3 AND y LIKE 'o''k\\' ESCAPE '\\'"#
344 );
345 assert_eq!(
346 fill_placeholders(sql, ¶ms, false, postgres_literal).unwrap(),
347 r#"SELECT "key", 'a?b\c' FROM t WHERE x = '3' AND y LIKE E'o''k\\' ESCAPE '\'"#
348 );
349 assert!(fill_placeholders("?", &[], false, postgres_literal).is_err());
350 }
351}