Skip to content

Commit 1fc9168

Browse files
committed
Make our query builder faster than a SQL string (diesel-rs#283)
We've added a new trait, `QueryId`, which must be implemented for all AST nodes, and optionally returns a type which conforms to `std::any::Any`. Nodes which have their SQL change without the type changing (such as boxed nodes) will return `None` here. If we're able to uniquely identify the query on the type alone, we use that for the cache key rather than the SQL string. This allows us to skip the query construction entirely. Additionally, a type identifier can be used for a faster hash lookup than a string could, allowing us to actually beat the string literal case. My intent is that all types which implement `QueryFragment` also implement `QueryId`. However, we can't make it a supertrait, as that would require specifying the `QueryId` associated type when creating a boxed trait object, which defeats the purpose entirely. Ironcially, we actually implement `QueryId` for the trait object directly, so in theory we should be able to separate them. I don't know if there's a way to actually do this, though. There's a handful of types which implement the "noop" form of `TypeId` that could potentially be safe to implement for real. The main one that comes to mind is `Aliased`. While the SQL from its implementation of `QueryFragment` implies that the SQL can change without the type, I think the type encapsulates enough information. It's a niche feature though so I erred on the side of caution. Here are the benchmark results: ``` name old ns/iter new ns/iter diff ns/iter diff % pg::connection::tests::benchmarks::prepared_statement_lookup_query_builder 1,048 191 -857 -81.77% pg::connection::tests::benchmarks::prepared_statement_lookup_raw_sql 319 318 -1 -0.31% sqlite::connection::tests::benchmarks::prepared_statement_lookup_query_builder 743 123 -620 -83.45% sqlite::connection::tests::benchmarks::prepared_statement_lookup_raw_sql 183 180 -3 -1.64% ``` These benchmarks measured creating/fetch the prepared statement, without executing the query (calling `prepare_query` on both backends). I opted to benchmark this as the inherent noise of a database round trip on PG made it difficult to demonstrate the difference. The benchmarks are not included in this PR as the code is messy and these aren't benchmarks that I care to keep in the long term. We could potentially gain a little bit more performance here. A type identifier is a u64 internally, so can be used with a perfect hashing function. This would change the lookup to be constant time. I believe this could save us another 100 ns or so, bringing the query builder close to 0.
1 parent 2125aaf commit 1fc9168

43 files changed

Lines changed: 473 additions & 60 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

diesel/src/connection/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
extern crate libc;
22

33
use backend::Backend;
4-
use query_builder::{AsQuery, QueryFragment};
4+
use query_builder::{AsQuery, QueryFragment, QueryId};
55
use query_source::Queryable;
66
use result::*;
77
use types::HasSqlType;
@@ -69,7 +69,7 @@ pub trait Connection: SimpleConnection + Sized {
6969
#[doc(hidden)]
7070
fn query_one<T, U>(&self, source: T) -> QueryResult<U> where
7171
T: AsQuery,
72-
T::Query: QueryFragment<Self::Backend>,
72+
T::Query: QueryFragment<Self::Backend> + QueryId,
7373
Self::Backend: HasSqlType<T::SqlType>,
7474
U: Queryable<T::SqlType, Self::Backend>,
7575
{
@@ -80,13 +80,13 @@ pub trait Connection: SimpleConnection + Sized {
8080
#[doc(hidden)]
8181
fn query_all<T, U>(&self, source: T) -> QueryResult<Vec<U>> where
8282
T: AsQuery,
83-
T::Query: QueryFragment<Self::Backend>,
83+
T::Query: QueryFragment<Self::Backend> + QueryId,
8484
Self::Backend: HasSqlType<T::SqlType>,
8585
U: Queryable<T::SqlType, Self::Backend>;
8686

8787
#[doc(hidden)]
8888
fn execute_returning_count<T>(&self, source: &T) -> QueryResult<usize> where
89-
T: QueryFragment<Self::Backend>;
89+
T: QueryFragment<Self::Backend> + QueryId;
9090

9191
#[doc(hidden)] fn silence_notices<F: FnOnce() -> T, T>(&self, f: F) -> T;
9292
#[doc(hidden)] fn begin_transaction(&self) -> QueryResult<()>;

diesel/src/expression/aliased.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ impl<'a, T, DB> QueryFragment<DB> for Aliased<'a, T> where
4545
}
4646
}
4747

48+
impl<'a, T> QueryId for Aliased<'a, T> {
49+
type QueryId = ();
50+
51+
fn has_static_query_id() -> bool {
52+
false
53+
}
54+
}
55+
4856
// FIXME This is incorrect, should only be selectable from WithQuerySource
4957
impl<'a, T, QS> SelectableExpression<QS> for Aliased<'a, T> where
5058
Aliased<'a, T>: Expression,

diesel/src/expression/array_comparison.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ impl<T, U, DB> QueryFragment<DB> for In<T, U> where
6262
}
6363
}
6464

65+
impl_query_id!(In<T, U>);
66+
6567
use std::marker::PhantomData;
6668
use query_builder::SelectStatement;
6769

@@ -132,6 +134,8 @@ impl<T, DB> QueryFragment<DB> for Many<T> where
132134
}
133135
}
134136

137+
impl_query_id!(noop: Many<T>);
138+
135139
pub struct Subselect<T, ST> {
136140
values: T,
137141
_sql_type: PhantomData<ST>,
@@ -163,3 +167,5 @@ impl<T, ST, DB> QueryFragment<DB> for Subselect<T, ST> where
163167
self.values.is_safe_to_cache_prepared()
164168
}
165169
}
170+
171+
impl_query_id!(Subselect<T, ST>);

diesel/src/expression/bound.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ impl<T, U, DB> QueryFragment<DB> for Bound<T, U> where
5555
}
5656
}
5757

58+
impl<T: QueryId, U> QueryId for Bound<T, U> {
59+
type QueryId = Bound<T::QueryId, ()>;
60+
61+
fn has_static_query_id() -> bool {
62+
T::has_static_query_id()
63+
}
64+
}
65+
5866
impl<T, U, QS> SelectableExpression<QS> for Bound<T, U> where
5967
Bound<T, U>: Expression,
6068
{

diesel/src/expression/count.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ impl<T: QueryFragment<DB>, DB: Backend> QueryFragment<DB> for Count<T> {
7474
}
7575
}
7676

77+
impl_query_id!(Count<T>);
78+
7779
impl<T: Expression, QS> SelectableExpression<QS> for Count<T> {
7880
}
7981

@@ -102,3 +104,5 @@ impl<DB: Backend> QueryFragment<DB> for CountStar {
102104

103105
impl<QS> SelectableExpression<QS> for CountStar {
104106
}
107+
108+
impl_query_id!(CountStar);

diesel/src/expression/functions/aggregate_folding.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ macro_rules! fold_function {
4949
}
5050
}
5151

52+
impl_query_id!($type_name<T>);
53+
5254
impl<ST, T, QS> SelectableExpression<QS> for $type_name<T> where
5355
ST: Foldable,
5456
T: Expression<SqlType=ST>,

diesel/src/expression/functions/aggregate_ordering.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ macro_rules! ord_function {
4646
}
4747
}
4848

49+
impl_query_id!($type_name<T>);
50+
4951
impl<T: Expression, QS> SelectableExpression<QS> for $type_name<T> {
5052
}
5153
}

diesel/src/expression/functions/date_and_time.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ impl<DB: Backend> QueryFragment<DB> for now {
3434
}
3535
}
3636

37+
impl_query_id!(now);
38+
3739
operator_allowed!(now, Add, add);
3840
operator_allowed!(now, Sub, sub);
3941
sql_function!(date, date_t, (x: Timestamp) -> Date,

diesel/src/expression/functions/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ macro_rules! sql_function_body {
5858
}
5959
}
6060

61+
impl_query_id!($struct_name<$($arg_name),+>);
62+
6163
#[allow(non_camel_case_types)]
6264
impl<$($arg_name),*, QS> $crate::expression::SelectableExpression<QS> for $struct_name<$($arg_name),*> where
6365
$($arg_name: $crate::expression::SelectableExpression<QS>,)*
@@ -134,6 +136,8 @@ macro_rules! no_arg_sql_function_body_except_to_sql {
134136

135137
impl $crate::expression::NonAggregate for $type_name {
136138
}
139+
140+
impl_query_id!($type_name);
137141
}
138142
}
139143

diesel/src/expression/grouped.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ impl<T: QueryFragment<DB>, DB: Backend> QueryFragment<DB> for Grouped<T> {
2727
}
2828
}
2929

30+
impl_query_id!(Grouped<T>);
31+
3032
impl<T, QS> SelectableExpression<QS> for Grouped<T> where
3133
T: SelectableExpression<QS>,
3234
Grouped<T>: Expression,

0 commit comments

Comments
 (0)