SQLx SQLite support

This commit is contained in:
Chris Tsang 2021-06-20 23:03:12 +08:00
parent cdc8056d4b
commit ec290156c5
10 changed files with 280 additions and 27 deletions

View File

@ -39,6 +39,7 @@ serde_json = { version = "^1", optional = true }
[dev-dependencies] [dev-dependencies]
async-std = { version = "^1.9", features = [ "attributes" ] } async-std = { version = "^1.9", features = [ "attributes" ] }
maplit = { version = "^1" } maplit = { version = "^1" }
sea-orm = { path = ".", features = ["sqlx-sqlite", "runtime-async-std-native-tls"] }
[features] [features]
debug-print = [] debug-print = []
@ -49,6 +50,7 @@ with-json = [ "serde_json", "sea-query/with-json" ]
sqlx-dep = [ "sqlx", "sqlx/json" ] sqlx-dep = [ "sqlx", "sqlx/json" ]
sqlx-mysql = [ "sqlx-dep", "sea-query/sqlx-mysql", "sqlx/mysql" ] sqlx-mysql = [ "sqlx-dep", "sea-query/sqlx-mysql", "sqlx/mysql" ]
sqlx-postgres = [ "sqlx-dep", "sea-query/sqlx-postgres", "sqlx/postgres" ] 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-actix-native-tls = [ "sqlx/runtime-actix-native-tls" ]
runtime-async-std-native-tls = [ "sqlx/runtime-async-std-native-tls" ] runtime-async-std-native-tls = [ "sqlx/runtime-async-std-native-tls" ]
runtime-tokio-native-tls = [ "sqlx/runtime-tokio-native-tls" ] runtime-tokio-native-tls = [ "sqlx/runtime-tokio-native-tls" ]

View File

@ -1,10 +1,12 @@
use crate::{ExecErr, ExecResult, QueryErr, QueryResult, Statement}; 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}; use std::{error::Error, fmt};
pub enum DatabaseConnection { pub enum DatabaseConnection {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection), SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection),
#[cfg(feature = "sqlx-sqlite")]
SqlxSqlitePoolConnection(crate::SqlxSqlitePoolConnection),
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
MockDatabaseConnection(crate::MockDatabaseConnection), MockDatabaseConnection(crate::MockDatabaseConnection),
Disconnected, Disconnected,
@ -15,6 +17,7 @@ pub type DbConn = DatabaseConnection;
pub enum QueryBuilderBackend { pub enum QueryBuilderBackend {
MySql, MySql,
Postgres, Postgres,
Sqlite,
} }
#[derive(Debug)] #[derive(Debug)]
@ -42,6 +45,8 @@ impl std::fmt::Debug for DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
Self::SqlxMySqlPoolConnection(_) => "SqlxMySqlPoolConnection", Self::SqlxMySqlPoolConnection(_) => "SqlxMySqlPoolConnection",
#[cfg(feature = "sqlx-sqlite")]
Self::SqlxSqlitePoolConnection(_) => "SqlxSqlitePoolConnection",
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
Self::MockDatabaseConnection(_) => "MockDatabaseConnection", Self::MockDatabaseConnection(_) => "MockDatabaseConnection",
Self::Disconnected => "Disconnected", Self::Disconnected => "Disconnected",
@ -55,6 +60,8 @@ impl DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
DatabaseConnection::SqlxMySqlPoolConnection(_) => QueryBuilderBackend::MySql, DatabaseConnection::SqlxMySqlPoolConnection(_) => QueryBuilderBackend::MySql,
#[cfg(feature = "sqlx-sqlite")]
DatabaseConnection::SqlxSqlitePoolConnection(_) => QueryBuilderBackend::Sqlite,
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
DatabaseConnection::MockDatabaseConnection(_) => QueryBuilderBackend::Postgres, DatabaseConnection::MockDatabaseConnection(_) => QueryBuilderBackend::Postgres,
DatabaseConnection::Disconnected => panic!("Disconnected"), DatabaseConnection::Disconnected => panic!("Disconnected"),
@ -65,6 +72,8 @@ impl DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.execute(stmt).await, DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.execute(stmt).await,
#[cfg(feature = "sqlx-sqlite")]
DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.execute(stmt).await,
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
DatabaseConnection::MockDatabaseConnection(conn) => conn.execute(stmt).await, DatabaseConnection::MockDatabaseConnection(conn) => conn.execute(stmt).await,
DatabaseConnection::Disconnected => panic!("Disconnected"), DatabaseConnection::Disconnected => panic!("Disconnected"),
@ -75,6 +84,8 @@ impl DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_one(stmt).await, DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_one(stmt).await,
#[cfg(feature = "sqlx-sqlite")]
DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_one(stmt).await,
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
DatabaseConnection::MockDatabaseConnection(conn) => conn.query_one(stmt).await, DatabaseConnection::MockDatabaseConnection(conn) => conn.query_one(stmt).await,
DatabaseConnection::Disconnected => panic!("Disconnected"), DatabaseConnection::Disconnected => panic!("Disconnected"),
@ -85,6 +96,8 @@ impl DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_all(stmt).await, DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_all(stmt).await,
#[cfg(feature = "sqlx-sqlite")]
DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_all(stmt).await,
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
DatabaseConnection::MockDatabaseConnection(conn) => conn.query_all(stmt).await, DatabaseConnection::MockDatabaseConnection(conn) => conn.query_all(stmt).await,
DatabaseConnection::Disconnected => panic!("Disconnected"), DatabaseConnection::Disconnected => panic!("Disconnected"),
@ -113,6 +126,7 @@ impl QueryBuilderBackend {
match self { match self {
Self::MySql => statement.build(MysqlQueryBuilder), Self::MySql => statement.build(MysqlQueryBuilder),
Self::Postgres => statement.build(PostgresQueryBuilder), Self::Postgres => statement.build(PostgresQueryBuilder),
Self::Sqlite => statement.build(SqliteQueryBuilder),
} }
.into() .into()
} }

View File

@ -19,6 +19,10 @@ impl Database {
if crate::SqlxMySqlConnector::accepts(string) { if crate::SqlxMySqlConnector::accepts(string) {
return Ok(crate::SqlxMySqlConnector::connect(string).await?); 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")] #[cfg(feature = "mock")]
if crate::MockDatabaseConnector::accepts(string) { if crate::MockDatabaseConnector::accepts(string) {
return Ok(crate::MockDatabaseConnector::connect(string).await?); return Ok(crate::MockDatabaseConnector::connect(string).await?);

View File

@ -2,8 +2,16 @@
mod mock; mod mock;
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
mod sqlx_mysql; mod sqlx_mysql;
#[cfg(feature = "sqlx-sqlite")]
mod sqlx_sqlite;
#[cfg(feature = "sqlx-dep")]
mod sqlx_types;
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
pub use mock::*; pub use mock::*;
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
pub use sqlx_mysql::*; pub use sqlx_mysql::*;
#[cfg(feature = "sqlx-sqlite")]
pub use sqlx_sqlite::*;
#[cfg(feature = "sqlx-dep")]
pub use sqlx_types::*;

View File

@ -93,18 +93,6 @@ impl From<MySqlQueryResult> for ExecResult {
} }
} }
impl From<sqlx::Error> for TypeErr {
fn from(_: sqlx::Error) -> TypeErr {
TypeErr
}
}
impl From<sqlx::Error> for ExecErr {
fn from(_: sqlx::Error) -> ExecErr {
ExecErr
}
}
fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, MySql, MySqlArguments> { fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, MySql, MySqlArguments> {
let mut query = sqlx::query(&stmt.sql); let mut query = sqlx::query(&stmt.sql);
if let Some(values) = &stmt.values { if let Some(values) = &stmt.values {

102
src/driver/sqlx_sqlite.rs Normal file
View File

@ -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<DatabaseConnection, ConnectionErr> {
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<ExecResult, ExecErr> {
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<Option<QueryResult>, 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<Vec<QueryResult>, 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<SqliteRow> for QueryResult {
fn from(row: SqliteRow) -> QueryResult {
QueryResult {
row: QueryResultRow::SqlxSqlite(row),
}
}
}
impl From<SqliteQueryResult> 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
}

13
src/driver/sqlx_types.rs Normal file
View File

@ -0,0 +1,13 @@
use crate::{ExecErr, TypeErr};
impl From<sqlx::Error> for TypeErr {
fn from(_: sqlx::Error) -> TypeErr {
TypeErr
}
}
impl From<sqlx::Error> for ExecErr {
fn from(_: sqlx::Error) -> ExecErr {
ExecErr
}
}

View File

@ -9,6 +9,8 @@ pub struct ExecResult {
pub(crate) enum ExecResultHolder { pub(crate) enum ExecResultHolder {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
SqlxMySql(sqlx::mysql::MySqlQueryResult), SqlxMySql(sqlx::mysql::MySqlQueryResult),
#[cfg(feature = "sqlx-sqlite")]
SqlxSqlite(sqlx::sqlite::SqliteQueryResult),
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
Mock(crate::MockExecResult), Mock(crate::MockExecResult),
} }
@ -23,6 +25,15 @@ impl ExecResult {
match &self.result { match &self.result {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
ExecResultHolder::SqlxMySql(result) => result.last_insert_id(), 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")] #[cfg(feature = "mock")]
ExecResultHolder::Mock(result) => result.last_insert_id, ExecResultHolder::Mock(result) => result.last_insert_id,
} }
@ -32,6 +43,8 @@ impl ExecResult {
match &self.result { match &self.result {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
ExecResultHolder::SqlxMySql(result) => result.rows_affected(), ExecResultHolder::SqlxMySql(result) => result.rows_affected(),
#[cfg(feature = "sqlx-sqlite")]
ExecResultHolder::SqlxSqlite(result) => result.rows_affected(),
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
ExecResultHolder::Mock(result) => result.rows_affected, ExecResultHolder::Mock(result) => result.rows_affected,
} }

View File

@ -5,10 +5,11 @@ pub struct QueryResult {
pub(crate) row: QueryResultRow, pub(crate) row: QueryResultRow,
} }
#[derive(Debug)]
pub(crate) enum QueryResultRow { pub(crate) enum QueryResultRow {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
SqlxMySql(sqlx::mysql::MySqlRow), SqlxMySql(sqlx::mysql::MySqlRow),
#[cfg(feature = "sqlx-sqlite")]
SqlxSqlite(sqlx::sqlite::SqliteRow),
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
Mock(crate::MockRow), 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 // // QueryErr //
impl Error for QueryErr {} impl Error for QueryErr {}
@ -64,7 +78,7 @@ impl fmt::Display for TypeErr {
// TryGetable // // TryGetable //
macro_rules! try_getable { macro_rules! try_getable_all {
( $type: ty ) => { ( $type: ty ) => {
impl TryGetable for $type { impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TypeErr> { fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TypeErr> {
@ -75,6 +89,11 @@ macro_rules! try_getable {
use sqlx::Row; use sqlx::Row;
Ok(row.try_get(column.as_str())?) 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")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?), QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?),
} }
@ -93,6 +112,14 @@ macro_rules! try_getable {
Err(_) => Ok(None), 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")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) { QueryResultRow::Mock(row) => match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)), Ok(v) => Ok(Some(v)),
@ -104,15 +131,59 @@ macro_rules! try_getable {
}; };
} }
try_getable!(bool); macro_rules! try_getable_mysql {
try_getable!(i8); ( $type: ty ) => {
try_getable!(i16); impl TryGetable for $type {
try_getable!(i32); fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TypeErr> {
try_getable!(i64); let column = format!("{}{}", pre, col);
try_getable!(u8); match &res.row {
try_getable!(u16); #[cfg(feature = "sqlx-mysql")]
try_getable!(u32); QueryResultRow::SqlxMySql(row) => {
try_getable!(u64); use sqlx::Row;
try_getable!(f32); Ok(row.try_get(column.as_str())?)
try_getable!(f64); }
try_getable!(String); #[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<Self, TypeErr> {
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);

View File

@ -43,6 +43,44 @@ impl FromQueryResult for JsonValue {
} }
Ok(JsonValue::Object(map)) 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<Sqlite>>::type_info().eq(col_type) {
map.insert(
col.to_owned(),
json!(res.try_get::<Option<$type>>(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")] #[cfg(feature = "mock")]
QueryResultRow::Mock(row) => { QueryResultRow::Mock(row) => {
let mut map = Map::new(); let mut map = Map::new();