-
-
Notifications
You must be signed in to change notification settings - Fork 12
add partly Mysql support #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
baf05ae
702ddad
778fecb
6f76152
61641d2
0da2814
0ebcb0d
52759f9
5f9478d
a92f2c5
e8e1a41
add2c6d
0e82a5c
f781c32
0bd0c52
bafc006
463dafc
858d82f
4ed4ca7
494511e
b1e714b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
use super::{HandleBatchInsert, HandleInsert}; | ||
use crate::context::WundergraphContext; | ||
use crate::helper::UnRef; | ||
use crate::query_builder::selection::fields::WundergraphFieldList; | ||
use crate::query_builder::selection::filter::build_filter::BuildFilter; | ||
use crate::query_builder::selection::order::BuildOrder; | ||
use crate::query_builder::selection::query_modifier::QueryModifier; | ||
use crate::query_builder::selection::select::BuildSelect; | ||
use crate::query_builder::selection::{LoadingHandler, SqlTypeOfPlaceholder}; | ||
use crate::scalar::WundergraphScalarValue; | ||
use diesel::associations::HasTable; | ||
use diesel::deserialize::FromSql; | ||
use diesel::dsl::SqlTypeOf; | ||
use diesel::expression::{Expression, NonAggregate, SelectableExpression}; | ||
use diesel::insertable::CanInsertInSingleQuery; | ||
use diesel::mysql::Mysql; | ||
use diesel::query_builder::{BoxedSelectStatement, QueryFragment}; | ||
use diesel::query_dsl::methods::{BoxedDsl, FilterDsl}; | ||
use diesel::sql_types::{Bigint, HasSqlType, Integer}; | ||
use diesel::{no_arg_sql_function, EqAll, Identifiable}; | ||
use diesel::{AppearsOnTable, Connection, Insertable, RunQueryDsl, Table}; | ||
use juniper::{ExecutionResult, Executor, Selection, Value}; | ||
use std::convert::TryFrom; | ||
|
||
// https://dev.mysql.com/doc/refman/8.0/en/getting-unique-id.html | ||
diesel::no_arg_sql_function!(LAST_INSERT_ID, Bigint); | ||
|
||
impl<I, Ctx, L, T> HandleInsert<L, I, Mysql, Ctx> for T | ||
where | ||
T: Table + HasTable<Table = T> + 'static, | ||
T::FromClause: QueryFragment<Mysql>, | ||
L: LoadingHandler<Mysql, Ctx, Table = T> + 'static, | ||
L::Columns: BuildOrder<T, Mysql> | ||
+ BuildSelect<T, Mysql, SqlTypeOfPlaceholder<L::FieldList, Mysql, L::PrimaryKeyIndex, T, Ctx>>, | ||
Ctx: WundergraphContext + QueryModifier<L, Mysql>, | ||
Ctx::Connection: Connection<Backend = Mysql>, | ||
L::FieldList: WundergraphFieldList<Mysql, L::PrimaryKeyIndex, T, Ctx>, | ||
I: Insertable<T>, | ||
I::Values: QueryFragment<Mysql> + CanInsertInSingleQuery<Mysql>, | ||
T::PrimaryKey: QueryFragment<Mysql> + Default, | ||
T: BoxedDsl< | ||
'static, | ||
Mysql, | ||
Output = BoxedSelectStatement<'static, SqlTypeOf<<T as Table>::AllColumns>, T, Mysql>, | ||
>, | ||
<Ctx::Connection as Connection>::Backend: HasSqlType<SqlTypeOf<T::PrimaryKey>> | ||
+ HasSqlType<SqlTypeOfPlaceholder<L::FieldList, Mysql, L::PrimaryKeyIndex, T, Ctx>>, | ||
<L::Filter as BuildFilter<Mysql>>::Ret: AppearsOnTable<T>, | ||
T::PrimaryKey: EqAll<i32>, | ||
T::PrimaryKey: Expression<SqlType = diesel::sql_types::Integer>, | ||
&'static L: Identifiable, | ||
<&'static L as Identifiable>::Id: UnRef<'static, UnRefed = i32>, | ||
<T::PrimaryKey as EqAll<i32>>::Output: | ||
SelectableExpression<T> + NonAggregate + QueryFragment<Mysql> + 'static, | ||
{ | ||
fn handle_insert( | ||
selection: Option<&'_ [Selection<'_, WundergraphScalarValue>]>, | ||
executor: &Executor<'_, Ctx, WundergraphScalarValue>, | ||
insertable: I, | ||
) -> ExecutionResult<WundergraphScalarValue> { | ||
handle_single_insert(selection, executor, insertable) | ||
} | ||
} | ||
|
||
impl<I, Ctx, L, T> HandleBatchInsert<L, I, Mysql, Ctx> for T | ||
where | ||
T: Table + HasTable<Table = T> + 'static, | ||
T::FromClause: QueryFragment<Mysql>, | ||
L: LoadingHandler<Mysql, Ctx, Table = T> + 'static, | ||
L::Columns: BuildOrder<T, Mysql> | ||
+ BuildSelect<T, Mysql, SqlTypeOfPlaceholder<L::FieldList, Mysql, L::PrimaryKeyIndex, T, Ctx>>, | ||
Ctx: WundergraphContext + QueryModifier<L, Mysql>, | ||
Ctx::Connection: Connection<Backend = Mysql>, | ||
L::FieldList: WundergraphFieldList<Mysql, L::PrimaryKeyIndex, T, Ctx>, | ||
I: Insertable<T>, | ||
I::Values: QueryFragment<Mysql> + CanInsertInSingleQuery<Mysql>, | ||
T::PrimaryKey: QueryFragment<Mysql> + Default, | ||
T: BoxedDsl< | ||
'static, | ||
Mysql, | ||
Output = BoxedSelectStatement<'static, SqlTypeOf<<T as Table>::AllColumns>, T, Mysql>, | ||
>, | ||
<Ctx::Connection as Connection>::Backend: HasSqlType<SqlTypeOf<T::PrimaryKey>> | ||
+ HasSqlType<SqlTypeOfPlaceholder<L::FieldList, Mysql, L::PrimaryKeyIndex, T, Ctx>>, | ||
<L::Filter as BuildFilter<Mysql>>::Ret: AppearsOnTable<T>, | ||
T::PrimaryKey: EqAll<i32>, | ||
T::PrimaryKey: Expression<SqlType = diesel::sql_types::Integer>, | ||
&'static L: Identifiable, | ||
<&'static L as Identifiable>::Id: UnRef<'static, UnRefed = i32>, | ||
<T::PrimaryKey as EqAll<i32>>::Output: | ||
SelectableExpression<T> + NonAggregate + QueryFragment<Mysql> + 'static, | ||
{ | ||
fn handle_batch_insert( | ||
selection: Option<&'_ [Selection<'_, WundergraphScalarValue>]>, | ||
executor: &Executor<'_, Ctx, WundergraphScalarValue>, | ||
batch: Vec<I>, | ||
) -> ExecutionResult<WundergraphScalarValue> { | ||
let r = batch | ||
.into_iter() | ||
.map(|i| handle_single_insert(selection, executor, i)) | ||
.collect::<Result<Vec<_>, _>>()?; | ||
Ok(Value::List(r)) | ||
} | ||
} | ||
|
||
fn handle_single_insert<L, I, T, Ctx>( | ||
selection: Option<&'_ [Selection<'_, WundergraphScalarValue>]>, | ||
executor: &Executor<'_, Ctx, WundergraphScalarValue>, | ||
insertable: I, | ||
) -> ExecutionResult<WundergraphScalarValue> | ||
where | ||
L: LoadingHandler<Mysql, Ctx, Table = T> + 'static, | ||
L::FieldList: WundergraphFieldList<Mysql, L::PrimaryKeyIndex, T, Ctx>, | ||
<L::Filter as BuildFilter<Mysql>>::Ret: AppearsOnTable<T>, | ||
L::Columns: BuildOrder<T, Mysql> | ||
+ BuildSelect<T, Mysql, SqlTypeOfPlaceholder<L::FieldList, Mysql, L::PrimaryKeyIndex, T, Ctx>>, | ||
&'static L: Identifiable, | ||
I: Insertable<T>, | ||
I::Values: QueryFragment<Mysql> + CanInsertInSingleQuery<Mysql>, | ||
Ctx: WundergraphContext + QueryModifier<L, Mysql>, | ||
Ctx::Connection: Connection<Backend = Mysql>, | ||
<Ctx::Connection as Connection>::Backend: HasSqlType<SqlTypeOf<T::PrimaryKey>> | ||
+ HasSqlType<SqlTypeOfPlaceholder<L::FieldList, Mysql, L::PrimaryKeyIndex, T, Ctx>>, | ||
T: Table + HasTable<Table = T> + 'static, | ||
T::FromClause: QueryFragment<Mysql>, | ||
T: BoxedDsl< | ||
'static, | ||
Mysql, | ||
Output = BoxedSelectStatement<'static, SqlTypeOf<<T as Table>::AllColumns>, T, Mysql>, | ||
>, | ||
T::PrimaryKey: EqAll<i32>, | ||
T::PrimaryKey: Expression<SqlType = Integer>, | ||
T::PrimaryKey: QueryFragment<Mysql> + Default, | ||
<T::PrimaryKey as EqAll<i32>>::Output: | ||
SelectableExpression<T> + NonAggregate + QueryFragment<Mysql> + 'static, | ||
i32: FromSql<Integer, Mysql>, | ||
{ | ||
let ctx = executor.context(); | ||
let conn = ctx.get_connection(); | ||
let look_ahead = executor.look_ahead(); | ||
insertable.insert_into(T::table()).execute(conn).unwrap(); | ||
let last_insert_id: i64 = diesel::select(LAST_INSERT_ID).first(conn)?; | ||
let last_insert_id = i32::try_from(last_insert_id)?; | ||
let q = L::build_query(&[], &look_ahead)?; | ||
let q = FilterDsl::filter(q, T::PrimaryKey::default().eq_all(last_insert_id)); | ||
let items = L::load(&look_ahead, selection, executor, q)?; | ||
Ok(items.into_iter().next().unwrap_or(Value::Null)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,16 @@ | ||
use crate::error::Result; | ||
#[cfg(any(feature = "postgres", feature = "sqlite"))] | ||
#[cfg(any(feature = "postgres", feature = "sqlite", feature = "mysql"))] | ||
use crate::error::WundergraphError; | ||
#[cfg(any(feature = "postgres", feature = "sqlite"))] | ||
#[cfg(any(feature = "postgres", feature = "sqlite", feature = "mysql"))] | ||
use crate::juniper_ext::FromLookAheadValue; | ||
use crate::query_builder::selection::{BoxedQuery, LoadingHandler}; | ||
use crate::scalar::WundergraphScalarValue; | ||
use diesel::backend::Backend; | ||
#[cfg(feature = "sqlite")] | ||
#[cfg(any(feature = "sqlite", feature = "mysql"))] | ||
use diesel::query_dsl::methods::LimitDsl; | ||
#[cfg(any(feature = "postgres", feature = "sqlite"))] | ||
#[cfg(any(feature = "postgres", feature = "sqlite", feature = "mysql"))] | ||
use diesel::query_dsl::methods::OffsetDsl; | ||
|
||
use juniper::LookAheadSelection; | ||
|
||
/// A trait abstracting over the different behaviour of limit/offset | ||
|
@@ -72,3 +73,36 @@ impl ApplyOffset for diesel::sqlite::Sqlite { | |
} | ||
} | ||
} | ||
|
||
#[cfg(feature = "mysql")] | ||
impl ApplyOffset for diesel::mysql::Mysql { | ||
fn apply_offset<'a, L, Ctx>( | ||
query: BoxedQuery<'a, L, Self, Ctx>, | ||
select: &LookAheadSelection<'_, WundergraphScalarValue>, | ||
) -> Result<BoxedQuery<'a, L, Self, Ctx>> | ||
where | ||
L: LoadingHandler<Self, Ctx>, | ||
{ | ||
use juniper::LookAheadMethods; | ||
if let Some(offset) = select.argument("offset") { | ||
let q = <_ as OffsetDsl>::offset( | ||
query, | ||
i64::from_look_ahead(offset.value()) | ||
.ok_or(WundergraphError::CouldNotBuildFilterArgument)?, | ||
); | ||
if select.argument("limit").is_some() { | ||
Ok(q) | ||
} else { | ||
// Mysql requires a limit clause in front of any offset clause | ||
// The documentation proposes the following: | ||
// > To retrieve all rows from a certain offset up to the end of the | ||
// > result set, you can use some large number for the second parameter. | ||
// https://dev.mysql.com/doc/refman/8.0/en/select.html | ||
// Therefore we just use i64::MAX as limit here | ||
Ok(<_ as LimitDsl>::limit(q, std::i64::MAX)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A small comment why this is necessary would be nice, maybe just link the relevant mysql documentation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I put diesel's comment there |
||
} | ||
} else { | ||
Ok(query) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,5 +24,6 @@ serde_json = "1" | |
|
||
[features] | ||
default = ["postgres", "sqlite"] | ||
sqlite = ["diesel/sqlite"] | ||
mysql = ["diesel/mysql"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are no other changes required to get There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've to prove it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically all changes to the model structs here should not happen. That means we need to filter out somehow primary keys for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1ddf43f#diff-d2705466bf65af436f86cbf11c0635a8R133 makes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a0dc070 filters primary keys explicitly |
||
postgres = ["diesel/postgres"] | ||
sqlite = ["diesel/sqlite"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,6 +85,9 @@ mod tests { | |
#[cfg(feature = "postgres")] | ||
const BACKEND: &str = "postgres"; | ||
|
||
#[cfg(feature = "mysql")] | ||
const BACKEND: &str = "mysql"; | ||
|
||
#[cfg(feature = "sqlite")] | ||
const BACKEND: &str = "sqlite"; | ||
|
||
|
@@ -125,6 +128,36 @@ mod tests { | |
);"#, | ||
]; | ||
|
||
#[cfg(feature = "mysql")] | ||
const MIGRATION: &[&str] = &[ | ||
r#"DROP TABLE IF EXISTS comments;"#, | ||
r#"DROP TABLE IF EXISTS posts;"#, | ||
r#"DROP TABLE IF EXISTS users;"#, | ||
r#"CREATE TABLE users( | ||
id INTEGER NOT NULL AUTO_INCREMENT, | ||
name TEXT NOT NULL, | ||
PRIMARY KEY (`id`) | ||
);"#, | ||
r#"CREATE TABLE posts( | ||
id INTEGER NOT NULL AUTO_INCREMENT, | ||
author INTEGER DEFAULT NULL, | ||
title TEXT NOT NULL, | ||
datetime TIMESTAMP NULL DEFAULT NULL, | ||
content TEXT, | ||
PRIMARY KEY (`id`), | ||
FOREIGN KEY (`author`) REFERENCES `users` (`id`) | ||
);"#, | ||
r#"CREATE TABLE comments( | ||
id INTEGER NOT NULL AUTO_INCREMENT, | ||
post INTEGER DEFAULT NULL, | ||
commenter INTEGER DEFAULT NULL, | ||
content TEXT NOT NULL, | ||
PRIMARY KEY (`id`), | ||
FOREIGN KEY (`post`) REFERENCES `posts` (`id`), | ||
FOREIGN KEY (`commenter`) REFERENCES `users` (`id`) | ||
);"#, | ||
]; | ||
|
||
fn setup_simple_schema(conn: &InferConnection) { | ||
use diesel::prelude::*; | ||
use diesel::sql_query; | ||
|
@@ -141,6 +174,12 @@ mod tests { | |
sql_query(*m).execute(conn).unwrap(); | ||
} | ||
} | ||
#[cfg(feature = "mysql")] | ||
InferConnection::Mysql(conn) => { | ||
for m in MIGRATION { | ||
sql_query(*m).execute(conn).unwrap(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
@@ -153,7 +192,7 @@ mod tests { | |
|
||
#[cfg(feature = "postgres")] | ||
print(&conn, Some("infer_test"), &mut out).unwrap(); | ||
#[cfg(feature = "sqlite")] | ||
#[cfg(any(feature = "mysql", feature = "sqlite"))] | ||
print(&conn, None, &mut out).unwrap(); | ||
|
||
let s = String::from_utf8(out).unwrap(); | ||
|
@@ -187,7 +226,7 @@ mod tests { | |
let mut api_file = File::create(api).unwrap(); | ||
#[cfg(feature = "postgres")] | ||
print(&conn, Some("infer_test"), &mut api_file).unwrap(); | ||
#[cfg(feature = "sqlite")] | ||
#[cfg(any(feature = "mysql", feature = "sqlite"))] | ||
print(&conn, None, &mut api_file).unwrap(); | ||
|
||
let main = tmp_dir | ||
|
@@ -224,6 +263,17 @@ mod tests { | |
) | ||
.unwrap(); | ||
|
||
#[cfg(feature = "mysql")] | ||
write!( | ||
main_file, | ||
include_str!("template_main.rs"), | ||
conn = "MysqlConnection", | ||
db_url = std::env::var("DATABASE_URL").unwrap(), | ||
migrations = migrations, | ||
listen_url = listen_url | ||
) | ||
.unwrap(); | ||
|
||
let cargo_toml = tmp_dir.path().join("wundergraph_roundtrip_test/Cargo.toml"); | ||
let mut cargo_toml_file = std::fs::OpenOptions::new() | ||
.write(true) | ||
|
@@ -269,6 +319,21 @@ mod tests { | |
) | ||
.unwrap(); | ||
} | ||
#[cfg(feature = "mysql")] | ||
{ | ||
writeln!( | ||
cargo_toml_file, | ||
r#"diesel = {{version = "1.4", features = ["mysql", "chrono"]}}"# | ||
) | ||
.unwrap(); | ||
|
||
writeln!( | ||
cargo_toml_file, | ||
"wundergraph = {{path = \"{}\", features = [\"mysql\", \"chrono\"] }}", | ||
wundergraph_dir | ||
) | ||
.unwrap(); | ||
} | ||
writeln!(cargo_toml_file, r#"juniper = "0.14""#).unwrap(); | ||
writeln!(cargo_toml_file, r#"failure = "0.1""#).unwrap(); | ||
writeln!(cargo_toml_file, r#"actix-web = "1""#).unwrap(); | ||
|
@@ -316,7 +381,7 @@ mod tests { | |
println!("Started server"); | ||
|
||
let client = reqwest::Client::new(); | ||
std::thread::sleep(std::time::Duration::from_secs(1)); | ||
std::thread::sleep(std::time::Duration::from_secs(5)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 5 secs was used to run mysql tests locally. But since ci postgres test failed for same reason, I increased the sleep duration globally. |
||
|
||
let query = "{\"query\": \"{ Users { id name } } \"}"; | ||
let mutation = r#"{"query":"mutation CreateUser {\n CreateUser(NewUser: {name: \"Max\"}) {\n id\n name\n }\n}","variables":null,"operationName":"CreateUser"}"#; | ||
|
Uh oh!
There was an error while loading. Please reload this page.