1use std::borrow::Cow;
22
23use sqlx::sqlite::{SqliteArguments, SqlitePool, SqliteRow};
24use sqlx::{Arguments, Row};
25
26#[cfg(feature = "postgres")]
27use sqlx::postgres::{PgArguments, PgPool, PgRow};
28
29use crate::error::{CoreError, Result};
30use crate::resolver::{GameId, ModId};
31
32#[derive(Debug, Clone)]
38pub enum Val {
39 NullText,
40 NullI64,
41 Bool(bool),
42 I64(i64),
43 Text(String),
44}
45
46impl From<&str> for Val {
47 fn from(s: &str) -> Self {
48 Val::Text(s.to_string())
49 }
50}
51impl From<String> for Val {
52 fn from(s: String) -> Self {
53 Val::Text(s)
54 }
55}
56impl From<&String> for Val {
57 fn from(s: &String) -> Self {
58 Val::Text(s.clone())
59 }
60}
61impl From<i64> for Val {
62 fn from(v: i64) -> Self {
63 Val::I64(v)
64 }
65}
66impl From<bool> for Val {
67 fn from(v: bool) -> Self {
68 Val::Bool(v)
69 }
70}
71impl From<&GameId> for Val {
72 fn from(v: &GameId) -> Self {
73 Val::Text(v.as_str().to_string())
74 }
75}
76impl From<&ModId> for Val {
77 fn from(v: &ModId) -> Self {
78 Val::Text(v.as_str().to_string())
79 }
80}
81impl From<Option<String>> for Val {
82 fn from(v: Option<String>) -> Self {
83 v.map_or(Val::NullText, Val::Text)
84 }
85}
86impl From<Option<&str>> for Val {
87 fn from(v: Option<&str>) -> Self {
88 v.map_or(Val::NullText, |s| Val::Text(s.to_string()))
89 }
90}
91impl From<Option<i64>> for Val {
92 fn from(v: Option<i64>) -> Self {
93 v.map_or(Val::NullI64, Val::I64)
94 }
95}
96
97macro_rules! vals {
105 () => { [] };
106 ($($x:expr),+ $(,)?) => { [ $( $crate::db::backend::Val::from($x) ),+ ] };
107}
108pub(crate) use vals;
109
110pub trait DbRow {
116 fn i64(&self, idx: usize) -> Result<i64>;
117 fn opt_i64(&self, idx: usize) -> Result<Option<i64>>;
118 fn string(&self, idx: usize) -> Result<String>;
119 fn opt_string(&self, idx: usize) -> Result<Option<String>>;
120 fn bool(&self, idx: usize) -> Result<bool>;
121}
122
123impl DbRow for SqliteRow {
124 fn i64(&self, idx: usize) -> Result<i64> {
125 Ok(self.try_get(idx)?)
126 }
127 fn opt_i64(&self, idx: usize) -> Result<Option<i64>> {
128 Ok(self.try_get(idx)?)
129 }
130 fn string(&self, idx: usize) -> Result<String> {
131 Ok(self.try_get(idx)?)
132 }
133 fn opt_string(&self, idx: usize) -> Result<Option<String>> {
134 Ok(self.try_get(idx)?)
135 }
136 fn bool(&self, idx: usize) -> Result<bool> {
137 Ok(self.try_get(idx)?)
138 }
139}
140
141#[cfg(feature = "postgres")]
142impl DbRow for PgRow {
143 fn i64(&self, idx: usize) -> Result<i64> {
144 Ok(self.try_get(idx)?)
145 }
146 fn opt_i64(&self, idx: usize) -> Result<Option<i64>> {
147 Ok(self.try_get(idx)?)
148 }
149 fn string(&self, idx: usize) -> Result<String> {
150 Ok(self.try_get(idx)?)
151 }
152 fn opt_string(&self, idx: usize) -> Result<Option<String>> {
153 Ok(self.try_get(idx)?)
154 }
155 fn bool(&self, idx: usize) -> Result<bool> {
156 Ok(self.try_get(idx)?)
157 }
158}
159
160#[derive(Clone, Copy)]
162enum Dialect {
163 Sqlite,
164 #[cfg(feature = "postgres")]
165 Postgres,
166}
167
168fn bind_err(e: impl std::fmt::Display) -> CoreError {
169 CoreError::Other(format!("failed to bind SQL parameter: {e}").into())
170}
171
172fn sqlite_args(vals: &[Val]) -> Result<SqliteArguments<'static>> {
173 let mut args = SqliteArguments::default();
174 for v in vals {
175 match v {
176 Val::NullText => args.add(None::<String>),
177 Val::NullI64 => args.add(None::<i64>),
178 Val::Bool(b) => args.add(*b),
179 Val::I64(i) => args.add(*i),
180 Val::Text(s) => args.add(s.clone()),
181 }
182 .map_err(bind_err)?;
183 }
184 Ok(args)
185}
186
187#[cfg(feature = "postgres")]
188fn pg_args(vals: &[Val]) -> Result<PgArguments> {
189 let mut args = PgArguments::default();
190 for v in vals {
191 match v {
192 Val::NullText => args.add(None::<String>),
193 Val::NullI64 => args.add(None::<i64>),
194 Val::Bool(b) => args.add(*b),
195 Val::I64(i) => args.add(*i),
196 Val::Text(s) => args.add(s.clone()),
197 }
198 .map_err(bind_err)?;
199 }
200 Ok(args)
201}
202
203#[cfg(feature = "postgres")]
209fn rewrite_placeholders(sql: &str) -> String {
210 let mut out = String::with_capacity(sql.len() + 8);
211 let mut n = 0u32;
212 let mut in_str = false;
213 for c in sql.chars() {
214 match c {
215 '\'' => {
216 in_str = !in_str;
217 out.push(c);
218 }
219 '?' if !in_str => {
220 n += 1;
221 out.push('$');
222 out.push_str(&n.to_string());
223 }
224 _ => out.push(c),
225 }
226 }
227 out
228}
229
230fn prepare_sql(dialect: Dialect, sql: &str) -> Cow<'_, str> {
231 match dialect {
232 Dialect::Sqlite => {
233 if sql.contains("{NOW}") {
234 Cow::Owned(sql.replace("{NOW}", "datetime('now')"))
235 } else {
236 Cow::Borrowed(sql)
237 }
238 }
239 #[cfg(feature = "postgres")]
240 Dialect::Postgres => {
241 let sql = sql.replace("{NOW}", "to_char(now(), 'YYYY-MM-DD HH24:MI:SS')");
242 Cow::Owned(rewrite_placeholders(&sql))
243 }
244 }
245}
246
247#[derive(Debug, Clone)]
251pub enum Db {
252 Sqlite(SqlitePool),
253 #[cfg(feature = "postgres")]
254 Postgres(PgPool),
255}
256
257impl Db {
258 fn dialect(&self) -> Dialect {
259 match self {
260 Db::Sqlite(_) => Dialect::Sqlite,
261 #[cfg(feature = "postgres")]
262 Db::Postgres(_) => Dialect::Postgres,
263 }
264 }
265
266 pub async fn execute(&self, sql: &str, vals: &[Val]) -> Result<u64> {
268 let sql = prepare_sql(self.dialect(), sql);
269 match self {
270 Db::Sqlite(pool) => {
271 let args = sqlite_args(vals)?;
272 Ok(sqlx::query_with(&sql, args)
273 .execute(pool)
274 .await?
275 .rows_affected())
276 }
277 #[cfg(feature = "postgres")]
278 Db::Postgres(pool) => {
279 let args = pg_args(vals)?;
280 Ok(sqlx::query_with(&sql, args)
281 .execute(pool)
282 .await?
283 .rows_affected())
284 }
285 }
286 }
287
288 pub async fn fetch_all<T>(
290 &self,
291 sql: &str,
292 vals: &[Val],
293 map: impl Fn(&dyn DbRow) -> Result<T>,
294 ) -> Result<Vec<T>> {
295 let sql = prepare_sql(self.dialect(), sql);
296 match self {
297 Db::Sqlite(pool) => {
298 let args = sqlite_args(vals)?;
299 let rows = sqlx::query_with(&sql, args).fetch_all(pool).await?;
300 rows.iter().map(|r| map(r as &dyn DbRow)).collect()
301 }
302 #[cfg(feature = "postgres")]
303 Db::Postgres(pool) => {
304 let args = pg_args(vals)?;
305 let rows = sqlx::query_with(&sql, args).fetch_all(pool).await?;
306 rows.iter().map(|r| map(r as &dyn DbRow)).collect()
307 }
308 }
309 }
310
311 pub async fn fetch_optional<T>(
313 &self,
314 sql: &str,
315 vals: &[Val],
316 map: impl Fn(&dyn DbRow) -> Result<T>,
317 ) -> Result<Option<T>> {
318 let sql = prepare_sql(self.dialect(), sql);
319 match self {
320 Db::Sqlite(pool) => {
321 let args = sqlite_args(vals)?;
322 match sqlx::query_with(&sql, args).fetch_optional(pool).await? {
323 Some(r) => Ok(Some(map(&r as &dyn DbRow)?)),
324 None => Ok(None),
325 }
326 }
327 #[cfg(feature = "postgres")]
328 Db::Postgres(pool) => {
329 let args = pg_args(vals)?;
330 match sqlx::query_with(&sql, args).fetch_optional(pool).await? {
331 Some(r) => Ok(Some(map(&r as &dyn DbRow)?)),
332 None => Ok(None),
333 }
334 }
335 }
336 }
337
338 pub async fn fetch_one<T>(
340 &self,
341 sql: &str,
342 vals: &[Val],
343 map: impl Fn(&dyn DbRow) -> Result<T>,
344 ) -> Result<T> {
345 let sql = prepare_sql(self.dialect(), sql);
346 match self {
347 Db::Sqlite(pool) => {
348 let args = sqlite_args(vals)?;
349 let r = sqlx::query_with(&sql, args).fetch_one(pool).await?;
350 map(&r as &dyn DbRow)
351 }
352 #[cfg(feature = "postgres")]
353 Db::Postgres(pool) => {
354 let args = pg_args(vals)?;
355 let r = sqlx::query_with(&sql, args).fetch_one(pool).await?;
356 map(&r as &dyn DbRow)
357 }
358 }
359 }
360
361 pub async fn begin(&self) -> Result<DbTx<'_>> {
364 Ok(match self {
365 Db::Sqlite(pool) => DbTx::Sqlite(pool.begin().await?),
366 #[cfg(feature = "postgres")]
367 Db::Postgres(pool) => DbTx::Postgres(pool.begin().await?),
368 })
369 }
370}
371
372pub enum DbTx<'c> {
374 Sqlite(sqlx::Transaction<'c, sqlx::Sqlite>),
375 #[cfg(feature = "postgres")]
376 Postgres(sqlx::Transaction<'c, sqlx::Postgres>),
377}
378
379impl DbTx<'_> {
380 fn dialect(&self) -> Dialect {
381 match self {
382 DbTx::Sqlite(_) => Dialect::Sqlite,
383 #[cfg(feature = "postgres")]
384 DbTx::Postgres(_) => Dialect::Postgres,
385 }
386 }
387
388 pub async fn execute(&mut self, sql: &str, vals: &[Val]) -> Result<u64> {
389 let sql = prepare_sql(self.dialect(), sql);
390 match self {
391 DbTx::Sqlite(tx) => {
392 let args = sqlite_args(vals)?;
393 Ok(sqlx::query_with(&sql, args)
394 .execute(&mut **tx)
395 .await?
396 .rows_affected())
397 }
398 #[cfg(feature = "postgres")]
399 DbTx::Postgres(tx) => {
400 let args = pg_args(vals)?;
401 Ok(sqlx::query_with(&sql, args)
402 .execute(&mut **tx)
403 .await?
404 .rows_affected())
405 }
406 }
407 }
408
409 pub async fn commit(self) -> Result<()> {
410 match self {
411 DbTx::Sqlite(tx) => tx.commit().await?,
412 #[cfg(feature = "postgres")]
413 DbTx::Postgres(tx) => tx.commit().await?,
414 }
415 Ok(())
416 }
417}