diesel_order_with_direction/
lib.rs

1//! # Diesel dynamic query ordering
2//! This crate provides the [`OrderWithDirectionDsl`] trait for dynamic query ordering;
3//! ```
4//! #[macro_use] extern crate diesel;
5//! use diesel::mysql::Mysql;
6//! use diesel::QueryDsl;
7//! use diesel_order_with_direction::{OrderWithDirectionDsl, QueryOrderDirection};
8//! table! {
9//!     posts (id) {
10//!         id -> Integer,
11//!     }
12//! }
13//!
14//! let direction = QueryOrderDirection::Descending;
15//!
16//! let query = posts::table
17//!     .into_boxed::<Mysql>()
18//!     .order_with_dir(direction, posts::id)
19//!     .limit(5);
20//!
21//! let query = posts::table
22//!     .into_boxed::<Mysql>()
23//!     .then_order_by_with_dir(direction, posts::id)
24//!     .limit(5);
25//! ```
26
27use diesel::dsl::{Asc, Desc};
28use diesel::query_dsl::methods::{OrderDsl, ThenOrderDsl};
29use diesel::ExpressionMethods;
30use std::fmt;
31
32/// The direction of the query ordering.
33///
34/// Implements [`Display`](fmt::Display)
35///
36/// ```
37/// # use diesel_order_with_direction::QueryOrderDirection;
38/// assert_eq!(&QueryOrderDirection::Ascending.to_string(), "ASC");
39/// assert_eq!(&QueryOrderDirection::Descending.to_string(), "DESC");
40/// ```
41#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
42pub enum QueryOrderDirection {
43    Ascending,
44    Descending,
45}
46
47impl Default for QueryOrderDirection {
48    fn default() -> Self {
49        Self::Ascending
50    }
51}
52
53impl fmt::Display for QueryOrderDirection {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            Self::Ascending => write!(f, "ASC"),
57            Self::Descending => write!(f, "DESC"),
58        }
59    }
60}
61
62impl QueryOrderDirection {
63    /// Append a [`QueryDsl::order`](diesel::QueryDsl::order) to the query. Same as
64    /// [`query.order_with_dir(self, expr)`](OrderWithDirectionDsl::order_with_dir)
65    pub fn order_query<Expr, Q>(&self, query: Q, expr: Expr) -> OrderWithDirection<Q, Expr>
66    where
67        Expr: ExpressionMethods,
68        Q: OrderDsl<Asc<Expr>> + OrderDsl<Desc<Expr>, Output = <Q as OrderDsl<Asc<Expr>>>::Output>,
69    {
70        match self {
71            Self::Ascending => query.order(expr.asc()),
72            Self::Descending => query.order(expr.desc()),
73        }
74    }
75
76    pub fn order_multiple<Expr, Q>(
77        &self,
78        query: Q,
79        expr: Expr,
80    ) -> OrderMultipleWithDirection<Q, Expr>
81    where
82        Expr: OrderableTuple,
83        Q: OrderDsl<<Expr as OrderableTuple>::AscOutput>
84            + OrderDsl<
85                <Expr as OrderableTuple>::DescOutput,
86                Output = <Q as OrderDsl<<Expr as OrderableTuple>::AscOutput>>::Output,
87            >,
88    {
89        match self {
90            Self::Ascending => query.order(OrderableTuple::asc(expr)),
91            Self::Descending => query.order(OrderableTuple::desc(expr)),
92        }
93    }
94
95    /// Append a [`QueryDsl::then_order_by`](diesel::QueryDsl::then_order_by) to the query. Same as
96    /// [`query.then_order_by_with_dir(self, expr)`](OrderWithDirectionDsl::then_order_by_with_dir)
97    pub fn then_order_by_query<Expr, Q>(
98        &self,
99        query: Q,
100        expr: Expr,
101    ) -> ThenOrderByWithDirection<Q, Expr>
102    where
103        Expr: ExpressionMethods,
104        Q: ThenOrderDsl<Asc<Expr>>
105            + ThenOrderDsl<Desc<Expr>, Output = <Q as ThenOrderDsl<Asc<Expr>>>::Output>,
106    {
107        match self {
108            Self::Ascending => query.then_order_by(expr.asc()),
109            Self::Descending => query.then_order_by(expr.desc()),
110        }
111    }
112}
113
114/// Represent the return type of [`.order_with_dir`](`OrderWithDirectionDsl::order_with_dir`)
115pub type OrderWithDirection<Q, Expr> = <Q as OrderDsl<Asc<Expr>>>::Output;
116/// Represent the return type of [`.order_multiple_with_dir`](`OrderWithDirectionDsl::order_with_dir`)
117pub type OrderMultipleWithDirection<Q, Expr> =
118    <Q as OrderDsl<<Expr as OrderableTuple>::AscOutput>>::Output;
119/// Represent the return type of [`.then_order_by_with_dir`](`OrderWithDirectionDsl::then_order_by_with_dir`)
120pub type ThenOrderByWithDirection<Q, Expr> = <Q as ThenOrderDsl<Asc<Expr>>>::Output;
121
122pub trait OrderWithDirectionDsl: Sized {
123    /// Wrapper around [`QueryDsl::order`](diesel::QueryDsl::order) with an extra [`QueryOrderDirection`] argument to
124    /// dynamically determine the ordering direction. Only works with boxed queries.
125    fn order_with_dir<Expr>(
126        self,
127        direction: QueryOrderDirection,
128        expr: Expr,
129    ) -> OrderWithDirection<Self, Expr>
130    where
131        Expr: ExpressionMethods,
132        Self: OrderDsl<Asc<Expr>>
133            + OrderDsl<Desc<Expr>, Output = <Self as OrderDsl<Asc<Expr>>>::Output>,
134    {
135        direction.order_query(self, expr)
136    }
137
138    /// Alias for `DynamicQueryOrderDsl::order_with_dir`
139    fn order_by_with_dir<Expr>(
140        self,
141        order: QueryOrderDirection,
142        expr: Expr,
143    ) -> OrderWithDirection<Self, Expr>
144    where
145        Expr: ExpressionMethods,
146        Self: OrderDsl<Asc<Expr>>
147            + OrderDsl<Desc<Expr>, Output = <Self as OrderDsl<Asc<Expr>>>::Output>,
148    {
149        self.order_with_dir(order, expr)
150    }
151
152    /// Wrapper around [`QueryDsl::order`](diesel::QueryDsl::order) when its `expr` is a tuple. The `direction` argument determines the ordering direction of all fields in the tuple
153    /// ```
154    /// # #[macro_use] extern crate diesel;
155    /// # use diesel::mysql::Mysql;
156    /// # use diesel::{QueryDsl, ExpressionMethods};
157    /// # use diesel_order_with_direction::{OrderWithDirectionDsl, QueryOrderDirection};
158    /// # table! {
159    /// #     people (id) {
160    /// #         id -> Integer,
161    /// #         age -> Integer,
162    /// #         height -> Integer,
163    /// #     }
164    /// # }
165    /// // These two do the same
166    /// people::table
167    ///     .into_boxed()
168    ///     .order((people::height.asc(), people::age.asc()));
169    /// people::table
170    ///     .into_boxed()
171    ///     .order_multiple_with_dir(
172    ///         QueryOrderDirection::Ascending,
173    ///         (people::height, people::age),
174    ///      );
175    /// ```
176    /// dynamically determine the ordering direction. Only works with boxed queries.
177    fn order_multiple_with_dir<Expr>(
178        self,
179        direction: QueryOrderDirection,
180        expr: Expr,
181    ) -> OrderMultipleWithDirection<Self, Expr>
182    where
183        Expr: OrderableTuple,
184        Self: OrderDsl<<Expr as OrderableTuple>::AscOutput>
185            + OrderDsl<
186                <Expr as OrderableTuple>::DescOutput,
187                Output = <Self as OrderDsl<<Expr as OrderableTuple>::AscOutput>>::Output,
188            >,
189    {
190        direction.order_multiple(self, expr)
191    }
192
193    /// Wrapper around [`QueryDsl::then_order_by`](diesel::QueryDsl::then_order_by) with an extra [`QueryOrderDirection`] argument to
194    /// dynamically determine the ordering direction. Only works with boxed queries.
195    fn then_order_by_with_dir<Expr>(
196        self,
197        direction: QueryOrderDirection,
198        expr: Expr,
199    ) -> ThenOrderByWithDirection<Self, Expr>
200    where
201        Expr: ExpressionMethods,
202        Self: ThenOrderDsl<Asc<Expr>>
203            + ThenOrderDsl<Desc<Expr>, Output = <Self as ThenOrderDsl<Asc<Expr>>>::Output>,
204    {
205        direction.then_order_by_query(self, expr)
206    }
207}
208
209impl<Q> OrderWithDirectionDsl for Q {}
210
211pub trait OrderableTuple {
212    type AscOutput: diesel::Expression;
213    type DescOutput: diesel::Expression;
214    fn asc(self) -> Self::AscOutput;
215    fn desc(self) -> Self::DescOutput;
216}
217
218macro_rules! impl_orderabletuple {
219    ($($G:ident),+) => {
220        impl<$($G: ExpressionMethods,)*> OrderableTuple for ($($G,)*) {
221            type AscOutput = ($(Asc<$G>,)*);
222            type DescOutput = ($(Desc<$G>,)*);
223            fn asc(self) -> Self::AscOutput {
224                #[allow(non_snake_case)]
225                let ($($G,)*) = self;
226                ($($G.asc(),)*)
227            }
228            fn desc(self) -> Self::DescOutput {
229                #[allow(non_snake_case)]
230                let ($($G,)*) = self;
231                ($($G.desc(),)*)
232            }
233        }
234    }
235}
236
237impl_orderabletuple! {A1}
238impl_orderabletuple! {A1,A2}
239impl_orderabletuple! {A1,A2,A3}
240impl_orderabletuple! {A1,A2,A3,A4}
241impl_orderabletuple! {A1,A2,A3,A4,A5}
242impl_orderabletuple! {A1,A2,A3,A4,A5,A6}
243impl_orderabletuple! {A1,A2,A3,A4,A5,A6,A7}
244impl_orderabletuple! {A1,A2,A3,A4,A5,A6,A7,A8}
245impl_orderabletuple! {A1,A2,A3,A4,A5,A6,A7,A8,A9}
246impl_orderabletuple! {A1,A2,A3,A4,A5,A6,A7,A8,A9,A10}
247impl_orderabletuple! {A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11}
248impl_orderabletuple! {A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12}
249impl_orderabletuple! {A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13}
250impl_orderabletuple! {A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13,A14}
251impl_orderabletuple! {A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13,A14,A15}