Skip to content

Commit 90b46fd

Browse files
committed
Add .then_order_by, which appends rather than replaces
I was originally only going to implement this for boxed queries, but I don't think there's any reason we need to do that here. My original reasoning was I didn't want to write code that appended tuples, but there's actually no difference in behavior between `.order((foo, bar, baz))` and `.order(((foo, bar), baz))` (which is the type we get from this). Fixes diesel-rs#1311.
1 parent 5669551 commit 90b46fd

7 files changed

Lines changed: 185 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
3636
* Tables with more than 56 columns are now supported by enabling the
3737
`128-column-tables` feature.
3838

39+
* Added `order_by` as an alias for `order`.
40+
41+
* Added `then_order_by`, which appends to an `ORDER BY` clause rather than
42+
replacing it. This is useful with boxed queries to dynamically construct an
43+
order by clause containing an unknown number of columns.
44+
3945
### Changed
4046

4147
* The bounds on `impl ToSql for Cow<'a, T>` have been loosened to no longer

diesel/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ pub mod helper_types {
220220
/// Represents the return type of `.order(ordering)`
221221
pub type Order<Source, Ordering> = <Source as OrderDsl<Ordering>>::Output;
222222

223+
/// Represents the return type of `.then_order_by(ordering)`
224+
pub type ThenOrderBy<Source, Ordering> = <Source as ThenOrderDsl<Ordering>>::Output;
225+
223226
/// Represents the return type of `.limit()`
224227
pub type Limit<Source> = <Source as LimitDsl>::Output;
225228

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,20 @@
11
simple_clause!(NoOrderClause, OrderClause, " ORDER BY ");
2+
3+
impl<'a, DB, Expr> Into<Option<Box<QueryFragment<DB> + 'a>>> for OrderClause<Expr>
4+
where
5+
DB: Backend,
6+
Expr: QueryFragment<DB> + 'a,
7+
{
8+
fn into(self) -> Option<Box<QueryFragment<DB> + 'a>> {
9+
Some(Box::new(self.0))
10+
}
11+
}
12+
13+
impl<'a, DB> Into<Option<Box<QueryFragment<DB> + 'a>>> for NoOrderClause
14+
where
15+
DB: Backend,
16+
{
17+
fn into(self) -> Option<Box<QueryFragment<DB> + 'a>> {
18+
None
19+
}
20+
}

diesel/src/query_builder/select_statement/boxed.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub struct BoxedSelectStatement<'a, ST, QS, DB> {
2424
from: QS,
2525
distinct: Box<QueryFragment<DB> + 'a>,
2626
where_clause: Option<Box<QueryFragment<DB> + 'a>>,
27-
order: Box<QueryFragment<DB> + 'a>,
27+
order: Option<Box<QueryFragment<DB> + 'a>>,
2828
limit: Box<QueryFragment<DB> + 'a>,
2929
offset: Box<QueryFragment<DB> + 'a>,
3030
group_by: Box<QueryFragment<DB> + 'a>,
@@ -38,7 +38,7 @@ impl<'a, ST, QS, DB> BoxedSelectStatement<'a, ST, QS, DB> {
3838
from: QS,
3939
distinct: Box<QueryFragment<DB> + 'a>,
4040
where_clause: Option<Box<QueryFragment<DB> + 'a>>,
41-
order: Box<QueryFragment<DB> + 'a>,
41+
order: Option<Box<QueryFragment<DB> + 'a>>,
4242
limit: Box<QueryFragment<DB> + 'a>,
4343
offset: Box<QueryFragment<DB> + 'a>,
4444
group_by: Box<QueryFragment<DB> + 'a>,
@@ -96,7 +96,12 @@ where
9696
}
9797

9898
self.group_by.walk_ast(out.reborrow())?;
99-
self.order.walk_ast(out.reborrow())?;
99+
100+
if let Some(ref order) = self.order {
101+
out.push_sql(" ORDER BY ");
102+
order.walk_ast(out.reborrow())?;
103+
}
104+
100105
self.limit.walk_ast(out.reborrow())?;
101106
self.offset.walk_ast(out.reborrow())?;
102107
Ok(())
@@ -257,7 +262,23 @@ where
257262
type Output = Self;
258263

259264
fn order(mut self, order: Order) -> Self::Output {
260-
self.order = Box::new(OrderClause(order));
265+
self.order = OrderClause(order).into();
266+
self
267+
}
268+
}
269+
270+
impl<'a, ST, QS, DB, Order> ThenOrderDsl<Order> for BoxedSelectStatement<'a, ST, QS, DB>
271+
where
272+
DB: Backend + 'a,
273+
Order: QueryFragment<DB> + AppearsOnTable<QS> + 'a,
274+
{
275+
type Output = Self;
276+
277+
fn then_order_by(mut self, order: Order) -> Self::Output {
278+
self.order = match self.order {
279+
Some(old) => Some(Box::new((old, order))),
280+
None => Some(Box::new(order)),
281+
};
261282
self
262283
}
263284
}

diesel/src/query_builder/select_statement/dsl_impls.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,41 @@ where
178178
}
179179
}
180180

181+
impl<F, S, D, W, O, L, Of, G, FU, Expr> ThenOrderDsl<Expr>
182+
for SelectStatement<F, S, D, W, OrderClause<O>, L, Of, G, FU>
183+
where
184+
Expr: AppearsOnTable<F>,
185+
{
186+
type Output = SelectStatement<F, S, D, W, OrderClause<(O, Expr)>, L, Of, G, FU>;
187+
188+
fn then_order_by(self, expr: Expr) -> Self::Output {
189+
SelectStatement::new(
190+
self.select,
191+
self.from,
192+
self.distinct,
193+
self.where_clause,
194+
OrderClause((self.order.0, expr)),
195+
self.limit,
196+
self.offset,
197+
self.group_by,
198+
self.for_update,
199+
)
200+
}
201+
}
202+
203+
impl<F, S, D, W, L, Of, G, FU, Expr> ThenOrderDsl<Expr>
204+
for SelectStatement<F, S, D, W, NoOrderClause, L, Of, G, FU>
205+
where
206+
Expr: Expression,
207+
Self: OrderDsl<Expr>,
208+
{
209+
type Output = ::dsl::Order<Self, Expr>;
210+
211+
fn then_order_by(self, expr: Expr) -> Self::Output {
212+
self.order_by(expr)
213+
}
214+
}
215+
181216
#[doc(hidden)]
182217
pub type Limit = AsExprOf<i64, BigInt>;
183218

@@ -280,7 +315,7 @@ where
280315
S: QueryFragment<DB> + SelectableExpression<F> + 'a,
281316
D: QueryFragment<DB> + 'a,
282317
W: Into<Option<Box<QueryFragment<DB> + 'a>>>,
283-
O: QueryFragment<DB> + 'a,
318+
O: Into<Option<Box<QueryFragment<DB> + 'a>>>,
284319
L: QueryFragment<DB> + 'a,
285320
Of: QueryFragment<DB> + 'a,
286321
G: QueryFragment<DB> + 'a,
@@ -293,7 +328,7 @@ where
293328
self.from,
294329
Box::new(self.distinct),
295330
self.where_clause.into(),
296-
Box::new(self.order),
331+
self.order.into(),
297332
Box::new(self.limit),
298333
Box::new(self.offset),
299334
Box::new(self.group_by),
@@ -310,7 +345,7 @@ where
310345
F::DefaultSelection: QueryFragment<DB> + 'a,
311346
D: QueryFragment<DB> + 'a,
312347
W: Into<Option<Box<QueryFragment<DB> + 'a>>>,
313-
O: QueryFragment<DB> + 'a,
348+
O: Into<Option<Box<QueryFragment<DB> + 'a>>>,
314349
L: QueryFragment<DB> + 'a,
315350
Of: QueryFragment<DB> + 'a,
316351
G: QueryFragment<DB> + 'a,
@@ -323,7 +358,7 @@ where
323358
self.from,
324359
Box::new(self.distinct),
325360
self.where_clause.into(),
326-
Box::new(self.order),
361+
self.order.into(),
327362
Box::new(self.limit),
328363
Box::new(self.offset),
329364
Box::new(self.group_by),

diesel/src/query_dsl/mod.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub mod methods {
6363
pub use super::load_dsl::{ExecuteDsl, LoadQuery};
6464
pub use super::locking_dsl::ForUpdateDsl;
6565
pub use super::offset_dsl::OffsetDsl;
66-
pub use super::order_dsl::OrderDsl;
66+
pub use super::order_dsl::{OrderDsl, ThenOrderDsl};
6767
pub use super::select_dsl::SelectDsl;
6868
}
6969

@@ -562,6 +562,8 @@ pub trait QueryDsl: Sized {
562562
///
563563
/// Ordering by multiple columns can be achieved by passing a tuple of those
564564
/// columns.
565+
/// To construct an order clause of an unknown number of columns,
566+
/// see [`QueryDsl::then_order_by`](#method.then_order_by)
565567
///
566568
/// # Examples
567569
///
@@ -613,6 +615,68 @@ pub trait QueryDsl: Sized {
613615
methods::OrderDsl::order(self, expr)
614616
}
615617

618+
/// Alias for `order`
619+
fn order_by<Expr>(self, expr: Expr) -> Order<Self, Expr>
620+
where
621+
Expr: Expression,
622+
Self: methods::OrderDsl<Expr>,
623+
{
624+
QueryDsl::order(self, expr)
625+
}
626+
627+
/// Appends to the `ORDER BY` clause of this SQL query.
628+
///
629+
/// Unlike `.order`, this method will append rather than replace.
630+
/// In other words,
631+
/// `.order_by(foo).order_by(bar)` is equivalent to `.order_by(bar)`.
632+
/// In contrast,
633+
/// `.order_by(foo).then_order_by(bar)` is equivalent to `.order((foo, bar))`.
634+
/// This method is only present on boxed queries.
635+
///
636+
/// # Examples
637+
///
638+
/// ```rust
639+
/// # #[macro_use] extern crate diesel;
640+
/// # include!("../doctest_setup.rs");
641+
/// #
642+
/// # fn main() {
643+
/// # run_test();
644+
/// # }
645+
/// #
646+
/// # fn run_test() -> QueryResult<()> {
647+
/// # use schema::users::dsl::*;
648+
/// # let connection = establish_connection();
649+
/// # connection.execute("DELETE FROM users")?;
650+
/// diesel::insert_into(users)
651+
/// .values(&vec![
652+
/// name.eq("Saul"),
653+
/// name.eq("Steve"),
654+
/// name.eq("Stan"),
655+
/// name.eq("Stan"),
656+
/// ])
657+
/// .execute(&connection)?;
658+
///
659+
/// let data = users.select((name, id))
660+
/// .order_by(name.asc())
661+
/// .then_order_by(id.desc())
662+
/// .load(&connection)?;
663+
/// let expected_data = vec![
664+
/// (String::from("Saul"), 3),
665+
/// (String::from("Stan"), 6),
666+
/// (String::from("Stan"), 5),
667+
/// (String::from("Steve"), 4),
668+
/// ];
669+
/// assert_eq!(expected_data, data);
670+
/// # Ok(())
671+
/// # }
672+
/// ```
673+
fn then_order_by<Order>(self, order: Order) -> ThenOrderBy<Self, Order>
674+
where
675+
Self: methods::ThenOrderDsl<Order>,
676+
{
677+
methods::ThenOrderDsl::then_order_by(self, order)
678+
}
679+
616680
/// Sets the limit clause of the query.
617681
///
618682
/// If there was already a limit clause, it will be overridden.

diesel/src/query_dsl/order_dsl.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,31 @@ where
2828
self.as_query().order(expr)
2929
}
3030
}
31+
32+
/// The `then_order_by` method
33+
///
34+
/// This trait should not be relied on directly by most apps. Its behavior is
35+
/// provided by [`QueryDsl`]. However, you may need a where clause on this trait
36+
/// to call `then_order_by` from generic code.
37+
///
38+
/// [`QueryDsl`]: ../trait.QueryDsl.html
39+
pub trait ThenOrderDsl<Expr> {
40+
/// The type returned by `.then_order_by`.
41+
type Output;
42+
43+
/// See the trait documentation.
44+
fn then_order_by(self, expr: Expr) -> Self::Output;
45+
}
46+
47+
impl<T, Expr> ThenOrderDsl<Expr> for T
48+
where
49+
Expr: Expression,
50+
T: Table,
51+
T::Query: ThenOrderDsl<Expr>,
52+
{
53+
type Output = <T::Query as ThenOrderDsl<Expr>>::Output;
54+
55+
fn then_order_by(self, expr: Expr) -> Self::Output {
56+
self.as_query().then_order_by(expr)
57+
}
58+
}

0 commit comments

Comments
 (0)