Merge branch 'master' into serialize-query-result

This commit is contained in:
Chris Tsang 2021-05-27 02:33:24 +08:00 committed by GitHub
commit 73ed4b6f81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 37 deletions

View File

@ -37,7 +37,7 @@ Use mock connections to write unit tests for your logic.
Quickly build search models that help you filter, sort and paginate data in APIs. Quickly build search models that help you filter, sort and paginate data in APIs.
## Design goals ## Design Goals
1. Intuitive and ergonomic 1. Intuitive and ergonomic
@ -50,3 +50,9 @@ Balance between compile-time checking and compilation speed.
3. Avoid 'symbol soup' 3. Avoid 'symbol soup'
Avoid macros with DSL, use derive macros where appropriate. Be friendly with IDE tools. Avoid macros with DSL, use derive macros where appropriate. Be friendly with IDE tools.
## Test Time
After some bitterness we realized it is not possible to capture everything compile time. But we don't
want to encounter problems at run time either. The solution is to perform checking at 'test time' to
uncover problems. These checks will be removed at production so there will be no run time penalty.

51
src/connector/executor.rs Normal file
View File

@ -0,0 +1,51 @@
use sqlx::mysql::MySqlQueryResult;
use std::{error::Error, fmt};
#[derive(Debug)]
pub struct ExecResult {
pub(crate) result: ExecResultHolder,
}
#[derive(Debug)]
pub(crate) enum ExecResultHolder {
SqlxMySql(MySqlQueryResult),
}
#[derive(Debug)]
pub struct ExecErr;
// ExecResult //
impl ExecResult {
pub fn last_insert_id(&self) -> u64 {
match &self.result {
ExecResultHolder::SqlxMySql(result) => {
result.last_insert_id()
}
}
}
pub fn rows_affected(&self) -> u64 {
match &self.result {
ExecResultHolder::SqlxMySql(result) => {
result.rows_affected()
}
}
}
}
// ExecErr //
impl Error for ExecErr {}
impl fmt::Display for ExecErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl From<sqlx::Error> for ExecErr {
fn from(_: sqlx::Error) -> ExecErr {
ExecErr
}
}

View File

@ -1,10 +1,10 @@
mod executor;
mod select; mod select;
#[cfg(feature = "serialize-query-result")] #[cfg(feature = "serialize-query-result")]
mod select_json; mod select_json;
pub use executor::*;
pub use select::*; pub use select::*;
#[cfg(feature = "serialize-query-result")] #[cfg(feature = "serialize-query-result")]
pub use select_json::*; pub use select_json::*;
@ -21,6 +21,8 @@ pub trait Connector {
#[async_trait] #[async_trait]
pub trait Connection { pub trait Connection {
async fn execute(&self, stmt: Statement) -> Result<ExecResult, ExecErr>;
async fn query_one(&self, stmt: Statement) -> Result<QueryResult, QueryErr>; async fn query_one(&self, stmt: Statement) -> Result<QueryResult, QueryErr>;
async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, QueryErr>; async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, QueryErr>;

View File

@ -3,25 +3,32 @@ use std::fmt;
pub struct Statement { pub struct Statement {
pub sql: String, pub sql: String,
pub values: Values, pub values: Option<Values>,
} }
impl From<(String, Values)> for Statement { impl From<(String, Values)> for Statement {
fn from(stmt: (String, Values)) -> Statement { fn from(stmt: (String, Values)) -> Statement {
Statement { Statement {
sql: stmt.0, sql: stmt.0,
values: stmt.1, values: Some(stmt.1),
} }
} }
} }
impl fmt::Display for Statement { impl fmt::Display for Statement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.values {
Some(values) => {
let string = inject_parameters( let string = inject_parameters(
&self.sql, &self.sql,
self.values.0.clone(), values.0.clone(),
&MySqlQueryBuilder::default(), &MySqlQueryBuilder::default(),
); );
write!(f, "{}", &string) write!(f, "{}", &string)
} }
None => {
write!(f, "{}", &self.sql)
}
}
}
} }

View File

@ -1,5 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use sqlx::{mysql::MySqlRow, MySqlPool}; use sqlx::{mysql::{MySqlRow, MySqlArguments, MySqlQueryResult}, MySql, MySqlPool};
sea_query::sea_query_driver_mysql!(); sea_query::sea_query_driver_mysql!();
use sea_query_driver_mysql::bind_query; use sea_query_driver_mysql::bind_query;
@ -31,10 +31,22 @@ impl Connector for SqlxMySqlConnector {
#[async_trait] #[async_trait]
impl Connection for &SqlxMySqlPoolConnection { impl Connection for &SqlxMySqlPoolConnection {
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)
}
async fn query_one(&self, stmt: Statement) -> Result<QueryResult, QueryErr> { async fn query_one(&self, stmt: Statement) -> Result<QueryResult, QueryErr> {
debug_print!("{}", stmt); debug_print!("{}", stmt);
let query = bind_query(sqlx::query(&stmt.sql), &stmt.values); let query = sqlx_query(&stmt);
if let Ok(conn) = &mut self.pool.acquire().await { if let Ok(conn) = &mut self.pool.acquire().await {
if let Ok(row) = query.fetch_one(conn).await { if let Ok(row) = query.fetch_one(conn).await {
return Ok(row.into()); return Ok(row.into());
@ -46,7 +58,7 @@ impl Connection for &SqlxMySqlPoolConnection {
async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, QueryErr> { async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, QueryErr> {
debug_print!("{}", stmt); debug_print!("{}", stmt);
let query = bind_query(sqlx::query(&stmt.sql), &stmt.values); let query = sqlx_query(&stmt);
if let Ok(conn) = &mut self.pool.acquire().await { if let Ok(conn) = &mut self.pool.acquire().await {
if let Ok(rows) = query.fetch_all(conn).await { if let Ok(rows) = query.fetch_all(conn).await {
return Ok(rows.into_iter().map(|r| r.into()).collect()); return Ok(rows.into_iter().map(|r| r.into()).collect());
@ -63,3 +75,19 @@ impl From<MySqlRow> for QueryResult {
} }
} }
} }
impl From<MySqlQueryResult> for ExecResult {
fn from(result: MySqlQueryResult) -> ExecResult {
ExecResult {
result: ExecResultHolder::SqlxMySql(result),
}
}
}
fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, MySql, MySqlArguments> {
let mut query = sqlx::query(&stmt.sql);
if let Some(values) = &stmt.values {
query = bind_query(query, values);
}
query
}

View File

@ -70,7 +70,7 @@ pub trait EntityTrait: EntityName {
/// .to_string(), /// .to_string(),
/// [ /// [
/// r#"SELECT "cake_filling"."cake_id", "cake_filling"."filling_id" FROM "cake_filling""#, /// r#"SELECT "cake_filling"."cake_id", "cake_filling"."filling_id" FROM "cake_filling""#,
/// r#"WHERE "cake_filling"."cake_id" = 2 AND ("cake_filling"."filling_id" = 3)"#, /// r#"WHERE "cake_filling"."cake_id" = 2 AND "cake_filling"."filling_id" = 3"#,
/// ].join(" ") /// ].join(" ")
/// ); /// );
/// ``` /// ```

View File

@ -116,7 +116,7 @@ mod tests {
"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,", "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,",
"`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`", "`fruit`.`id` AS `B_id`, `fruit`.`name` AS `B_name`, `fruit`.`cake_id` AS `B_cake_id`",
"FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`", "FROM `cake` LEFT JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id`",
"WHERE `cake`.`id` = 1 AND (`fruit`.`id` = 2)", "WHERE `cake`.`id` = 1 AND `fruit`.`id` = 2",
].join(" ") ].join(" ")
); );
} }

View File

@ -1,12 +1,8 @@
use crate::{ColumnTrait, Identity, IntoSimpleExpr, RelationDef}; use crate::{ColumnTrait, Identity, IntoSimpleExpr, RelationDef};
use sea_query::{Alias, Expr, SelectExpr, SelectStatement, SimpleExpr}; use sea_query::{Alias, Expr, IntoCondition, SelectExpr, SelectStatement, SimpleExpr};
pub use sea_query::{Condition, JoinType, Order}; pub use sea_query::{Condition, JoinType, Order};
use std::rc::Rc; use std::rc::Rc;
pub trait IntoCondition {
fn into_condition(self) -> Condition;
}
pub trait QueryHelper: Sized { pub trait QueryHelper: Sized {
fn query(&mut self) -> &mut SelectStatement; fn query(&mut self) -> &mut SelectStatement;
@ -71,7 +67,7 @@ pub trait QueryHelper: Sized {
/// .filter(cake::Column::Id.eq(5)) /// .filter(cake::Column::Id.eq(5))
/// .build(MysqlQueryBuilder) /// .build(MysqlQueryBuilder)
/// .to_string(), /// .to_string(),
/// "SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 4 AND (`cake`.`id` = 5)" /// "SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 4 AND `cake`.`id` = 5"
/// ); /// );
/// ``` /// ```
/// ///
@ -92,7 +88,9 @@ pub trait QueryHelper: Sized {
/// ); /// );
/// ``` /// ```
fn filter<F>(mut self, filter: F) -> Self fn filter<F>(mut self, filter: F) -> Self
where F: IntoCondition { where
F: IntoCondition,
{
self.query().cond_where(filter.into_condition()); self.query().cond_where(filter.into_condition());
self self
} }
@ -216,18 +214,6 @@ pub trait QueryHelper: Sized {
} }
} }
impl IntoCondition for SimpleExpr {
fn into_condition(self) -> Condition {
Condition::all().add(self)
}
}
impl IntoCondition for Condition {
fn into_condition(self) -> Condition {
self
}
}
fn join_condition(rel: RelationDef) -> SimpleExpr { fn join_condition(rel: RelationDef) -> SimpleExpr {
let from_tbl = rel.from_tbl.clone(); let from_tbl = rel.from_tbl.clone();
let to_tbl = rel.to_tbl.clone(); let to_tbl = rel.to_tbl.clone();