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]
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" ]

View File

@ -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()
}

View File

@ -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?);

View File

@ -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::*;

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> {
let mut query = sqlx::query(&stmt.sql);
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 {
#[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,
}

View File

@ -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<Self, TypeErr> {
@ -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<Self, TypeErr> {
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<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))
}
#[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")]
QueryResultRow::Mock(row) => {
let mut map = Map::new();