Skip to content
4 changes: 4 additions & 0 deletions cot/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,9 @@ pub trait FromDbValue {
/// An alias for the value type internally used by the [`sea_query`] crate.
pub type DbValue = sea_query::Value;

/// An alias for the values type internally used the [`sea_query`] crate.
pub type DbValues = sea_query::Values;

/// A trait for converting a Rust value to a database value.
pub trait ToDbValue: Send + Sync {
/// Converts the Rust value to a `sea_query` value.
Expand Down Expand Up @@ -2572,6 +2575,7 @@ impl Database {
let mut select = sea_query::Query::select();
select.columns(columns_to_get).from(T::TABLE_NAME);
query.add_filter_to_statement(&mut select, executor.as_sql_query_builder())?;
query.add_order_by_to_statement(&mut select, executor.as_sql_query_builder())?;
query.add_limit_to_statement(&mut select);
query.add_offset_to_statement(&mut select);

Expand Down
1 change: 1 addition & 0 deletions cot/src/db/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ impl_db_field!(Vec<u8>, Blob);
impl_db_field!(Bytes, Blob, with Vec<u8>);

impl TextField for String {}
impl TextField for &str {}

impl ToDbValue for &str {
fn to_db_value(&self) -> DbValue {
Expand Down
43 changes: 42 additions & 1 deletion cot/src/db/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use derive_more::with_trait::Debug;
use thiserror::Error;

use crate::db;
use crate::db::query::expr::SqlQueryBuilder;
pub use crate::db::query::expr::{Expr, ExprAdd, ExprDiv, ExprMul, ExprOrd, ExprSub};
use crate::db::query::expr::{OrderByExpr, SqlQueryBuilder};
use crate::db::{Auto, DatabaseBackend, ForeignKey, Model, StatementResult, ToDbFieldValue};
const ERROR_PREFIX: &str = "expression error:";

Expand Down Expand Up @@ -47,6 +47,7 @@ pub enum QueryBuildingError {
pub struct Query<T> {
filter: Option<Expr>,
limit: Option<u64>,
order_by: Vec<OrderByExpr>,
offset: Option<u64>,
phantom_data: PhantomData<fn() -> T>,
}
Expand All @@ -56,6 +57,7 @@ impl<T> Debug for Query<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Query")
.field("filter", &self.filter)
.field("order_by", &self.order_by)
.field("limit", &self.limit)
.field("offset", &self.offset)
.field("phantom_data", &self.phantom_data)
Expand All @@ -69,6 +71,7 @@ impl<T> Clone for Query<T> {
Self {
filter: self.filter.clone(),
limit: self.limit,
order_by: self.order_by.clone(),
offset: self.offset,
phantom_data: PhantomData,
}
Expand Down Expand Up @@ -112,6 +115,7 @@ impl<T: Model> Query<T> {
Self {
filter: None,
limit: None,
order_by: Vec::new(),
offset: None,
phantom_data: PhantomData,
}
Expand Down Expand Up @@ -163,6 +167,32 @@ impl<T: Model> Query<T> {
self
}

/// Set an order for records from the query.
///
/// # Example
///
/// ```
/// use cot::db::model;
/// use cot::db::query::{Expr, Query};
///
/// #[model]
/// struct User {
/// #[model(primary_key)]
/// id: i32,
/// age: i32,
/// }
///
/// let query = Query::<User>::new().order_by(User::age, Order::Asc); // or Order::Desc
/// ```
pub fn order_by<I, O>(&mut self, order_by: I) -> &mut Self
where
O: Into<OrderByExpr>,
I: IntoIterator<Item = O>,
{
self.order_by = order_by.into_iter().map(Into::into).collect();
self
}

/// Set the offset for the query.
///
/// # Example
Expand Down Expand Up @@ -249,6 +279,17 @@ impl<T: Model> Query<T> {
}
}

pub(super) fn add_order_by_to_statement(
&self,
statement: &mut sea_query::SelectStatement,
sql_builder: &dyn SqlQueryBuilder,
) -> Result<(), QueryBuildingError> {
for order_by in &self.order_by {
order_by.add_to_statement(statement, sql_builder)?;
}
Ok(())
}

pub(super) fn add_offset_to_statement(&self, statement: &mut sea_query::SelectStatement) {
if let Some(offset) = self.offset {
statement.offset(offset);
Expand Down
69 changes: 68 additions & 1 deletion cot/src/db/query/expr.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
//! Database expressions.
pub mod like;
mod order_by;

use std::marker::PhantomData;
use std::ops::Add;

use cot::db::query::{IntoField, QueryBuildingError};
use cot::db::{DbFieldValue, DbValue, FromDbValue, Identifier, ToDbFieldValue};
use cot::db::{DbFieldValue, DbValue, FromDbValue, Identifier, LimitedString, ToDbFieldValue};
pub use like::ExprLike;
use like::{CaseSensitivity, LikeExprBuilder, LikeMode};
pub use order_by::{ExprSort, NullsOrder, OrderByExpr, SortOrder};
use sea_query::{ExprTrait, IntoColumnRef, SimpleExpr};

use crate::db::ToDbValue;
use crate::db::query::expr::order_by::OrderTarget;

/// An expression that can be used to filter, update, or delete rows.
///
/// This is used to create complex queries with multiple conditions. Typically,
Expand Down Expand Up @@ -1197,6 +1203,38 @@ impl Expr {
Self::RawLike(Box::new(lhs), Box::new(rhs), CaseSensitivity::Insensitive)
}

/// Builds an ascending `ORDER BY` term from this expression. See the
/// note on [`Query::filter`](crate::db::query::Query::filter) about
/// `Expr` not being restricted to field references — the same applies
/// here; ordering by a boolean-producing expression is legal SQL but
/// rarely what you want.
#[must_use]
pub fn asc(self) -> OrderByExpr {
OrderByExpr::directional(OrderTarget::Expression(self), SortOrder::Asc)
}

/// The descending counterpart of [`Self::asc`].
#[must_use]
pub fn desc(self) -> OrderByExpr {
OrderByExpr::directional(OrderTarget::Expression(self), SortOrder::Desc)
}

/// The [`ExprSort::custom`]-equivalent for a compound expression.
///
/// Takes plain [`ToDbValue`] items rather than [`IntoField<T>`]: unlike
/// [`FieldRef<T>`], a general `Expr` isn't associated with one Rust
/// field type to convert against, so there's no `T` for `IntoField<T>`
/// to key off of.
#[must_use]
pub fn custom<I>(self, values: I) -> OrderByExpr
where
I: IntoIterator,
I::Item: ToDbValue,
{
let values = values.into_iter().map(|v| v.to_db_value()).collect();
OrderByExpr::custom(OrderTarget::Expression(self), sea_query::Values(values))
}

/// Returns the expression as a [`sea_query::SimpleExpr`].
///
/// # Example
Expand Down Expand Up @@ -1316,6 +1354,21 @@ impl<T> FieldRef<T> {
pub fn as_expr(&self) -> Expr {
Expr::Field(self.identifier)
}

pub(crate) fn identifier(&self) -> Identifier {
self.identifier
}
}

impl<LHS, RHS> Add<FieldRef<RHS>> for FieldRef<LHS>
where
FieldRef<LHS>: ExprAdd<FieldRef<RHS>>,
{
type Output = Expr;

fn add(self, rhs: FieldRef<RHS>) -> Self::Output {
ExprAdd::add(self, rhs)
}
}

/// A trait for types that can be compared in database expressions.
Expand Down Expand Up @@ -1645,6 +1698,20 @@ impl_num_expr!(u64);
impl_num_expr!(f32);
impl_num_expr!(f64);

impl ExprAdd<String> for FieldRef<String> {
fn add<V: Into<String>>(self, other: V) -> Expr {
Expr::add(Expr::field(self.identifier()), Expr::value(other.into()))
}
}

impl<const LIMIT: u32> ExprAdd<FieldRef<LimitedString<LIMIT>>> for FieldRef<LimitedString<LIMIT>> {
fn add<V: Into<FieldRef<LimitedString<LIMIT>>>>(self, other: V) -> Expr {
Expr::add(
Expr::field(self.identifier()),
Expr::field(other.into().identifier()),
)
}
}
#[cfg(test)]
mod test {
use super::*;
Expand Down
Loading
Loading