1#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
2#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
3#![allow(clippy::multiple_crate_versions)]
4
5pub mod config;
6#[cfg(feature = "postgres-raw")]
7pub mod postgres;
8pub mod profiles;
9#[cfg(feature = "sqlite-rusqlite")]
10pub mod rusqlite;
11#[cfg(feature = "simulator")]
12pub mod simulator;
13#[cfg(feature = "sqlx")]
14pub mod sqlx;
15
16pub mod query;
17
18#[cfg(feature = "schema")]
19pub mod schema;
20
21use std::{num::TryFromIntError, sync::Arc};
22
23use async_trait::async_trait;
24use chrono::NaiveDateTime;
25use query::{
26 DeleteStatement, InsertStatement, SelectQuery, UpdateStatement, UpsertMultiStatement,
27 UpsertStatement,
28};
29use thiserror::Error;
30
31#[derive(Debug, Clone, PartialEq)]
32pub enum DatabaseValue {
33 Null,
34 String(String),
35 StringOpt(Option<String>),
36 Bool(bool),
37 BoolOpt(Option<bool>),
38 Number(i64),
39 NumberOpt(Option<i64>),
40 UNumber(u64),
41 UNumberOpt(Option<u64>),
42 Real(f64),
43 RealOpt(Option<f64>),
44 NowAdd(String),
45 Now,
46 DateTime(NaiveDateTime),
47}
48
49impl DatabaseValue {
50 #[must_use]
51 #[allow(clippy::missing_const_for_fn)]
52 pub fn as_str(&self) -> Option<&str> {
53 match self {
54 Self::String(value) | Self::StringOpt(Some(value)) => Some(value),
55 _ => None,
56 }
57 }
58}
59
60impl<T: Into<Self>> From<Option<T>> for DatabaseValue {
61 fn from(val: Option<T>) -> Self {
62 val.map_or(Self::Null, std::convert::Into::into)
63 }
64}
65
66impl From<bool> for DatabaseValue {
67 fn from(val: bool) -> Self {
68 Self::Bool(val)
69 }
70}
71
72impl From<&str> for DatabaseValue {
73 fn from(val: &str) -> Self {
74 Self::String(val.to_string())
75 }
76}
77
78impl From<&String> for DatabaseValue {
79 fn from(val: &String) -> Self {
80 Self::String(val.to_string())
81 }
82}
83
84impl From<String> for DatabaseValue {
85 fn from(val: String) -> Self {
86 Self::String(val)
87 }
88}
89
90impl From<f32> for DatabaseValue {
91 fn from(val: f32) -> Self {
92 Self::Real(f64::from(val))
93 }
94}
95
96impl From<f64> for DatabaseValue {
97 fn from(val: f64) -> Self {
98 Self::Real(val)
99 }
100}
101
102impl From<i8> for DatabaseValue {
103 fn from(val: i8) -> Self {
104 Self::Number(i64::from(val))
105 }
106}
107
108impl From<i16> for DatabaseValue {
109 fn from(val: i16) -> Self {
110 Self::Number(i64::from(val))
111 }
112}
113
114impl From<i32> for DatabaseValue {
115 fn from(val: i32) -> Self {
116 Self::Number(i64::from(val))
117 }
118}
119
120impl From<i64> for DatabaseValue {
121 fn from(val: i64) -> Self {
122 Self::Number(val)
123 }
124}
125
126impl From<isize> for DatabaseValue {
127 fn from(val: isize) -> Self {
128 Self::Number(val as i64)
129 }
130}
131
132impl From<u8> for DatabaseValue {
133 fn from(val: u8) -> Self {
134 Self::UNumber(u64::from(val))
135 }
136}
137
138impl From<u16> for DatabaseValue {
139 fn from(val: u16) -> Self {
140 Self::UNumber(u64::from(val))
141 }
142}
143
144impl From<u32> for DatabaseValue {
145 fn from(val: u32) -> Self {
146 Self::UNumber(u64::from(val))
147 }
148}
149
150impl From<u64> for DatabaseValue {
151 fn from(val: u64) -> Self {
152 Self::UNumber(val)
153 }
154}
155
156impl From<usize> for DatabaseValue {
157 fn from(val: usize) -> Self {
158 Self::UNumber(val as u64)
159 }
160}
161
162pub trait AsId {
163 fn as_id(&self) -> DatabaseValue;
164}
165
166#[derive(Debug, Error)]
167pub enum TryFromError {
168 #[error("Could not convert to type '{0}'")]
169 CouldNotConvert(String),
170 #[error(transparent)]
171 TryFromInt(#[from] TryFromIntError),
172}
173
174impl TryFrom<DatabaseValue> for u64 {
175 type Error = TryFromError;
176
177 fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
178 match value {
179 DatabaseValue::Number(value) | DatabaseValue::NumberOpt(Some(value)) => {
180 Ok(Self::try_from(value)?)
181 }
182 DatabaseValue::UNumber(value) | DatabaseValue::UNumberOpt(Some(value)) => Ok(value),
183 _ => Err(TryFromError::CouldNotConvert("u64".into())),
184 }
185 }
186}
187
188impl TryFrom<DatabaseValue> for i32 {
189 type Error = TryFromError;
190
191 fn try_from(value: DatabaseValue) -> Result<Self, Self::Error> {
192 match value {
193 DatabaseValue::Number(value) | DatabaseValue::NumberOpt(Some(value)) => {
194 Ok(Self::try_from(value)?)
195 }
196 DatabaseValue::UNumber(value) | DatabaseValue::UNumberOpt(Some(value)) => {
197 Ok(Self::try_from(value)?)
198 }
199 _ => Err(TryFromError::CouldNotConvert("i32".into())),
200 }
201 }
202}
203
204#[derive(Debug, Error)]
205pub enum DatabaseError {
206 #[cfg(feature = "sqlite-rusqlite")]
207 #[error(transparent)]
208 Rusqlite(rusqlite::RusqliteDatabaseError),
209 #[cfg(feature = "mysql-sqlx")]
210 #[error(transparent)]
211 MysqlSqlx(sqlx::mysql::SqlxDatabaseError),
212 #[cfg(feature = "sqlite-sqlx")]
213 #[error(transparent)]
214 SqliteSqlx(sqlx::sqlite::SqlxDatabaseError),
215 #[cfg(feature = "postgres-raw")]
216 #[error(transparent)]
217 Postgres(postgres::postgres::PostgresDatabaseError),
218 #[cfg(feature = "postgres-sqlx")]
219 #[error(transparent)]
220 PostgresSqlx(sqlx::postgres::SqlxDatabaseError),
221 #[error("No row")]
222 NoRow,
223 #[cfg(feature = "schema")]
224 #[error("Invalid schema: {0}")]
225 InvalidSchema(String),
226}
227
228impl DatabaseError {
229 #[must_use]
230 #[allow(clippy::missing_const_for_fn)]
231 pub fn is_connection_error(&self) -> bool {
232 match &self {
233 #[cfg(feature = "postgres-sqlx")]
234 Self::PostgresSqlx(sqlx::postgres::SqlxDatabaseError::Sqlx(::sqlx::Error::Io(
235 _io_err,
236 ))) => true,
237 #[cfg(feature = "mysql-sqlx")]
238 Self::MysqlSqlx(sqlx::mysql::SqlxDatabaseError::Sqlx(::sqlx::Error::Io(_io_err))) => {
239 true
240 }
241 #[cfg(feature = "sqlite-sqlx")]
242 Self::SqliteSqlx(sqlx::sqlite::SqlxDatabaseError::Sqlx(::sqlx::Error::Io(_io_err))) => {
243 true
244 }
245 #[cfg(feature = "postgres-raw")]
246 Self::Postgres(postgres::postgres::PostgresDatabaseError::Postgres(pg_err)) => {
247 pg_err.to_string().as_str() == "connection closed"
248 }
249 #[cfg(feature = "sqlite-rusqlite")]
250 Self::Rusqlite(rusqlite::RusqliteDatabaseError::Rusqlite(
251 ::rusqlite::Error::SqliteFailure(_, _),
252 )) => true,
253 _ => false,
254 }
255 }
256}
257
258#[derive(Debug)]
259pub struct Row {
260 pub columns: Vec<(String, DatabaseValue)>,
261}
262
263impl Row {
264 #[must_use]
265 pub fn get(&self, column_name: &str) -> Option<DatabaseValue> {
266 self.columns
267 .iter()
268 .find(|c| c.0 == column_name)
269 .map(|c| c.1.clone())
270 }
271
272 #[must_use]
273 pub fn id(&self) -> Option<DatabaseValue> {
274 self.get("id")
275 }
276}
277
278#[async_trait]
279pub trait Database: Send + Sync + std::fmt::Debug {
280 fn select(&self, table_name: &'static str) -> SelectQuery<'static> {
281 query::select(table_name)
282 }
283 fn update<'a>(&self, table_name: &'a str) -> UpdateStatement<'a> {
284 query::update(table_name)
285 }
286 fn insert<'a>(&self, table_name: &'a str) -> InsertStatement<'a> {
287 query::insert(table_name)
288 }
289 fn upsert<'a>(&self, table_name: &'a str) -> UpsertStatement<'a> {
290 query::upsert(table_name)
291 }
292 fn upsert_first<'a>(&self, table_name: &'a str) -> UpsertStatement<'a> {
293 query::upsert(table_name)
294 }
295 fn upsert_multi<'a>(&self, table_name: &'a str) -> UpsertMultiStatement<'a> {
296 query::upsert_multi(table_name)
297 }
298 fn delete<'a>(&self, table_name: &'a str) -> DeleteStatement<'a> {
299 query::delete(table_name)
300 }
301
302 #[cfg(feature = "schema")]
303 fn create_table<'a>(&self, table_name: &'a str) -> schema::CreateTableStatement<'a> {
304 schema::create_table(table_name)
305 }
306
307 async fn query(&self, query: &SelectQuery<'_>) -> Result<Vec<Row>, DatabaseError>;
308 async fn query_first(&self, query: &SelectQuery<'_>) -> Result<Option<Row>, DatabaseError>;
309 async fn exec_update(&self, statement: &UpdateStatement<'_>)
310 -> Result<Vec<Row>, DatabaseError>;
311 async fn exec_update_first(
312 &self,
313 statement: &UpdateStatement<'_>,
314 ) -> Result<Option<Row>, DatabaseError>;
315 async fn exec_insert(&self, statement: &InsertStatement<'_>) -> Result<Row, DatabaseError>;
316 async fn exec_upsert(&self, statement: &UpsertStatement<'_>)
317 -> Result<Vec<Row>, DatabaseError>;
318 async fn exec_upsert_first(
319 &self,
320 statement: &UpsertStatement<'_>,
321 ) -> Result<Row, DatabaseError>;
322 async fn exec_upsert_multi(
323 &self,
324 statement: &UpsertMultiStatement<'_>,
325 ) -> Result<Vec<Row>, DatabaseError>;
326 async fn exec_delete(&self, statement: &DeleteStatement<'_>)
327 -> Result<Vec<Row>, DatabaseError>;
328 async fn exec_delete_first(
329 &self,
330 statement: &DeleteStatement<'_>,
331 ) -> Result<Option<Row>, DatabaseError>;
332
333 async fn exec_raw(&self, statement: &str) -> Result<(), DatabaseError>;
334
335 fn trigger_close(&self) -> Result<(), DatabaseError> {
339 Ok(())
340 }
341
342 async fn close(&self) -> Result<(), DatabaseError> {
343 self.trigger_close()
344 }
345
346 #[cfg(feature = "schema")]
347 async fn exec_create_table(
348 &self,
349 statement: &schema::CreateTableStatement<'_>,
350 ) -> Result<(), DatabaseError>;
351}
352
353#[async_trait]
354pub trait TryFromDb<T>
355where
356 Self: Sized,
357{
358 type Error;
359
360 async fn try_from_db(value: T, db: Arc<Box<dyn Database>>) -> Result<Self, Self::Error>;
361}
362
363#[async_trait]
364impl<T, U: Send + 'static> TryFromDb<Vec<U>> for Vec<T>
365where
366 T: TryFromDb<U> + Send,
367{
368 type Error = T::Error;
369
370 async fn try_from_db(value: Vec<U>, db: Arc<Box<dyn Database>>) -> Result<Self, T::Error> {
371 let mut converted = Self::with_capacity(value.len());
372
373 for x in value {
374 converted.push(T::try_from_db(x, db.clone()).await?);
375 }
376
377 Ok(converted)
378 }
379}
380
381#[async_trait]
382impl<T, U: Send + 'static> TryFromDb<Option<U>> for Option<T>
383where
384 T: TryFromDb<U>,
385{
386 type Error = T::Error;
387
388 async fn try_from_db(value: Option<U>, db: Arc<Box<dyn Database>>) -> Result<Self, T::Error> {
389 Ok(match value {
390 Some(x) => Some(T::try_from_db(x, db).await?),
391 None => None,
392 })
393 }
394}
395
396#[async_trait]
397pub trait TryIntoDb<T>
398where
399 Self: Sized,
400{
401 type Error;
402
403 async fn try_into_db(self, db: Arc<Box<dyn Database>>) -> Result<T, Self::Error>;
404}
405
406#[async_trait]
407impl<T: Send, U> TryIntoDb<U> for T
408where
409 U: TryFromDb<T>,
410{
411 type Error = U::Error;
412
413 async fn try_into_db(self, db: Arc<Box<dyn Database>>) -> Result<U, U::Error> {
414 U::try_from_db(self, db).await
415 }
416}