diff --git a/Cargo.toml b/Cargo.toml index 9e950495..356f0271 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ serde_json = { version = "^1", optional = true } [dev-dependencies] async-std = { version = "^1.9", features = [ "attributes" ] } maplit = { version = "^1" } +sea-orm = { path = ".", features = ["sqlx-sqlite", "runtime-async-std-native-tls"] } [features] debug-print = [] @@ -49,6 +50,7 @@ with-json = [ "serde_json", "sea-query/with-json" ] sqlx-dep = [ "sqlx", "sqlx/json" ] sqlx-mysql = [ "sqlx-dep", "sea-query/sqlx-mysql", "sqlx/mysql" ] sqlx-postgres = [ "sqlx-dep", "sea-query/sqlx-postgres", "sqlx/postgres" ] +sqlx-sqlite = [ "sqlx-dep", "sea-query/sqlx-sqlite", "sqlx/sqlite" ] runtime-actix-native-tls = [ "sqlx/runtime-actix-native-tls" ] runtime-async-std-native-tls = [ "sqlx/runtime-async-std-native-tls" ] runtime-tokio-native-tls = [ "sqlx/runtime-tokio-native-tls" ] diff --git a/src/database/connection.rs b/src/database/connection.rs index b26f432d..22acbd09 100644 --- a/src/database/connection.rs +++ b/src/database/connection.rs @@ -1,10 +1,12 @@ use crate::{ExecErr, ExecResult, QueryErr, QueryResult, Statement}; -use sea_query::{MysqlQueryBuilder, PostgresQueryBuilder, QueryStatementBuilder}; +use sea_query::{MysqlQueryBuilder, PostgresQueryBuilder, QueryStatementBuilder, SqliteQueryBuilder}; use std::{error::Error, fmt}; pub enum DatabaseConnection { #[cfg(feature = "sqlx-mysql")] SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection), + #[cfg(feature = "sqlx-sqlite")] + SqlxSqlitePoolConnection(crate::SqlxSqlitePoolConnection), #[cfg(feature = "mock")] MockDatabaseConnection(crate::MockDatabaseConnection), Disconnected, @@ -15,6 +17,7 @@ pub type DbConn = DatabaseConnection; pub enum QueryBuilderBackend { MySql, Postgres, + Sqlite, } #[derive(Debug)] @@ -42,6 +45,8 @@ impl std::fmt::Debug for DatabaseConnection { match self { #[cfg(feature = "sqlx-mysql")] Self::SqlxMySqlPoolConnection(_) => "SqlxMySqlPoolConnection", + #[cfg(feature = "sqlx-sqlite")] + Self::SqlxSqlitePoolConnection(_) => "SqlxSqlitePoolConnection", #[cfg(feature = "mock")] Self::MockDatabaseConnection(_) => "MockDatabaseConnection", Self::Disconnected => "Disconnected", @@ -55,6 +60,8 @@ impl DatabaseConnection { match self { #[cfg(feature = "sqlx-mysql")] DatabaseConnection::SqlxMySqlPoolConnection(_) => QueryBuilderBackend::MySql, + #[cfg(feature = "sqlx-sqlite")] + DatabaseConnection::SqlxSqlitePoolConnection(_) => QueryBuilderBackend::Sqlite, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(_) => QueryBuilderBackend::Postgres, DatabaseConnection::Disconnected => panic!("Disconnected"), @@ -65,6 +72,8 @@ impl DatabaseConnection { match self { #[cfg(feature = "sqlx-mysql")] DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.execute(stmt).await, + #[cfg(feature = "sqlx-sqlite")] + DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.execute(stmt).await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.execute(stmt).await, DatabaseConnection::Disconnected => panic!("Disconnected"), @@ -75,6 +84,8 @@ impl DatabaseConnection { match self { #[cfg(feature = "sqlx-mysql")] DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_one(stmt).await, + #[cfg(feature = "sqlx-sqlite")] + DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_one(stmt).await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.query_one(stmt).await, DatabaseConnection::Disconnected => panic!("Disconnected"), @@ -85,6 +96,8 @@ impl DatabaseConnection { match self { #[cfg(feature = "sqlx-mysql")] DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_all(stmt).await, + #[cfg(feature = "sqlx-sqlite")] + DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_all(stmt).await, #[cfg(feature = "mock")] DatabaseConnection::MockDatabaseConnection(conn) => conn.query_all(stmt).await, DatabaseConnection::Disconnected => panic!("Disconnected"), @@ -113,6 +126,7 @@ impl QueryBuilderBackend { match self { Self::MySql => statement.build(MysqlQueryBuilder), Self::Postgres => statement.build(PostgresQueryBuilder), + Self::Sqlite => statement.build(SqliteQueryBuilder), } .into() } diff --git a/src/database/mod.rs b/src/database/mod.rs index e86d63bd..ffcdb1c0 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -19,6 +19,10 @@ impl Database { if crate::SqlxMySqlConnector::accepts(string) { return Ok(crate::SqlxMySqlConnector::connect(string).await?); } + #[cfg(feature = "sqlx-sqlite")] + if crate::SqlxSqliteConnector::accepts(string) { + return Ok(crate::SqlxSqliteConnector::connect(string).await?); + } #[cfg(feature = "mock")] if crate::MockDatabaseConnector::accepts(string) { return Ok(crate::MockDatabaseConnector::connect(string).await?); diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 963a1597..2158f441 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -2,8 +2,16 @@ mod mock; #[cfg(feature = "sqlx-mysql")] mod sqlx_mysql; +#[cfg(feature = "sqlx-sqlite")] +mod sqlx_sqlite; +#[cfg(feature = "sqlx-dep")] +mod sqlx_types; #[cfg(feature = "mock")] pub use mock::*; #[cfg(feature = "sqlx-mysql")] pub use sqlx_mysql::*; +#[cfg(feature = "sqlx-sqlite")] +pub use sqlx_sqlite::*; +#[cfg(feature = "sqlx-dep")] +pub use sqlx_types::*; diff --git a/src/driver/sqlx_mysql.rs b/src/driver/sqlx_mysql.rs index 6d44d06e..8b609d61 100644 --- a/src/driver/sqlx_mysql.rs +++ b/src/driver/sqlx_mysql.rs @@ -93,18 +93,6 @@ impl From for ExecResult { } } -impl From for TypeErr { - fn from(_: sqlx::Error) -> TypeErr { - TypeErr - } -} - -impl From for ExecErr { - fn from(_: sqlx::Error) -> ExecErr { - ExecErr - } -} - fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, MySql, MySqlArguments> { let mut query = sqlx::query(&stmt.sql); if let Some(values) = &stmt.values { diff --git a/src/driver/sqlx_sqlite.rs b/src/driver/sqlx_sqlite.rs new file mode 100644 index 00000000..021cc919 --- /dev/null +++ b/src/driver/sqlx_sqlite.rs @@ -0,0 +1,102 @@ +use sqlx::{ + sqlite::{SqliteArguments, SqliteQueryResult, SqliteRow}, + Sqlite, SqlitePool, +}; + +sea_query::sea_query_driver_sqlite!(); +use sea_query_driver_sqlite::bind_query; + +use crate::{debug_print, executor::*, ConnectionErr, DatabaseConnection, Statement}; + +pub struct SqlxSqliteConnector; + +pub struct SqlxSqlitePoolConnection { + pool: SqlitePool, +} + +impl SqlxSqliteConnector { + pub fn accepts(string: &str) -> bool { + string.starts_with("sqlite:") + } + + pub async fn connect(string: &str) -> Result { + if let Ok(pool) = SqlitePool::connect(string).await { + Ok(DatabaseConnection::SqlxSqlitePoolConnection( + SqlxSqlitePoolConnection { pool }, + )) + } else { + Err(ConnectionErr) + } + } +} + +impl SqlxSqliteConnector { + pub fn from_sqlx_sqlite_pool(pool: SqlitePool) -> DatabaseConnection { + DatabaseConnection::SqlxSqlitePoolConnection(SqlxSqlitePoolConnection { pool }) + } +} + +impl SqlxSqlitePoolConnection { + pub async fn execute(&self, stmt: Statement) -> Result { + debug_print!("{}", stmt); + + let query = sqlx_query(&stmt); + if let Ok(conn) = &mut self.pool.acquire().await { + if let Ok(res) = query.execute(conn).await { + return Ok(res.into()); + } + } + Err(ExecErr) + } + + pub async fn query_one(&self, stmt: Statement) -> Result, QueryErr> { + debug_print!("{}", stmt); + + let query = sqlx_query(&stmt); + if let Ok(conn) = &mut self.pool.acquire().await { + if let Ok(row) = query.fetch_one(conn).await { + Ok(Some(row.into())) + } else { + Ok(None) + } + } else { + Err(QueryErr) + } + } + + pub async fn query_all(&self, stmt: Statement) -> Result, QueryErr> { + debug_print!("{}", stmt); + + let query = sqlx_query(&stmt); + if let Ok(conn) = &mut self.pool.acquire().await { + if let Ok(rows) = query.fetch_all(conn).await { + return Ok(rows.into_iter().map(|r| r.into()).collect()); + } + } + Err(QueryErr) + } +} + +impl From for QueryResult { + fn from(row: SqliteRow) -> QueryResult { + QueryResult { + row: QueryResultRow::SqlxSqlite(row), + } + } +} + +impl From for ExecResult { + fn from(result: SqliteQueryResult) -> ExecResult { + ExecResult { + result: ExecResultHolder::SqlxSqlite(result), + } + } +} + +fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, Sqlite, SqliteArguments> { + let mut query = sqlx::query(&stmt.sql); + if let Some(values) = &stmt.values { + query = bind_query(query, values); + } + query +} diff --git a/src/driver/sqlx_types.rs b/src/driver/sqlx_types.rs new file mode 100644 index 00000000..76876227 --- /dev/null +++ b/src/driver/sqlx_types.rs @@ -0,0 +1,13 @@ +use crate::{ExecErr, TypeErr}; + +impl From for TypeErr { + fn from(_: sqlx::Error) -> TypeErr { + TypeErr + } +} + +impl From for ExecErr { + fn from(_: sqlx::Error) -> ExecErr { + ExecErr + } +} \ No newline at end of file diff --git a/src/executor/execute.rs b/src/executor/execute.rs index b735cce2..c1a7cd86 100644 --- a/src/executor/execute.rs +++ b/src/executor/execute.rs @@ -9,6 +9,8 @@ pub struct ExecResult { pub(crate) enum ExecResultHolder { #[cfg(feature = "sqlx-mysql")] SqlxMySql(sqlx::mysql::MySqlQueryResult), + #[cfg(feature = "sqlx-sqlite")] + SqlxSqlite(sqlx::sqlite::SqliteQueryResult), #[cfg(feature = "mock")] Mock(crate::MockExecResult), } @@ -23,6 +25,15 @@ impl ExecResult { match &self.result { #[cfg(feature = "sqlx-mysql")] ExecResultHolder::SqlxMySql(result) => result.last_insert_id(), + #[cfg(feature = "sqlx-sqlite")] + ExecResultHolder::SqlxSqlite(result) => { + let last_insert_rowid = result.last_insert_rowid(); + if last_insert_rowid < 0 { + panic!("negative last_insert_rowid") + } else { + last_insert_rowid as u64 + } + } #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => result.last_insert_id, } @@ -32,6 +43,8 @@ impl ExecResult { match &self.result { #[cfg(feature = "sqlx-mysql")] ExecResultHolder::SqlxMySql(result) => result.rows_affected(), + #[cfg(feature = "sqlx-sqlite")] + ExecResultHolder::SqlxSqlite(result) => result.rows_affected(), #[cfg(feature = "mock")] ExecResultHolder::Mock(result) => result.rows_affected, } diff --git a/src/executor/query.rs b/src/executor/query.rs index 3f73cce4..ff8ec0f6 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -5,10 +5,11 @@ pub struct QueryResult { pub(crate) row: QueryResultRow, } -#[derive(Debug)] pub(crate) enum QueryResultRow { #[cfg(feature = "sqlx-mysql")] SqlxMySql(sqlx::mysql::MySqlRow), + #[cfg(feature = "sqlx-sqlite")] + SqlxSqlite(sqlx::sqlite::SqliteRow), #[cfg(feature = "mock")] Mock(crate::MockRow), } @@ -36,6 +37,19 @@ impl QueryResult { } } +impl fmt::Debug for QueryResultRow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + #[cfg(feature = "sqlx-mysql")] + Self::SqlxMySql(row) => write!(f, "{:?}", row), + #[cfg(feature = "sqlx-sqlite")] + Self::SqlxSqlite(_) => panic!("QueryResultRow::SqlxSqlite cannot be inspected"), + #[cfg(feature = "mock")] + Self::Mock(row) => write!(f, "{:?}", row), + } + } +} + // QueryErr // impl Error for QueryErr {} @@ -64,7 +78,7 @@ impl fmt::Display for TypeErr { // TryGetable // -macro_rules! try_getable { +macro_rules! try_getable_all { ( $type: ty ) => { impl TryGetable for $type { fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { @@ -75,6 +89,11 @@ macro_rules! try_getable { use sqlx::Row; Ok(row.try_get(column.as_str())?) } + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(row) => { + use sqlx::Row; + Ok(row.try_get(column.as_str())?) + } #[cfg(feature = "mock")] QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?), } @@ -93,6 +112,14 @@ macro_rules! try_getable { Err(_) => Ok(None), } } + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(row) => { + use sqlx::Row; + match row.try_get(column.as_str()) { + Ok(v) => Ok(Some(v)), + Err(_) => Ok(None), + } + } #[cfg(feature = "mock")] QueryResultRow::Mock(row) => match row.try_get(column.as_str()) { Ok(v) => Ok(Some(v)), @@ -104,15 +131,59 @@ macro_rules! try_getable { }; } -try_getable!(bool); -try_getable!(i8); -try_getable!(i16); -try_getable!(i32); -try_getable!(i64); -try_getable!(u8); -try_getable!(u16); -try_getable!(u32); -try_getable!(u64); -try_getable!(f32); -try_getable!(f64); -try_getable!(String); +macro_rules! try_getable_mysql { + ( $type: ty ) => { + impl TryGetable for $type { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + let column = format!("{}{}", pre, col); + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + Ok(row.try_get(column.as_str())?) + } + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(_) => panic!("{} unsupported by sqlx-sqlite", stringify!($type)), + #[cfg(feature = "mock")] + QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?), + } + } + } + + impl TryGetable for Option<$type> { + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + let column = format!("{}{}", pre, col); + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + use sqlx::Row; + match row.try_get(column.as_str()) { + Ok(v) => Ok(Some(v)), + Err(_) => Ok(None), + } + } + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(_) => panic!("{} unsupported by sqlx-sqlite", stringify!($type)), + #[cfg(feature = "mock")] + QueryResultRow::Mock(row) => match row.try_get(column.as_str()) { + Ok(v) => Ok(Some(v)), + Err(_) => Ok(None), + }, + } + } + } + }; +} + +try_getable_all!(bool); +try_getable_all!(i8); +try_getable_all!(i16); +try_getable_all!(i32); +try_getable_all!(i64); +try_getable_all!(u8); +try_getable_all!(u16); +try_getable_all!(u32); +try_getable_mysql!(u64); +try_getable_all!(f32); +try_getable_all!(f64); +try_getable_all!(String); diff --git a/src/query/json.rs b/src/query/json.rs index 28a1a688..9385dac4 100644 --- a/src/query/json.rs +++ b/src/query/json.rs @@ -43,6 +43,44 @@ impl FromQueryResult for JsonValue { } Ok(JsonValue::Object(map)) } + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(row) => { + use serde_json::json; + use sqlx::{Column, Sqlite, Row, Type}; + let mut map = Map::new(); + for column in row.columns() { + let col = if !column.name().starts_with(pre) { + continue; + } else { + column.name().replacen(pre, "", 1) + }; + let col_type = column.type_info(); + macro_rules! match_sqlite_type { + ( $type: ty ) => { + if <$type as Type>::type_info().eq(col_type) { + map.insert( + col.to_owned(), + json!(res.try_get::>(pre, &col)?), + ); + continue; + } + }; + } + match_sqlite_type!(bool); + match_sqlite_type!(i8); + match_sqlite_type!(i16); + match_sqlite_type!(i32); + match_sqlite_type!(i64); + match_sqlite_type!(u8); + match_sqlite_type!(u16); + match_sqlite_type!(u32); + // match_sqlite_type!(u64); // unsupported by SQLx Sqlite + match_sqlite_type!(f32); + match_sqlite_type!(f64); + match_sqlite_type!(String); + } + Ok(JsonValue::Object(map)) + } #[cfg(feature = "mock")] QueryResultRow::Mock(row) => { let mut map = Map::new();