From 306e873a5f8a9fb74d35729fe29e0f2e8bbc0fd5 Mon Sep 17 00:00:00 2001 From: Elijah Date: Sun, 5 Apr 2026 02:08:20 +0000 Subject: [PATCH 01/12] Add query macro docs --- cot-macros/src/lib.rs | 230 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/cot-macros/src/lib.rs b/cot-macros/src/lib.rs index 34fe4134..a3af69c4 100644 --- a/cot-macros/src/lib.rs +++ b/cot-macros/src/lib.rs @@ -202,6 +202,236 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { TokenStream::new() } +/// A convenient macro that allows you to write queries in a declarative +/// fashion. +/// +/// `query!` parses a query expression and lowers it into a [`Query`] builder. +/// The resulting query is still lazy: it is only executed when you call a +/// terminal query method such as [`Query::get`] or [`Query::all`]. +/// +/// The macro expands roughly to: +/// +/// ```ignore +/// ::objects().filter(...) +/// ``` +/// +/// # Query syntax +/// +/// Query expressions can reference model fields with `$field_name`, combine +/// conditions with boolean operators, and use comparison and arithmetic +/// operators. +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// #[model] +/// struct Customer { +/// #[model(primary_key)] +/// id: i32, +/// full_name: String, +/// status: String, +/// price: i32, +/// stock: i32, +/// quantity: i32, +/// is_active: bool +/// } +/// +/// let query = query!(Customer, $id == 5 && $full_name == "Jon Doe"); +/// ``` +/// +/// In the example above, `$id` and `$full_name` refer to fields on the +/// `Customer` model. +/// +/// +/// ## Field references +/// +/// Use `$name` to refer to a model field. +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// # #[model] +/// # struct Customer { +/// # #[model(primary_key)] +/// # id: i32, +/// # full_name: String, +/// # } +/// let _ = query!(Customer, $id); +/// let _ = query!(Customer, $full_name); +/// ``` +/// +/// ## Literal values +/// +/// Rust literals can be used directly in expressions. +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// # #[model] +/// # struct Customer { +/// # #[model(primary_key)] +/// # id: i32, +/// # full_name: String, +/// # is_active: bool, +/// # } +/// let _ = query!(Customer, $id == 5); +/// let _ = query!(Customer, $full_name == "Jon Doe"); +/// let _ = query!(Customer, $is_active == true); +/// ``` +/// +/// ## Comparison operators +/// +/// ### Equality +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// # #[model] +/// # struct Customer { +/// # #[model(primary_key)] +/// # id: i32, +/// # } +/// let _ = query!(Customer, $id == 5); +/// ``` +/// +/// ### Inequality +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// # #[model] +/// # struct Customer { +/// # #[model(primary_key)] +/// # id: i32, +/// # } +/// let _ = query!(Customer, $id != 5); +/// ``` +/// +/// ### Less than and less than or equal +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// # #[model] +/// # struct Customer { +/// # #[model(primary_key)] +/// # id: i32, +/// # } +/// let _ = query!(Customer, $id < 10); +/// let _ = query!(Customer, $id <= 10); +/// ``` +/// +/// ### Greater than and greater than or equal +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// # #[model] +/// # struct Customer { +/// # #[model(primary_key)] +/// # id: i32, +/// # } +/// let _ = query!(Customer, $id > 5); +/// let _ = query!(Customer, $id >= 5); +/// ``` +/// +/// ## Boolean operators +/// +/// Combine expressions with `&&` and `||`. +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// # #[model] +/// # struct Customer { +/// # #[model(primary_key)] +/// # id: i32, +/// # full_name: String, +/// # } +/// let _ = query!(Customer, $id > 5 && $full_name == "Jon Doe"); +/// let _ = query!(Customer, $id < 5 || $id > 100); +/// ``` +/// +/// ## Arithmetic operators +/// +/// Query expressions also support arithmetic over fields and values. +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// # #[model] +/// # struct Customer { +/// # #[model(primary_key)] +/// # id: i32, +/// # price: i32, +/// # stock: i32, +/// # quantity: i32, +/// # } +/// let _ = query!(Customer, $price + 10); +/// let _ = query!(Customer, $stock - 1); +/// let _ = query!(Customer, $quantity * 2); +/// let _ = query!(Customer, $price / 2); +/// ``` +/// +/// ## Rust-side value expressions +/// +/// When an expression does not reference a database field, `query!` can treat +/// it as a Rust value expression. This includes member access, path access, and +/// function calls. +/// +/// ``` +/// use cot::db::{model, query}; +/// +/// # #[model] +/// # struct Customer { +/// # #[model(primary_key)] +/// # id: i32, +/// # status: String, +/// # } +/// struct User { +/// id: i32, +/// } +/// +/// mod constants { +/// pub const ACTIVE_STATUS: &str = "active"; +/// } +/// +/// fn next_customer_id() -> i32 { +/// 42 +/// } +/// +/// let user = User { id: 5 }; +/// +/// let _ = query!(Customer, $id == user.id); +/// let _ = query!(Customer, $status == constants::ACTIVE_STATUS); +/// let _ = query!(Customer, $id == next_customer_id()); +/// ``` +/// +/// # Executing a query +/// +/// `query!` builds a query. To execute it and retrieve results, call a terminal +/// query method such as [`Query::get`] or [`Query::all`]. +/// +/// ``` +/// use cot::db::{Database, model, query}; +/// +/// #[model] +/// struct Customer { +/// #[model(primary_key)] +/// id: i32, +/// full_name: String, +/// } +/// +/// # async fn run(db: Database) -> cot::Result<()> { +/// let customer = query!(Customer, $id == 5).get(db).await?; +/// println!("Customer: {:?}", customer); +/// # Ok(()) +/// # } +/// ``` +/// +/// [`Query`]: query/struct.Query.html +/// [`Query::get`]: query/Struct.Query.html#method.get +/// [`Query::all`]: query/Struct.Query.html#method.all #[proc_macro] pub fn query(input: TokenStream) -> TokenStream { let query_input = parse_macro_input!(input as Query); From 85e9f3c37c934fe39436307e00ddd1fee28148a3 Mon Sep 17 00:00:00 2001 From: Elijah Date: Sun, 5 Apr 2026 02:42:00 +0000 Subject: [PATCH 02/12] fix doc errors --- cot-macros/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cot-macros/src/lib.rs b/cot-macros/src/lib.rs index a3af69c4..0d0ed52e 100644 --- a/cot-macros/src/lib.rs +++ b/cot-macros/src/lib.rs @@ -416,6 +416,7 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// use cot::db::{Database, model, query}; /// /// #[model] +/// #[derive(Debug, Clone)] /// struct Customer { /// #[model(primary_key)] /// id: i32, @@ -423,7 +424,7 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// } /// /// # async fn run(db: Database) -> cot::Result<()> { -/// let customer = query!(Customer, $id == 5).get(db).await?; +/// let customer = query!(Customer, $id == 5).get(&db).await?; /// println!("Customer: {:?}", customer); /// # Ok(()) /// # } From 9f14d2e7ab299f2ec60d193e4968fd81d2d20975 Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 9 Apr 2026 12:58:06 +0000 Subject: [PATCH 03/12] merge comparison section --- cot-macros/src/lib.rs | 55 ------------------------------------------- 1 file changed, 55 deletions(-) diff --git a/cot-macros/src/lib.rs b/cot-macros/src/lib.rs index 0d0ed52e..d135e606 100644 --- a/cot-macros/src/lib.rs +++ b/cot-macros/src/lib.rs @@ -281,8 +281,6 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// /// ## Comparison operators /// -/// ### Equality -/// /// ``` /// use cot::db::{model, query}; /// @@ -292,66 +290,13 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// # id: i32, /// # } /// let _ = query!(Customer, $id == 5); -/// ``` -/// -/// ### Inequality -/// -/// ``` -/// use cot::db::{model, query}; -/// -/// # #[model] -/// # struct Customer { -/// # #[model(primary_key)] -/// # id: i32, -/// # } /// let _ = query!(Customer, $id != 5); -/// ``` -/// -/// ### Less than and less than or equal -/// -/// ``` -/// use cot::db::{model, query}; -/// -/// # #[model] -/// # struct Customer { -/// # #[model(primary_key)] -/// # id: i32, -/// # } /// let _ = query!(Customer, $id < 10); /// let _ = query!(Customer, $id <= 10); -/// ``` -/// -/// ### Greater than and greater than or equal -/// -/// ``` -/// use cot::db::{model, query}; -/// -/// # #[model] -/// # struct Customer { -/// # #[model(primary_key)] -/// # id: i32, -/// # } /// let _ = query!(Customer, $id > 5); /// let _ = query!(Customer, $id >= 5); /// ``` /// -/// ## Boolean operators -/// -/// Combine expressions with `&&` and `||`. -/// -/// ``` -/// use cot::db::{model, query}; -/// -/// # #[model] -/// # struct Customer { -/// # #[model(primary_key)] -/// # id: i32, -/// # full_name: String, -/// # } -/// let _ = query!(Customer, $id > 5 && $full_name == "Jon Doe"); -/// let _ = query!(Customer, $id < 5 || $id > 100); -/// ``` -/// /// ## Arithmetic operators /// /// Query expressions also support arithmetic over fields and values. From 7458e0bc46ac367c79fe69796cbb55fcf3661ad5 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 15 Apr 2026 06:07:10 +0000 Subject: [PATCH 04/12] add tests for query macro --- cot-macros/src/query.rs | 4 - cot-macros/tests/query.rs | 389 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 389 insertions(+), 4 deletions(-) create mode 100644 cot-macros/tests/query.rs diff --git a/cot-macros/src/query.rs b/cot-macros/src/query.rs index d832e0d3..70d1c884 100644 --- a/cot-macros/src/query.rs +++ b/cot-macros/src/query.rs @@ -34,10 +34,6 @@ pub(super) fn query_to_tokens(query: Query) -> TokenStream { } pub(super) fn expr_to_tokens(model_name: &syn::Type, expr: Expr) -> TokenStream { - if let Some(tokens) = expr.as_tokens() { - return tokens; - } - let crate_name = cot_ident(); match expr { Expr::FieldRef { field_name, .. } => { diff --git a/cot-macros/tests/query.rs b/cot-macros/tests/query.rs new file mode 100644 index 00000000..507eb021 --- /dev/null +++ b/cot-macros/tests/query.rs @@ -0,0 +1,389 @@ +use cot::db::query::{Expr, ExprAdd, ExprDiv, ExprEq, ExprMul, ExprOrd, ExprSub, Query}; +use cot::db::{model, query}; + +#[model] +#[derive(Debug, PartialEq)] +struct MyModel { + #[model(primary_key)] + id: i32, + name: String, + price: i64, + quantity: i64 +} + +#[test] +fn test_query_equality() { + assert_eq!( + Query::::new().filter( + ExprEq::eq( + ::Fields::id, + 5 + ) + ), + query!(MyModel, $id == 5) + ); + + assert_eq!( + Query::::new().filter( + ExprEq::ne( + ::Fields::id, + 5 + ) + ), + query!(MyModel, $id != 5) + ); +} + +#[test] +fn test_query_comparison() { + assert_eq!( + Query::::new().filter( + ExprOrd::lt( + ::Fields::id, + 5 + ) + ), + query!(MyModel, $id < 5) + ); + + assert_eq!( + Query::::new().filter( + ExprOrd::lte( + ::Fields::id, + 5 + ) + ), + query!(MyModel, $id <= 5) + ); + + assert_eq!( + Query::::new().filter( + ExprOrd::gt( + ::Fields::id, + 5 + ) + ), + query!(MyModel, $id > 5) + ); + + assert_eq!( + Query::::new().filter( + ExprOrd::gte( + ::Fields::id, + 5 + ) + ), + query!(MyModel, $id >= 5) + ); + + assert_eq!( + Query::::new().filter( + Expr::and( + ExprEq::eq( + ::Fields::id, + 5 + ), + ExprEq::eq( + ::Fields::name, + "test" + ) + ) + ), + query!(MyModel, $id == 5 && $name == "test") + ); + + assert_eq!( + Query::::new().filter( + Expr::or( + ExprEq::eq( + ::Fields::id, + 5 + ), + ExprEq::eq( + ::Fields::id, + 10 + ) + ) + ), + query!(MyModel, $id == 5 || $id == 10) + ); + + assert_eq!( + Query::::new().filter( + Expr::and( + ExprOrd::gt( + ::Fields::id, + 0 + ), + Expr::or( + ExprEq::eq( + ::Fields::name, + "a" + ), + ExprEq::eq( + ::Fields::name, + "b" + ) + ) + ) + ), + query!(MyModel, $id > 0 && ($name == "a" || $name == "b")) + ); +} + +#[test] +fn test_query_arithmetic() { + assert_eq!( + ::objects().filter( + Expr::eq( + ::Fields::id.as_expr(), + ExprAdd::add( + ::Fields::id, + 5) + ) + ), + query!(MyModel, $id == $id + 5) + ); + + + assert_eq!( + Query::::new().filter( + Expr::eq( + Expr::field("price"), + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + ) + ), + query!(MyModel, $price == $quantity + $id) + ); + + assert_eq!( + Query::::new().filter( + Expr::gt( + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + ) + ), + query!(MyModel, $quantity + $id > 11) + ); + + assert_eq!( + Query::::new().filter( + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + ), + query!(MyModel, $quantity + $id) + ); + + + assert_eq!( + Query::::new().filter( + Expr::eq( + Expr::field("id"), + ::Fields::id.sub(5) + ) + ), + query!(MyModel, $id == $id - 5) + ); + + assert_eq!( + Query::::new().filter( + Expr::eq( + Expr::field("price"), + Expr::sub( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + ) + ), + query!(MyModel, $price == $quantity - $id) + ); + + assert_eq!( + Query::::new().filter( + Expr::gt( + Expr::sub( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + ) + ), + query!(MyModel, $quantity - $id > 11) + ); + + assert_eq!( + ::objects().filter( + ExprSub::sub( + ::Fields::quantity, + 1 + ) + ), + query!(MyModel, $quantity - 1) + ); + + assert_eq!( + ::objects().filter( + Expr::eq( + ::Fields::id.as_expr(), + ExprMul::mul( + ::Fields::id, + 5 + ) + ) + ), + query!(MyModel, $id == $id * 5) + ); + + assert_eq!( + Query::::new().filter( + Expr::eq( + Expr::field("price"), + Expr::mul( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + ) + ), + query!(MyModel, $price == $quantity * $id) + ); + + assert_eq!( + ::objects().filter( + Expr::gt( + Expr::mul( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + ) + ), + query!(MyModel, $quantity * $id > 11) + ); + + assert_eq!( + ::objects().filter( + ::cot::db::query::ExprMul::mul( + ::Fields::quantity, + 5i64 + ) + ), + query!(MyModel, $quantity * 5) + ); + + assert_eq!( + Query::::new().filter( + Expr::eq( + Expr::field("id"), + ::Fields::id.div(5) + ) + ), + query!(MyModel, $id == $id / 5) + ); + + assert_eq!( + Query::::new().filter( + Expr::eq( + Expr::field("price"), + Expr::div( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + ) + ), + query!(MyModel, $price == $quantity / $id) + ); + + assert_eq!( + Query::::new().filter( + Expr::gt( + Expr::div( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + ) + ), + query!(MyModel, $quantity / $id > 11) + ); + + assert_eq!( + Query::::new().filter( + Expr::div( + ::Fields::quantity.as_expr(), + Expr::value(1i64) + ) + ), + query!(MyModel, $quantity / 1) + ); + +} + +#[test] +fn test_query_rust_expressions() { + assert_eq!( + ::objects().filter( + ExprEq::eq( + ::Fields::id, + 10 + ) + ), + query!(MyModel, $id == 10) + ); + + struct Outer { + inner: i32, + } + let outer = Outer { inner: 20 }; + assert_eq!( + ::objects().filter( + ExprEq::eq( + ::Fields::id, + outer.inner) + ), + query!(MyModel, $id == outer.inner) + ); + + fn get_id() -> i32 { + 30 + } + assert_eq!( + ::objects().filter( + ExprEq::eq( + ::Fields::id, + get_id() + ) + ), + query!(MyModel, $id == get_id()) + ); + + assert_eq!( + ::objects().filter( + ::Fields::id.as_expr() + ), + query!(MyModel, $id) + ); +} + +#[test] +fn test_query_path_access() { + mod constants { + pub(crate) const ID: i32 = 100; + } + assert_eq!( + ::objects().filter( + ExprEq::eq( + ::Fields::id, + constants::ID + ) + ), + query!(MyModel, $id == constants::ID) + ); +} From 05cdd2882a5fdb7422718482e05d8a2c2c0bba69 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 06:07:30 +0000 Subject: [PATCH 05/12] chore(pre-commit.ci): auto fixes from pre-commit hooks --- cot-macros/tests/query.rs | 353 +++++++++++++------------------------- 1 file changed, 123 insertions(+), 230 deletions(-) diff --git a/cot-macros/tests/query.rs b/cot-macros/tests/query.rs index 507eb021..483a8d91 100644 --- a/cot-macros/tests/query.rs +++ b/cot-macros/tests/query.rs @@ -8,28 +8,18 @@ struct MyModel { id: i32, name: String, price: i64, - quantity: i64 + quantity: i64, } #[test] fn test_query_equality() { assert_eq!( - Query::::new().filter( - ExprEq::eq( - ::Fields::id, - 5 - ) - ), + Query::::new().filter(ExprEq::eq(::Fields::id, 5)), query!(MyModel, $id == 5) ); assert_eq!( - Query::::new().filter( - ExprEq::ne( - ::Fields::id, - 5 - ) - ), + Query::::new().filter(ExprEq::ne(::Fields::id, 5)), query!(MyModel, $id != 5) ); } @@ -37,96 +27,49 @@ fn test_query_equality() { #[test] fn test_query_comparison() { assert_eq!( - Query::::new().filter( - ExprOrd::lt( - ::Fields::id, - 5 - ) - ), + Query::::new().filter(ExprOrd::lt(::Fields::id, 5)), query!(MyModel, $id < 5) ); assert_eq!( - Query::::new().filter( - ExprOrd::lte( - ::Fields::id, - 5 - ) - ), + Query::::new().filter(ExprOrd::lte(::Fields::id, 5)), query!(MyModel, $id <= 5) ); assert_eq!( - Query::::new().filter( - ExprOrd::gt( - ::Fields::id, - 5 - ) - ), + Query::::new().filter(ExprOrd::gt(::Fields::id, 5)), query!(MyModel, $id > 5) ); assert_eq!( - Query::::new().filter( - ExprOrd::gte( - ::Fields::id, - 5 - ) - ), + Query::::new().filter(ExprOrd::gte(::Fields::id, 5)), query!(MyModel, $id >= 5) ); - + assert_eq!( - Query::::new().filter( - Expr::and( - ExprEq::eq( - ::Fields::id, - 5 - ), - ExprEq::eq( - ::Fields::name, - "test" - ) - ) - ), + Query::::new().filter(Expr::and( + ExprEq::eq(::Fields::id, 5), + ExprEq::eq(::Fields::name, "test") + )), query!(MyModel, $id == 5 && $name == "test") ); assert_eq!( - Query::::new().filter( - Expr::or( - ExprEq::eq( - ::Fields::id, - 5 - ), - ExprEq::eq( - ::Fields::id, - 10 - ) - ) - ), + Query::::new().filter(Expr::or( + ExprEq::eq(::Fields::id, 5), + ExprEq::eq(::Fields::id, 10) + )), query!(MyModel, $id == 5 || $id == 10) ); assert_eq!( - Query::::new().filter( - Expr::and( - ExprOrd::gt( - ::Fields::id, - 0 - ), - Expr::or( - ExprEq::eq( - ::Fields::name, - "a" - ), - ExprEq::eq( - ::Fields::name, - "b" - ) - ) + Query::::new().filter(Expr::and( + ExprOrd::gt(::Fields::id, 0), + Expr::or( + ExprEq::eq(::Fields::name, "a"), + ExprEq::eq(::Fields::name, "b") ) - ), + )), query!(MyModel, $id > 0 && ($name == "a" || $name == "b")) ); } @@ -134,207 +77,163 @@ fn test_query_comparison() { #[test] fn test_query_arithmetic() { assert_eq!( - ::objects().filter( - Expr::eq( - ::Fields::id.as_expr(), - ExprAdd::add( - ::Fields::id, - 5) - ) - ), + ::objects().filter(Expr::eq( + ::Fields::id.as_expr(), + ExprAdd::add(::Fields::id, 5) + )), query!(MyModel, $id == $id + 5) ); - assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("price"), - Expr::add( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) + Query::::new().filter(Expr::eq( + Expr::field("price"), + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() ) - ), + )), query!(MyModel, $price == $quantity + $id) ); assert_eq!( - Query::::new().filter( - Expr::gt( - Expr::add( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - ) - ), + Query::::new().filter(Expr::gt( + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + )), query!(MyModel, $quantity + $id > 11) ); assert_eq!( - Query::::new().filter( - Expr::add( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) - ), + Query::::new().filter(Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + )), query!(MyModel, $quantity + $id) ); - assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("id"), - ::Fields::id.sub(5) - ) - ), + Query::::new().filter(Expr::eq( + Expr::field("id"), + ::Fields::id.sub(5) + )), query!(MyModel, $id == $id - 5) ); assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("price"), - Expr::sub( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) + Query::::new().filter(Expr::eq( + Expr::field("price"), + Expr::sub( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() ) - ), + )), query!(MyModel, $price == $quantity - $id) ); assert_eq!( - Query::::new().filter( - Expr::gt( - Expr::sub( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - ) - ), + Query::::new().filter(Expr::gt( + Expr::sub( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + )), query!(MyModel, $quantity - $id > 11) ); - + assert_eq!( - ::objects().filter( - ExprSub::sub( - ::Fields::quantity, - 1 - ) - ), + ::objects().filter(ExprSub::sub( + ::Fields::quantity, + 1 + )), query!(MyModel, $quantity - 1) ); assert_eq!( - ::objects().filter( - Expr::eq( - ::Fields::id.as_expr(), - ExprMul::mul( - ::Fields::id, - 5 - ) - ) - ), + ::objects().filter(Expr::eq( + ::Fields::id.as_expr(), + ExprMul::mul(::Fields::id, 5) + )), query!(MyModel, $id == $id * 5) ); assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("price"), - Expr::mul( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) + Query::::new().filter(Expr::eq( + Expr::field("price"), + Expr::mul( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() ) - ), + )), query!(MyModel, $price == $quantity * $id) ); assert_eq!( - ::objects().filter( - Expr::gt( - Expr::mul( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - ) - ), + ::objects().filter(Expr::gt( + Expr::mul( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + )), query!(MyModel, $quantity * $id > 11) ); assert_eq!( - ::objects().filter( - ::cot::db::query::ExprMul::mul( - ::Fields::quantity, - 5i64 - ) - ), + ::objects().filter(::cot::db::query::ExprMul::mul( + ::Fields::quantity, + 5i64 + )), query!(MyModel, $quantity * 5) ); assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("id"), - ::Fields::id.div(5) - ) - ), + Query::::new().filter(Expr::eq( + Expr::field("id"), + ::Fields::id.div(5) + )), query!(MyModel, $id == $id / 5) ); assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("price"), - Expr::div( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) + Query::::new().filter(Expr::eq( + Expr::field("price"), + Expr::div( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() ) - ), + )), query!(MyModel, $price == $quantity / $id) ); assert_eq!( - Query::::new().filter( - Expr::gt( - Expr::div( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - ) - ), + Query::::new().filter(Expr::gt( + Expr::div( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + )), query!(MyModel, $quantity / $id > 11) ); assert_eq!( - Query::::new().filter( - Expr::div( - ::Fields::quantity.as_expr(), - Expr::value(1i64) - ) - ), + Query::::new().filter(Expr::div( + ::Fields::quantity.as_expr(), + Expr::value(1i64) + )), query!(MyModel, $quantity / 1) ); - } #[test] fn test_query_rust_expressions() { assert_eq!( - ::objects().filter( - ExprEq::eq( - ::Fields::id, - 10 - ) - ), + ::objects() + .filter(ExprEq::eq(::Fields::id, 10)), query!(MyModel, $id == 10) ); @@ -343,11 +242,10 @@ fn test_query_rust_expressions() { } let outer = Outer { inner: 20 }; assert_eq!( - ::objects().filter( - ExprEq::eq( - ::Fields::id, - outer.inner) - ), + ::objects().filter(ExprEq::eq( + ::Fields::id, + outer.inner + )), query!(MyModel, $id == outer.inner) ); @@ -355,19 +253,16 @@ fn test_query_rust_expressions() { 30 } assert_eq!( - ::objects().filter( - ExprEq::eq( - ::Fields::id, - get_id() - ) - ), + ::objects().filter(ExprEq::eq( + ::Fields::id, + get_id() + )), query!(MyModel, $id == get_id()) ); assert_eq!( - ::objects().filter( - ::Fields::id.as_expr() - ), + ::objects() + .filter(::Fields::id.as_expr()), query!(MyModel, $id) ); } @@ -378,12 +273,10 @@ fn test_query_path_access() { pub(crate) const ID: i32 = 100; } assert_eq!( - ::objects().filter( - ExprEq::eq( - ::Fields::id, - constants::ID - ) - ), + ::objects().filter(ExprEq::eq( + ::Fields::id, + constants::ID + )), query!(MyModel, $id == constants::ID) ); } From 23abe5cd0320f3961764c6511df8db5f8fb9ff19 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 15 Apr 2026 06:11:49 +0000 Subject: [PATCH 06/12] Revert "chore(pre-commit.ci): auto fixes from pre-commit hooks" This reverts commit 426596cec279e9ab2af153744200067ae681a88b. --- cot-macros/tests/query.rs | 353 +++++++++++++++++++++++++------------- 1 file changed, 230 insertions(+), 123 deletions(-) diff --git a/cot-macros/tests/query.rs b/cot-macros/tests/query.rs index 483a8d91..507eb021 100644 --- a/cot-macros/tests/query.rs +++ b/cot-macros/tests/query.rs @@ -8,18 +8,28 @@ struct MyModel { id: i32, name: String, price: i64, - quantity: i64, + quantity: i64 } #[test] fn test_query_equality() { assert_eq!( - Query::::new().filter(ExprEq::eq(::Fields::id, 5)), + Query::::new().filter( + ExprEq::eq( + ::Fields::id, + 5 + ) + ), query!(MyModel, $id == 5) ); assert_eq!( - Query::::new().filter(ExprEq::ne(::Fields::id, 5)), + Query::::new().filter( + ExprEq::ne( + ::Fields::id, + 5 + ) + ), query!(MyModel, $id != 5) ); } @@ -27,49 +37,96 @@ fn test_query_equality() { #[test] fn test_query_comparison() { assert_eq!( - Query::::new().filter(ExprOrd::lt(::Fields::id, 5)), + Query::::new().filter( + ExprOrd::lt( + ::Fields::id, + 5 + ) + ), query!(MyModel, $id < 5) ); assert_eq!( - Query::::new().filter(ExprOrd::lte(::Fields::id, 5)), + Query::::new().filter( + ExprOrd::lte( + ::Fields::id, + 5 + ) + ), query!(MyModel, $id <= 5) ); assert_eq!( - Query::::new().filter(ExprOrd::gt(::Fields::id, 5)), + Query::::new().filter( + ExprOrd::gt( + ::Fields::id, + 5 + ) + ), query!(MyModel, $id > 5) ); assert_eq!( - Query::::new().filter(ExprOrd::gte(::Fields::id, 5)), + Query::::new().filter( + ExprOrd::gte( + ::Fields::id, + 5 + ) + ), query!(MyModel, $id >= 5) ); - + assert_eq!( - Query::::new().filter(Expr::and( - ExprEq::eq(::Fields::id, 5), - ExprEq::eq(::Fields::name, "test") - )), + Query::::new().filter( + Expr::and( + ExprEq::eq( + ::Fields::id, + 5 + ), + ExprEq::eq( + ::Fields::name, + "test" + ) + ) + ), query!(MyModel, $id == 5 && $name == "test") ); assert_eq!( - Query::::new().filter(Expr::or( - ExprEq::eq(::Fields::id, 5), - ExprEq::eq(::Fields::id, 10) - )), + Query::::new().filter( + Expr::or( + ExprEq::eq( + ::Fields::id, + 5 + ), + ExprEq::eq( + ::Fields::id, + 10 + ) + ) + ), query!(MyModel, $id == 5 || $id == 10) ); assert_eq!( - Query::::new().filter(Expr::and( - ExprOrd::gt(::Fields::id, 0), - Expr::or( - ExprEq::eq(::Fields::name, "a"), - ExprEq::eq(::Fields::name, "b") + Query::::new().filter( + Expr::and( + ExprOrd::gt( + ::Fields::id, + 0 + ), + Expr::or( + ExprEq::eq( + ::Fields::name, + "a" + ), + ExprEq::eq( + ::Fields::name, + "b" + ) + ) ) - )), + ), query!(MyModel, $id > 0 && ($name == "a" || $name == "b")) ); } @@ -77,163 +134,207 @@ fn test_query_comparison() { #[test] fn test_query_arithmetic() { assert_eq!( - ::objects().filter(Expr::eq( - ::Fields::id.as_expr(), - ExprAdd::add(::Fields::id, 5) - )), + ::objects().filter( + Expr::eq( + ::Fields::id.as_expr(), + ExprAdd::add( + ::Fields::id, + 5) + ) + ), query!(MyModel, $id == $id + 5) ); + assert_eq!( - Query::::new().filter(Expr::eq( - Expr::field("price"), - Expr::add( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() + Query::::new().filter( + Expr::eq( + Expr::field("price"), + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) ) - )), + ), query!(MyModel, $price == $quantity + $id) ); assert_eq!( - Query::::new().filter(Expr::gt( - Expr::add( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - )), + Query::::new().filter( + Expr::gt( + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + ) + ), query!(MyModel, $quantity + $id > 11) ); assert_eq!( - Query::::new().filter(Expr::add( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - )), + Query::::new().filter( + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + ), query!(MyModel, $quantity + $id) ); + assert_eq!( - Query::::new().filter(Expr::eq( - Expr::field("id"), - ::Fields::id.sub(5) - )), + Query::::new().filter( + Expr::eq( + Expr::field("id"), + ::Fields::id.sub(5) + ) + ), query!(MyModel, $id == $id - 5) ); assert_eq!( - Query::::new().filter(Expr::eq( - Expr::field("price"), - Expr::sub( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() + Query::::new().filter( + Expr::eq( + Expr::field("price"), + Expr::sub( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) ) - )), + ), query!(MyModel, $price == $quantity - $id) ); assert_eq!( - Query::::new().filter(Expr::gt( - Expr::sub( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - )), + Query::::new().filter( + Expr::gt( + Expr::sub( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + ) + ), query!(MyModel, $quantity - $id > 11) ); - + assert_eq!( - ::objects().filter(ExprSub::sub( - ::Fields::quantity, - 1 - )), + ::objects().filter( + ExprSub::sub( + ::Fields::quantity, + 1 + ) + ), query!(MyModel, $quantity - 1) ); assert_eq!( - ::objects().filter(Expr::eq( - ::Fields::id.as_expr(), - ExprMul::mul(::Fields::id, 5) - )), + ::objects().filter( + Expr::eq( + ::Fields::id.as_expr(), + ExprMul::mul( + ::Fields::id, + 5 + ) + ) + ), query!(MyModel, $id == $id * 5) ); assert_eq!( - Query::::new().filter(Expr::eq( - Expr::field("price"), - Expr::mul( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() + Query::::new().filter( + Expr::eq( + Expr::field("price"), + Expr::mul( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) ) - )), + ), query!(MyModel, $price == $quantity * $id) ); assert_eq!( - ::objects().filter(Expr::gt( - Expr::mul( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - )), + ::objects().filter( + Expr::gt( + Expr::mul( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + ) + ), query!(MyModel, $quantity * $id > 11) ); assert_eq!( - ::objects().filter(::cot::db::query::ExprMul::mul( - ::Fields::quantity, - 5i64 - )), + ::objects().filter( + ::cot::db::query::ExprMul::mul( + ::Fields::quantity, + 5i64 + ) + ), query!(MyModel, $quantity * 5) ); assert_eq!( - Query::::new().filter(Expr::eq( - Expr::field("id"), - ::Fields::id.div(5) - )), + Query::::new().filter( + Expr::eq( + Expr::field("id"), + ::Fields::id.div(5) + ) + ), query!(MyModel, $id == $id / 5) ); assert_eq!( - Query::::new().filter(Expr::eq( - Expr::field("price"), - Expr::div( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() + Query::::new().filter( + Expr::eq( + Expr::field("price"), + Expr::div( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) ) - )), + ), query!(MyModel, $price == $quantity / $id) ); assert_eq!( - Query::::new().filter(Expr::gt( - Expr::div( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - )), + Query::::new().filter( + Expr::gt( + Expr::div( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + ) + ), query!(MyModel, $quantity / $id > 11) ); assert_eq!( - Query::::new().filter(Expr::div( - ::Fields::quantity.as_expr(), - Expr::value(1i64) - )), + Query::::new().filter( + Expr::div( + ::Fields::quantity.as_expr(), + Expr::value(1i64) + ) + ), query!(MyModel, $quantity / 1) ); + } #[test] fn test_query_rust_expressions() { assert_eq!( - ::objects() - .filter(ExprEq::eq(::Fields::id, 10)), + ::objects().filter( + ExprEq::eq( + ::Fields::id, + 10 + ) + ), query!(MyModel, $id == 10) ); @@ -242,10 +343,11 @@ fn test_query_rust_expressions() { } let outer = Outer { inner: 20 }; assert_eq!( - ::objects().filter(ExprEq::eq( - ::Fields::id, - outer.inner - )), + ::objects().filter( + ExprEq::eq( + ::Fields::id, + outer.inner) + ), query!(MyModel, $id == outer.inner) ); @@ -253,16 +355,19 @@ fn test_query_rust_expressions() { 30 } assert_eq!( - ::objects().filter(ExprEq::eq( - ::Fields::id, - get_id() - )), + ::objects().filter( + ExprEq::eq( + ::Fields::id, + get_id() + ) + ), query!(MyModel, $id == get_id()) ); assert_eq!( - ::objects() - .filter(::Fields::id.as_expr()), + ::objects().filter( + ::Fields::id.as_expr() + ), query!(MyModel, $id) ); } @@ -273,10 +378,12 @@ fn test_query_path_access() { pub(crate) const ID: i32 = 100; } assert_eq!( - ::objects().filter(ExprEq::eq( - ::Fields::id, - constants::ID - )), + ::objects().filter( + ExprEq::eq( + ::Fields::id, + constants::ID + ) + ), query!(MyModel, $id == constants::ID) ); } From 377b744e537f2d61e7bfac605820ab7ee1d7cd0d Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 15 Apr 2026 06:11:49 +0000 Subject: [PATCH 07/12] Revert "add tests for query macro" This reverts commit f9ae80ce009026d2c6d6874a1303e66b66f3c352. --- cot-macros/src/query.rs | 4 + cot-macros/tests/query.rs | 389 -------------------------------------- 2 files changed, 4 insertions(+), 389 deletions(-) delete mode 100644 cot-macros/tests/query.rs diff --git a/cot-macros/src/query.rs b/cot-macros/src/query.rs index 70d1c884..d832e0d3 100644 --- a/cot-macros/src/query.rs +++ b/cot-macros/src/query.rs @@ -34,6 +34,10 @@ pub(super) fn query_to_tokens(query: Query) -> TokenStream { } pub(super) fn expr_to_tokens(model_name: &syn::Type, expr: Expr) -> TokenStream { + if let Some(tokens) = expr.as_tokens() { + return tokens; + } + let crate_name = cot_ident(); match expr { Expr::FieldRef { field_name, .. } => { diff --git a/cot-macros/tests/query.rs b/cot-macros/tests/query.rs deleted file mode 100644 index 507eb021..00000000 --- a/cot-macros/tests/query.rs +++ /dev/null @@ -1,389 +0,0 @@ -use cot::db::query::{Expr, ExprAdd, ExprDiv, ExprEq, ExprMul, ExprOrd, ExprSub, Query}; -use cot::db::{model, query}; - -#[model] -#[derive(Debug, PartialEq)] -struct MyModel { - #[model(primary_key)] - id: i32, - name: String, - price: i64, - quantity: i64 -} - -#[test] -fn test_query_equality() { - assert_eq!( - Query::::new().filter( - ExprEq::eq( - ::Fields::id, - 5 - ) - ), - query!(MyModel, $id == 5) - ); - - assert_eq!( - Query::::new().filter( - ExprEq::ne( - ::Fields::id, - 5 - ) - ), - query!(MyModel, $id != 5) - ); -} - -#[test] -fn test_query_comparison() { - assert_eq!( - Query::::new().filter( - ExprOrd::lt( - ::Fields::id, - 5 - ) - ), - query!(MyModel, $id < 5) - ); - - assert_eq!( - Query::::new().filter( - ExprOrd::lte( - ::Fields::id, - 5 - ) - ), - query!(MyModel, $id <= 5) - ); - - assert_eq!( - Query::::new().filter( - ExprOrd::gt( - ::Fields::id, - 5 - ) - ), - query!(MyModel, $id > 5) - ); - - assert_eq!( - Query::::new().filter( - ExprOrd::gte( - ::Fields::id, - 5 - ) - ), - query!(MyModel, $id >= 5) - ); - - assert_eq!( - Query::::new().filter( - Expr::and( - ExprEq::eq( - ::Fields::id, - 5 - ), - ExprEq::eq( - ::Fields::name, - "test" - ) - ) - ), - query!(MyModel, $id == 5 && $name == "test") - ); - - assert_eq!( - Query::::new().filter( - Expr::or( - ExprEq::eq( - ::Fields::id, - 5 - ), - ExprEq::eq( - ::Fields::id, - 10 - ) - ) - ), - query!(MyModel, $id == 5 || $id == 10) - ); - - assert_eq!( - Query::::new().filter( - Expr::and( - ExprOrd::gt( - ::Fields::id, - 0 - ), - Expr::or( - ExprEq::eq( - ::Fields::name, - "a" - ), - ExprEq::eq( - ::Fields::name, - "b" - ) - ) - ) - ), - query!(MyModel, $id > 0 && ($name == "a" || $name == "b")) - ); -} - -#[test] -fn test_query_arithmetic() { - assert_eq!( - ::objects().filter( - Expr::eq( - ::Fields::id.as_expr(), - ExprAdd::add( - ::Fields::id, - 5) - ) - ), - query!(MyModel, $id == $id + 5) - ); - - - assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("price"), - Expr::add( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) - ) - ), - query!(MyModel, $price == $quantity + $id) - ); - - assert_eq!( - Query::::new().filter( - Expr::gt( - Expr::add( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - ) - ), - query!(MyModel, $quantity + $id > 11) - ); - - assert_eq!( - Query::::new().filter( - Expr::add( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) - ), - query!(MyModel, $quantity + $id) - ); - - - assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("id"), - ::Fields::id.sub(5) - ) - ), - query!(MyModel, $id == $id - 5) - ); - - assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("price"), - Expr::sub( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) - ) - ), - query!(MyModel, $price == $quantity - $id) - ); - - assert_eq!( - Query::::new().filter( - Expr::gt( - Expr::sub( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - ) - ), - query!(MyModel, $quantity - $id > 11) - ); - - assert_eq!( - ::objects().filter( - ExprSub::sub( - ::Fields::quantity, - 1 - ) - ), - query!(MyModel, $quantity - 1) - ); - - assert_eq!( - ::objects().filter( - Expr::eq( - ::Fields::id.as_expr(), - ExprMul::mul( - ::Fields::id, - 5 - ) - ) - ), - query!(MyModel, $id == $id * 5) - ); - - assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("price"), - Expr::mul( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) - ) - ), - query!(MyModel, $price == $quantity * $id) - ); - - assert_eq!( - ::objects().filter( - Expr::gt( - Expr::mul( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - ) - ), - query!(MyModel, $quantity * $id > 11) - ); - - assert_eq!( - ::objects().filter( - ::cot::db::query::ExprMul::mul( - ::Fields::quantity, - 5i64 - ) - ), - query!(MyModel, $quantity * 5) - ); - - assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("id"), - ::Fields::id.div(5) - ) - ), - query!(MyModel, $id == $id / 5) - ); - - assert_eq!( - Query::::new().filter( - Expr::eq( - Expr::field("price"), - Expr::div( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ) - ) - ), - query!(MyModel, $price == $quantity / $id) - ); - - assert_eq!( - Query::::new().filter( - Expr::gt( - Expr::div( - ::Fields::quantity.as_expr(), - ::Fields::id.as_expr() - ), - Expr::value(11) - ) - ), - query!(MyModel, $quantity / $id > 11) - ); - - assert_eq!( - Query::::new().filter( - Expr::div( - ::Fields::quantity.as_expr(), - Expr::value(1i64) - ) - ), - query!(MyModel, $quantity / 1) - ); - -} - -#[test] -fn test_query_rust_expressions() { - assert_eq!( - ::objects().filter( - ExprEq::eq( - ::Fields::id, - 10 - ) - ), - query!(MyModel, $id == 10) - ); - - struct Outer { - inner: i32, - } - let outer = Outer { inner: 20 }; - assert_eq!( - ::objects().filter( - ExprEq::eq( - ::Fields::id, - outer.inner) - ), - query!(MyModel, $id == outer.inner) - ); - - fn get_id() -> i32 { - 30 - } - assert_eq!( - ::objects().filter( - ExprEq::eq( - ::Fields::id, - get_id() - ) - ), - query!(MyModel, $id == get_id()) - ); - - assert_eq!( - ::objects().filter( - ::Fields::id.as_expr() - ), - query!(MyModel, $id) - ); -} - -#[test] -fn test_query_path_access() { - mod constants { - pub(crate) const ID: i32 = 100; - } - assert_eq!( - ::objects().filter( - ExprEq::eq( - ::Fields::id, - constants::ID - ) - ), - query!(MyModel, $id == constants::ID) - ); -} From 5df92e2464b2a833c68eb13f59108dd526066644 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 15 Apr 2026 06:18:00 +0000 Subject: [PATCH 08/12] add tests for query macro --- cot-macros/tests/query.rs | 282 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 cot-macros/tests/query.rs diff --git a/cot-macros/tests/query.rs b/cot-macros/tests/query.rs new file mode 100644 index 00000000..483a8d91 --- /dev/null +++ b/cot-macros/tests/query.rs @@ -0,0 +1,282 @@ +use cot::db::query::{Expr, ExprAdd, ExprDiv, ExprEq, ExprMul, ExprOrd, ExprSub, Query}; +use cot::db::{model, query}; + +#[model] +#[derive(Debug, PartialEq)] +struct MyModel { + #[model(primary_key)] + id: i32, + name: String, + price: i64, + quantity: i64, +} + +#[test] +fn test_query_equality() { + assert_eq!( + Query::::new().filter(ExprEq::eq(::Fields::id, 5)), + query!(MyModel, $id == 5) + ); + + assert_eq!( + Query::::new().filter(ExprEq::ne(::Fields::id, 5)), + query!(MyModel, $id != 5) + ); +} + +#[test] +fn test_query_comparison() { + assert_eq!( + Query::::new().filter(ExprOrd::lt(::Fields::id, 5)), + query!(MyModel, $id < 5) + ); + + assert_eq!( + Query::::new().filter(ExprOrd::lte(::Fields::id, 5)), + query!(MyModel, $id <= 5) + ); + + assert_eq!( + Query::::new().filter(ExprOrd::gt(::Fields::id, 5)), + query!(MyModel, $id > 5) + ); + + assert_eq!( + Query::::new().filter(ExprOrd::gte(::Fields::id, 5)), + query!(MyModel, $id >= 5) + ); + + assert_eq!( + Query::::new().filter(Expr::and( + ExprEq::eq(::Fields::id, 5), + ExprEq::eq(::Fields::name, "test") + )), + query!(MyModel, $id == 5 && $name == "test") + ); + + assert_eq!( + Query::::new().filter(Expr::or( + ExprEq::eq(::Fields::id, 5), + ExprEq::eq(::Fields::id, 10) + )), + query!(MyModel, $id == 5 || $id == 10) + ); + + assert_eq!( + Query::::new().filter(Expr::and( + ExprOrd::gt(::Fields::id, 0), + Expr::or( + ExprEq::eq(::Fields::name, "a"), + ExprEq::eq(::Fields::name, "b") + ) + )), + query!(MyModel, $id > 0 && ($name == "a" || $name == "b")) + ); +} + +#[test] +fn test_query_arithmetic() { + assert_eq!( + ::objects().filter(Expr::eq( + ::Fields::id.as_expr(), + ExprAdd::add(::Fields::id, 5) + )), + query!(MyModel, $id == $id + 5) + ); + + assert_eq!( + Query::::new().filter(Expr::eq( + Expr::field("price"), + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + )), + query!(MyModel, $price == $quantity + $id) + ); + + assert_eq!( + Query::::new().filter(Expr::gt( + Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + )), + query!(MyModel, $quantity + $id > 11) + ); + + assert_eq!( + Query::::new().filter(Expr::add( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + )), + query!(MyModel, $quantity + $id) + ); + + assert_eq!( + Query::::new().filter(Expr::eq( + Expr::field("id"), + ::Fields::id.sub(5) + )), + query!(MyModel, $id == $id - 5) + ); + + assert_eq!( + Query::::new().filter(Expr::eq( + Expr::field("price"), + Expr::sub( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + )), + query!(MyModel, $price == $quantity - $id) + ); + + assert_eq!( + Query::::new().filter(Expr::gt( + Expr::sub( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + )), + query!(MyModel, $quantity - $id > 11) + ); + + assert_eq!( + ::objects().filter(ExprSub::sub( + ::Fields::quantity, + 1 + )), + query!(MyModel, $quantity - 1) + ); + + assert_eq!( + ::objects().filter(Expr::eq( + ::Fields::id.as_expr(), + ExprMul::mul(::Fields::id, 5) + )), + query!(MyModel, $id == $id * 5) + ); + + assert_eq!( + Query::::new().filter(Expr::eq( + Expr::field("price"), + Expr::mul( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + )), + query!(MyModel, $price == $quantity * $id) + ); + + assert_eq!( + ::objects().filter(Expr::gt( + Expr::mul( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + )), + query!(MyModel, $quantity * $id > 11) + ); + + assert_eq!( + ::objects().filter(::cot::db::query::ExprMul::mul( + ::Fields::quantity, + 5i64 + )), + query!(MyModel, $quantity * 5) + ); + + assert_eq!( + Query::::new().filter(Expr::eq( + Expr::field("id"), + ::Fields::id.div(5) + )), + query!(MyModel, $id == $id / 5) + ); + + assert_eq!( + Query::::new().filter(Expr::eq( + Expr::field("price"), + Expr::div( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ) + )), + query!(MyModel, $price == $quantity / $id) + ); + + assert_eq!( + Query::::new().filter(Expr::gt( + Expr::div( + ::Fields::quantity.as_expr(), + ::Fields::id.as_expr() + ), + Expr::value(11) + )), + query!(MyModel, $quantity / $id > 11) + ); + + assert_eq!( + Query::::new().filter(Expr::div( + ::Fields::quantity.as_expr(), + Expr::value(1i64) + )), + query!(MyModel, $quantity / 1) + ); +} + +#[test] +fn test_query_rust_expressions() { + assert_eq!( + ::objects() + .filter(ExprEq::eq(::Fields::id, 10)), + query!(MyModel, $id == 10) + ); + + struct Outer { + inner: i32, + } + let outer = Outer { inner: 20 }; + assert_eq!( + ::objects().filter(ExprEq::eq( + ::Fields::id, + outer.inner + )), + query!(MyModel, $id == outer.inner) + ); + + fn get_id() -> i32 { + 30 + } + assert_eq!( + ::objects().filter(ExprEq::eq( + ::Fields::id, + get_id() + )), + query!(MyModel, $id == get_id()) + ); + + assert_eq!( + ::objects() + .filter(::Fields::id.as_expr()), + query!(MyModel, $id) + ); +} + +#[test] +fn test_query_path_access() { + mod constants { + pub(crate) const ID: i32 = 100; + } + assert_eq!( + ::objects().filter(ExprEq::eq( + ::Fields::id, + constants::ID + )), + query!(MyModel, $id == constants::ID) + ); +} From 8ccd578e4ad6803fb267b12c037013ad553b8661 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 15 Apr 2026 06:23:08 +0000 Subject: [PATCH 09/12] remove line that skips parsing literals into Expr::value --- cot-macros/src/query.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cot-macros/src/query.rs b/cot-macros/src/query.rs index d832e0d3..70d1c884 100644 --- a/cot-macros/src/query.rs +++ b/cot-macros/src/query.rs @@ -34,10 +34,6 @@ pub(super) fn query_to_tokens(query: Query) -> TokenStream { } pub(super) fn expr_to_tokens(model_name: &syn::Type, expr: Expr) -> TokenStream { - if let Some(tokens) = expr.as_tokens() { - return tokens; - } - let crate_name = cot_ident(); match expr { Expr::FieldRef { field_name, .. } => { From 565f16e0f6dc9c40ad49e3cde297f25d4f6571c2 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 15 Apr 2026 06:33:37 +0000 Subject: [PATCH 10/12] clippy fix --- cot-macros/tests/query.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/cot-macros/tests/query.rs b/cot-macros/tests/query.rs index 483a8d91..b9024057 100644 --- a/cot-macros/tests/query.rs +++ b/cot-macros/tests/query.rs @@ -75,7 +75,7 @@ fn test_query_comparison() { } #[test] -fn test_query_arithmetic() { +fn test_query_add_fields() { assert_eq!( ::objects().filter(Expr::eq( ::Fields::id.as_expr(), @@ -113,7 +113,10 @@ fn test_query_arithmetic() { )), query!(MyModel, $quantity + $id) ); +} +#[test] +fn test_query_sub_fields() { assert_eq!( Query::::new().filter(Expr::eq( Expr::field("id"), @@ -151,7 +154,10 @@ fn test_query_arithmetic() { )), query!(MyModel, $quantity - 1) ); +} +#[test] +fn test_query_mul_fields() { assert_eq!( ::objects().filter(Expr::eq( ::Fields::id.as_expr(), @@ -189,7 +195,10 @@ fn test_query_arithmetic() { )), query!(MyModel, $quantity * 5) ); +} +#[test] +fn test_query_div_fields() { assert_eq!( Query::::new().filter(Expr::eq( Expr::field("id"), @@ -229,6 +238,10 @@ fn test_query_arithmetic() { ); } +struct Outer { + inner: i32, +} + #[test] fn test_query_rust_expressions() { assert_eq!( @@ -237,9 +250,6 @@ fn test_query_rust_expressions() { query!(MyModel, $id == 10) ); - struct Outer { - inner: i32, - } let outer = Outer { inner: 20 }; assert_eq!( ::objects().filter(ExprEq::eq( @@ -249,9 +259,8 @@ fn test_query_rust_expressions() { query!(MyModel, $id == outer.inner) ); - fn get_id() -> i32 { - 30 - } + let get_id = || 30; + assert_eq!( ::objects().filter(ExprEq::eq( ::Fields::id, From 9e3cbf0cc09b8279c3a3e679d9c73fa5dc5dc515 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 15 Apr 2026 06:45:54 +0000 Subject: [PATCH 11/12] update docs based on comments --- cot-macros/src/lib.rs | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/cot-macros/src/lib.rs b/cot-macros/src/lib.rs index d135e606..363f87b0 100644 --- a/cot-macros/src/lib.rs +++ b/cot-macros/src/lib.rs @@ -236,7 +236,11 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// is_active: bool /// } /// -/// let query = query!(Customer, $id == 5 && $full_name == "Jon Doe"); +/// # async fn run(db: Database) -> cot::Result<()> { +/// let customer = query!(Customer, $id > 5 && $full_name == "Jon Doe").await?; +/// println!("Customer: {:?}", customer); +/// # Ok(()) +/// # } /// ``` /// /// In the example above, `$id` and `$full_name` refer to fields on the @@ -256,8 +260,7 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// # id: i32, /// # full_name: String, /// # } -/// let _ = query!(Customer, $id); -/// let _ = query!(Customer, $full_name); +/// let _ = query!(Customer, $id == 5); /// ``` /// /// ## Literal values @@ -312,10 +315,10 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// # stock: i32, /// # quantity: i32, /// # } -/// let _ = query!(Customer, $price + 10); -/// let _ = query!(Customer, $stock - 1); -/// let _ = query!(Customer, $quantity * 2); -/// let _ = query!(Customer, $price / 2); +/// let _ = query!(Customer, $price + 10 > 20); +/// let _ = query!(Customer, $stock - 1 == 20); +/// let _ = query!(Customer, $quantity * 2 < 100); +/// let _ = query!(Customer, $price / 2 != $quantity); /// ``` /// /// ## Rust-side value expressions @@ -352,29 +355,6 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// let _ = query!(Customer, $id == next_customer_id()); /// ``` /// -/// # Executing a query -/// -/// `query!` builds a query. To execute it and retrieve results, call a terminal -/// query method such as [`Query::get`] or [`Query::all`]. -/// -/// ``` -/// use cot::db::{Database, model, query}; -/// -/// #[model] -/// #[derive(Debug, Clone)] -/// struct Customer { -/// #[model(primary_key)] -/// id: i32, -/// full_name: String, -/// } -/// -/// # async fn run(db: Database) -> cot::Result<()> { -/// let customer = query!(Customer, $id == 5).get(&db).await?; -/// println!("Customer: {:?}", customer); -/// # Ok(()) -/// # } -/// ``` -/// /// [`Query`]: query/struct.Query.html /// [`Query::get`]: query/Struct.Query.html#method.get /// [`Query::all`]: query/Struct.Query.html#method.all From 5c3d928dc932ace20a692202642f32329549f4b8 Mon Sep 17 00:00:00 2001 From: Elijah Date: Wed, 15 Apr 2026 07:00:30 +0000 Subject: [PATCH 12/12] fix doc tests --- cot-macros/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cot-macros/src/lib.rs b/cot-macros/src/lib.rs index 363f87b0..3733c337 100644 --- a/cot-macros/src/lib.rs +++ b/cot-macros/src/lib.rs @@ -222,9 +222,10 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// operators. /// /// ``` -/// use cot::db::{model, query}; +/// use cot::db::{Database, model, query}; /// /// #[model] +/// #[derive(Debug, Clone)] /// struct Customer { /// #[model(primary_key)] /// id: i32, @@ -236,8 +237,8 @@ pub fn derive_model_helper(_item: TokenStream) -> TokenStream { /// is_active: bool /// } /// -/// # async fn run(db: Database) -> cot::Result<()> { -/// let customer = query!(Customer, $id > 5 && $full_name == "Jon Doe").await?; +/// # async fn run(db: Database) -> cot::Result<()> { +/// let customer = query!(Customer, $id == 5).get(&db).await?; /// println!("Customer: {:?}", customer); /// # Ok(()) /// # }