Skip to content

Commit 56365ad

Browse files
committed
Add #into_boxed to all queries, for conditional changes
This is the first pass of what will be a much larger feature. The intention is to support cases such as this: ```rust let mut source = users::table.into_boxed(); if let Some(search) = params.search { source = source.filter(name.like(format!("%{}%", search))); } if let Some(order) = column_with_name(params.order) { source = source.order(order.desc()); } let users = source.load(&connection); ``` Right now, this is basically impossible, as `source` needs to have the same type, and each query in diesel has a unique type. This *will* result in a slowdown of the query builder when used (though I think we can still work to eliminate this cost through prepared statement caching). However, this will not affect the performance of anything that isn't using it. As of this commit, we do not achieve the above use case, as `BoxedSelectStatement` doesn't implement any of our query DSLs. The intent is to have it implement all of them, but this felt like a good place to cut a first pass. This will fail to compile due to rust-lang/rust#32222. We can resolve this by pointing at `nightly-2016-03-05` if needed, but hopefully that can be resolved upstream in the next day or two.
1 parent 9084bb6 commit 56365ad

File tree

8 files changed

+185
-6
lines changed

8 files changed

+185
-6
lines changed

diesel/src/query_builder/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub mod insert_statement;
1818
pub mod update_statement;
1919

2020
#[doc(hidden)]
21-
pub use self::select_statement::SelectStatement;
21+
pub use self::select_statement::{SelectStatement, BoxedSelectStatement};
2222
#[doc(inline)]
2323
pub use self::update_statement::{IncompleteUpdateStatement, AsChangeset, Changeset, UpdateTarget};
2424
#[doc(inline)]
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use std::marker::PhantomData;
2+
3+
use backend::Backend;
4+
use query_builder::*;
5+
use query_source::QuerySource;
6+
use types::HasSqlType;
7+
8+
pub struct BoxedSelectStatement<ST, QS, DB> {
9+
select: Box<QueryFragment<DB>>,
10+
from: QS,
11+
where_clause: Box<QueryFragment<DB>>,
12+
order: Box<QueryFragment<DB>>,
13+
limit: Box<QueryFragment<DB>>,
14+
offset: Box<QueryFragment<DB>>,
15+
_marker: PhantomData<(ST, DB)>,
16+
}
17+
18+
impl<ST, QS, DB> BoxedSelectStatement<ST, QS, DB> {
19+
pub fn new(
20+
select: Box<QueryFragment<DB>>,
21+
from: QS,
22+
where_clause: Box<QueryFragment<DB>>,
23+
order: Box<QueryFragment<DB>>,
24+
limit: Box<QueryFragment<DB>>,
25+
offset: Box<QueryFragment<DB>>,
26+
) -> Self {
27+
BoxedSelectStatement {
28+
select: select,
29+
from: from,
30+
where_clause: where_clause,
31+
order: order,
32+
limit: limit,
33+
offset: offset,
34+
_marker: PhantomData,
35+
}
36+
}
37+
}
38+
39+
impl<ST, QS, DB> Query for BoxedSelectStatement<ST, QS, DB> where
40+
DB: Backend,
41+
DB: HasSqlType<ST>,
42+
{
43+
type SqlType = ST;
44+
}
45+
46+
impl<ST, QS, DB> QueryFragment<DB> for BoxedSelectStatement<ST, QS, DB> where
47+
DB: Backend,
48+
QS: QuerySource,
49+
QS::FromClause: QueryFragment<DB>,
50+
{
51+
fn to_sql(&self, out: &mut DB::QueryBuilder) -> BuildQueryResult {
52+
out.push_sql("SELECT ");
53+
try!(self.select.to_sql(out));
54+
out.push_sql(" FROM ");
55+
try!(self.from.from_clause().to_sql(out));
56+
try!(self.where_clause.to_sql(out));
57+
try!(self.order.to_sql(out));
58+
try!(self.limit.to_sql(out));
59+
try!(self.offset.to_sql(out));
60+
Ok(())
61+
}
62+
}

diesel/src/query_builder/select_statement/dsl_impls.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
use backend::Backend;
12
use expression::*;
23
use expression::aliased::Aliased;
3-
use query_builder::{Query, SelectStatement};
44
use query_builder::group_by_clause::*;
55
use query_builder::limit_clause::*;
66
use query_builder::offset_clause::*;
77
use query_builder::order_clause::*;
88
use query_builder::where_clause::*;
9+
use query_builder::{Query, QueryFragment, SelectStatement};
910
use query_dsl::*;
11+
use super::BoxedSelectStatement;
1012
use types::{self, Bool};
1113

1214
impl<ST, S, F, W, O, L, Of, G, Selection, Type> SelectDsl<Selection, Type>
@@ -106,3 +108,26 @@ for SelectStatement<ST, S, F, W, O, L, Of, G> where
106108
self.order, self.limit, self.offset, group_by)
107109
}
108110
}
111+
112+
impl<ST, S, F, W, O, L, Of, G, DB> BoxedDsl<DB>
113+
for SelectStatement<ST, S, F, W, O, L, Of, G> where
114+
DB: Backend,
115+
S: QueryFragment<DB> + 'static,
116+
W: QueryFragment<DB> + 'static,
117+
O: QueryFragment<DB> + 'static,
118+
L: QueryFragment<DB> + 'static,
119+
Of: QueryFragment<DB> + 'static,
120+
{
121+
type Output = BoxedSelectStatement<ST, F, DB>;
122+
123+
fn into_boxed(self) -> Self::Output {
124+
BoxedSelectStatement::new(
125+
Box::new(self.select),
126+
self.from,
127+
Box::new(self.where_clause),
128+
Box::new(self.order),
129+
Box::new(self.limit),
130+
Box::new(self.offset),
131+
)
132+
}
133+
}

diesel/src/query_builder/select_statement/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
mod dsl_impls;
2+
mod boxed;
3+
4+
pub use self::boxed::BoxedSelectStatement;
5+
6+
use std::marker::PhantomData;
27

38
use backend::Backend;
49
use expression::*;
510
use query_source::*;
6-
use std::marker::PhantomData;
7-
use super::{Query, QueryBuilder, QueryFragment, BuildQueryResult};
811
use super::group_by_clause::NoGroupByClause;
912
use super::limit_clause::NoLimitClause;
1013
use super::offset_clause::NoOffsetClause;
1114
use super::order_clause::NoOrderClause;
1215
use super::where_clause::NoWhereClause;
16+
use super::{Query, QueryBuilder, QueryFragment, BuildQueryResult};
1317

1418
#[derive(Debug, Clone, Copy)]
1519
#[doc(hidden)]

diesel/src/query_dsl/boxed_dsl.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use backend::Backend;
2+
use query_builder::AsQuery;
3+
use query_source::QuerySource;
4+
5+
pub trait BoxedDsl<DB: Backend> {
6+
type Output;
7+
8+
fn into_boxed(self) -> Self::Output;
9+
}
10+
11+
impl<T, DB> BoxedDsl<DB> for T where
12+
DB: Backend,
13+
T: QuerySource + AsQuery,
14+
T::Query: BoxedDsl<DB>,
15+
{
16+
type Output = <T::Query as BoxedDsl<DB>>::Output;
17+
18+
fn into_boxed(self) -> Self::Output {
19+
self.as_query().into_boxed()
20+
}
21+
}

diesel/src/query_dsl/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod belonging_to_dsl;
2+
mod boxed_dsl;
23
mod count_dsl;
34
mod group_by_dsl;
45
#[doc(hidden)]
@@ -15,6 +16,7 @@ mod order_dsl;
1516
mod with_dsl;
1617

1718
pub use self::belonging_to_dsl::BelongingToDsl;
19+
pub use self::boxed_dsl::BoxedDsl;
1820
pub use self::count_dsl::CountDsl;
1921
pub use self::filter_dsl::{FilterDsl, FindDsl};
2022
#[doc(hidden)]

diesel_tests/tests/boxed_queries.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use super::schema::*;
2+
use diesel::*;
3+
use diesel::query_builder::BoxedSelectStatement;
4+
5+
#[test]
6+
fn boxed_queries_can_be_executed() {
7+
let connection = connection_with_sean_and_tess_in_users_table();
8+
insert(&NewUser::new("Jim", None)).into(users::table)
9+
.execute(&connection).unwrap();
10+
let query_which_fails_unless_all_segments_are_applied =
11+
users::table
12+
.select(users::name)
13+
.filter(users::name.ne("jim"))
14+
.order(users::name.desc())
15+
.limit(1)
16+
.offset(1)
17+
.into_boxed();
18+
19+
let expected_data = vec!["Sean".to_string()];
20+
let data = query_which_fails_unless_all_segments_are_applied.load(&connection);
21+
assert_eq!(Ok(expected_data), data);
22+
}
23+
24+
#[test]
25+
fn boxed_queries_can_differ_conditionally() {
26+
let connection = connection_with_sean_and_tess_in_users_table();
27+
insert(&NewUser::new("Jim", None)).into(users::table)
28+
.execute(&connection).unwrap();
29+
30+
enum Query { All, Ordered, One };
31+
fn source(query: Query)
32+
-> BoxedSelectStatement<users::SqlType, users::table, TestBackend>
33+
{
34+
match query {
35+
Query::All => users::table.into_boxed(),
36+
Query::Ordered =>
37+
users::table
38+
.order(users::name.desc())
39+
.into_boxed(),
40+
Query::One =>
41+
users::table
42+
.filter(users::name.ne("jim"))
43+
.order(users::name.desc())
44+
.limit(1)
45+
.offset(1)
46+
.into_boxed(),
47+
}
48+
}
49+
let sean = find_user_by_name("Sean", &connection);
50+
let tess = find_user_by_name("Tess", &connection);
51+
let jim = find_user_by_name("Jim", &connection);
52+
53+
let all = source(Query::All).load(&connection);
54+
let expected_data = vec![sean.clone(), tess.clone(), jim.clone()];
55+
assert_eq!(Ok(expected_data), all);
56+
57+
let ordered = source(Query::Ordered).load(&connection);
58+
let expected_data = vec![tess.clone(), sean.clone(), jim.clone()];
59+
assert_eq!(Ok(expected_data), ordered);
60+
61+
let one = source(Query::One).load(&connection);
62+
let expected_data = vec![sean.clone()];
63+
assert_eq!(Ok(expected_data), one);
64+
}

diesel_tests/tests/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ include!("lib.in.rs");
1111
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
1212

1313
mod associations;
14-
mod expressions;
14+
mod boxed_queries;
1515
mod connection;
16+
mod debug;
17+
mod expressions;
1618
mod filter;
1719
mod filter_operators;
1820
mod find;
@@ -27,4 +29,3 @@ mod select;
2729
mod transactions;
2830
mod types;
2931
mod types_roundtrip;
30-
mod debug;

0 commit comments

Comments
 (0)