Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
86bc443
add basic code for ordering
xelab04 Feb 24, 2026
8692126
Merge branch 'cot-rs:master' into db-sort-by
xelab04 Mar 11, 2026
8fb90e8
change ordering to use fieldref instead of string
xelab04 Mar 11, 2026
fe383db
Merge branch 'cot-rs:master' into db-sort-by
xelab04 May 6, 2026
30bff01
Merge branch 'master' into db-sort-by
m4tx Jun 30, 2026
a72f2ec
Merge branch 'master' into db-sort-by
ElijahAhianyo Jul 5, 2026
9a4b9e6
Merge branch 'master' into db-sort-by
ElijahAhianyo Aug 25, 2026
62dbf4e
get this in a much better shape. Still lacking other expressiveness
ElijahAhianyo Aug 26, 2026
c34fef0
order_by field. Need a better name
ElijahAhianyo Aug 26, 2026
c671e62
much much better API. no tests yet. Also add proper support for custo…
ElijahAhianyo Aug 28, 2026
e7df2a9
Merge branch 'master' into db-sort-by
ElijahAhianyo Aug 30, 2026
3100b54
tests, tests and tests
ElijahAhianyo Sep 5, 2026
9734e1c
Merge branch 'master' into db-sort-by
ElijahAhianyo Sep 5, 2026
d25dbe0
docs initial draft. needs more work
ElijahAhianyo Sep 5, 2026
c3f65f3
Merge remote-tracking branch 'xelab04-fork/db-sort-by' into db-sort-by
ElijahAhianyo Sep 5, 2026
15462cc
Merge branch 'master' into db-sort-by
ElijahAhianyo Sep 7, 2026
e88dc1c
improve tests and docs, and more improvements
ElijahAhianyo Sep 8, 2026
718e15e
Merge remote-tracking branch 'xelab04-fork/db-sort-by' into db-sort-by
ElijahAhianyo Sep 8, 2026
f32ac80
chore(pre-commit.ci): auto fixes from pre-commit hooks
pre-commit-ci[bot] Sep 8, 2026
e6d81fe
bikeshedding
ElijahAhianyo Sep 8, 2026
7ef4696
Merge remote-tracking branch 'xelab04-fork/db-sort-by' into db-sort-by
ElijahAhianyo Sep 8, 2026
ef9aad0
increase coverage. Also, remove EprAdd impl for email and url. They a…
ElijahAhianyo Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cot/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2576,6 +2576,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
49 changes: 47 additions & 2 deletions 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};
pub use crate::db::query::expr::{Expr, ExprAdd, ExprDiv, ExprMul, ExprOrd, ExprSort, 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,36 @@ impl<T: Model> Query<T> {
self
}

/// Set an order for records from the query.
///
/// # Example
///
/// ```
/// use cot::db::model;
/// use cot::db::query::{ExprSort, Query};
///
/// #[model]
/// struct User {
/// #[model(primary_key)]
/// id: i32,
/// name: String,
/// }
///
/// let mut query = Query::<User>::new();
/// query.order_by([
/// <User as cot::db::Model>::Fields::id.asc(),
/// <User as cot::db::Model>::Fields::name.desc().nulls_first(),
/// ]);
/// ```
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 +283,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
146 changes: 146 additions & 0 deletions 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, Div, Mul, Sub};

use cot::db::query::{IntoField, QueryBuildingError};
use cot::db::{DbFieldValue, DbValue, FromDbValue, Identifier, 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,86 @@ impl Expr {
Self::RawLike(Box::new(lhs), Box::new(rhs), CaseSensitivity::Insensitive)
}

/// Builds an ascending `ORDER BY` term from an expression, with `NULL`s
/// sorted last by default.
///
/// # Examples
///
/// ```
/// use cot::db::query::Query;
/// use cot::db::query::expr::Expr;
/// use cot::db::{model, query};
///
/// #[model]
/// struct MyModel {
/// #[model(primary_key)]
/// id: i32,
/// filename: String,
/// }
///
/// let _ = Expr::field("filename").asc();
/// ```
#[must_use]
pub fn asc(self) -> OrderByExpr {
OrderByExpr::directional(OrderTarget::Expression(self), SortOrder::Asc)
}

/// Builds a descending `ORDER BY` term from an expression, with `NULL`s
/// sorted first by default.
///
/// # Examples
///
/// ```
/// use cot::db::query::Query;
/// use cot::db::query::expr::Expr;
/// use cot::db::{model, query};
///
/// #[model]
/// struct MyModel {
/// #[model(primary_key)]
/// id: i32,
/// filename: String,
/// }
///
/// let _ = Expr::field("filename").desc();
/// ```
#[must_use]
pub fn desc(self) -> OrderByExpr {
OrderByExpr::directional(OrderTarget::Expression(self), SortOrder::Desc)
}

/// Order an expression based on the position of the provided field values
///
/// # Examples
///
/// ```
/// use cot::db::query::Query;
/// use cot::db::query::expr::Expr;
/// use cot::db::{ToDbValue, model, query};
///
/// #[model]
/// struct MyModel {
/// #[model(primary_key)]
/// id: i32,
/// filename: String,
/// }
///
/// let _ = Expr::field("filename").field_value(vec![
/// "foo".to_string(),
/// "bar".to_string(),
/// "baz".to_string(),
/// ]);
/// ```
#[must_use]
pub fn field_value<I>(self, values: I) -> OrderByExpr
where
I: IntoIterator,
I::Item: ToDbValue,
{
let values = values.into_iter().map(|v| v.to_db_value()).collect();
OrderByExpr::field_value(OrderTarget::Expression(self), sea_query::Values(values))
}

/// Returns the expression as a [`sea_query::SimpleExpr`].
///
/// # Example
Expand Down Expand Up @@ -1316,6 +1402,42 @@ 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> {
type Output = Expr;

fn add(self, rhs: FieldRef<Rhs>) -> Self::Output {
Expr::add(self.as_expr(), rhs.as_expr())
}
}

impl<Lhs, Rhs> Sub<FieldRef<Rhs>> for FieldRef<Lhs> {
type Output = Expr;

fn sub(self, rhs: FieldRef<Rhs>) -> Self::Output {
Expr::sub(self.as_expr(), rhs.as_expr())
}
}

impl<Lhs, Rhs> Mul<FieldRef<Rhs>> for FieldRef<Lhs> {
type Output = Expr;

fn mul(self, rhs: FieldRef<Rhs>) -> Self::Output {
Expr::mul(self.as_expr(), rhs.as_expr())
}
}

impl<Lhs, Rhs> Div<FieldRef<Rhs>> for FieldRef<Lhs> {
type Output = Expr;

fn div(self, rhs: FieldRef<Rhs>) -> Self::Output {
Expr::div(self.as_expr(), rhs.as_expr())
}
}

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

// TODO: Provide `ExprAdd<T> for FieldRef<T>` implementations for String and
// LimitedString if Expr::concat is supported

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -1696,4 +1821,25 @@ mod test {
test_expr_constructor!(expr_sub, Sub, sub);
test_expr_constructor!(expr_mul, Mul, mul);
test_expr_constructor!(expr_div, Div, div);

#[test]
fn field_ref_sub_operator_builds_sub_expr() {
let x: FieldRef<i32> = FieldRef::new(Identifier::new("x"));
let y: FieldRef<i32> = FieldRef::new(Identifier::new("y"));
assert!(matches!(x - y, Expr::Sub(_, _)));
}

#[test]
fn field_ref_mul_operator_builds_mul_expr() {
let x: FieldRef<i32> = FieldRef::new(Identifier::new("x"));
let y: FieldRef<i32> = FieldRef::new(Identifier::new("y"));
assert!(matches!(x * y, Expr::Mul(_, _)));
}

#[test]
fn field_ref_div_operator_builds_div_expr() {
let x: FieldRef<i32> = FieldRef::new(Identifier::new("x"));
let y: FieldRef<i32> = FieldRef::new(Identifier::new("y"));
assert!(matches!(x / y, Expr::Div(_, _)));
}
}
Loading
Loading