Skip to content
157 changes: 157 additions & 0 deletions cot-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,163 @@ 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
/// <Model as cot::db::Model>::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::{Database, model, query};
///
/// #[model]
/// #[derive(Debug, Clone)]
/// struct Customer {
/// #[model(primary_key)]
/// id: i32,
/// full_name: String,
/// status: String,
/// price: i32,
/// stock: i32,
/// quantity: i32,
/// is_active: bool
Comment thread
seqre marked this conversation as resolved.
/// }
///
/// # async fn run(db: Database) -> cot::Result<()> {
/// let customer = query!(Customer, $id == 5).get(&db).await?;
/// println!("Customer: {:?}", customer);
/// # Ok(())
/// # }
/// ```
///
/// 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 == 5);
/// ```
Comment thread
ElijahAhianyo marked this conversation as resolved.
///
/// ## 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
///
/// ```
/// use cot::db::{model, query};
///
/// # #[model]
/// # struct Customer {
/// # #[model(primary_key)]
/// # id: i32,
/// # }
/// let _ = query!(Customer, $id == 5);
/// let _ = query!(Customer, $id != 5);
/// let _ = query!(Customer, $id < 10);
/// let _ = query!(Customer, $id <= 10);
/// let _ = query!(Customer, $id > 5);
/// let _ = query!(Customer, $id >= 5);
/// ```
Comment thread
ElijahAhianyo marked this conversation as resolved.
///
/// ## 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 > 20);
/// let _ = query!(Customer, $stock - 1 == 20);
/// let _ = query!(Customer, $quantity * 2 < 100);
/// let _ = query!(Customer, $price / 2 != $quantity);
/// ```
///
/// ## 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());
/// ```
///
/// [`Query`]: query/struct.Query.html
/// [`Query::get`]: query/Struct.Query.html#method.get
/// [`Query::all`]: query/Struct.Query.html#method.all
Comment on lines +359 to +361
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can always move these docs into the main crate instead to avoid these hardcoded links. Does that make sense?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I get this. Where in the main crate will this be moved?
The model (and other) macro docs live in the cot-macro crate and are then referenced in the main crate. Does this move from that idea?

#[proc_macro]
pub fn query(input: TokenStream) -> TokenStream {
let query_input = parse_macro_input!(input as Query);
Expand Down
4 changes: 0 additions & 4 deletions cot-macros/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Comment on lines -37 to -40
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning behind removing this?

let crate_name = cot_ident();
match expr {
Expr::FieldRef { field_name, .. } => {
Expand Down
Loading
Loading