docs-site/content/docs/how-to/query-data.md
+++ title = "Query data with the condition DSL" description = "Filter entities with ConditionBuilder operators, build date ranges, and sort results — without hand-writing Sea-ORM conditions." date = 2021-05-01T18:10:00+00:00 updated = 2021-05-01T18:10:00+00:00 draft = false weight = 2 sort_by = "weight" template = "docs/page.html"
[extra] lead = "" toc = true top = false +++
Goal: filter rows from a model using Loco's ConditionBuilder fluent DSL instead of hand-assembling a Sea-ORM Condition.
This assumes a working model (see Add a model). For the exhaustive operator list, exact signatures, and the date_range boundary semantics, see Query DSL & pagination.
query is the model-layer query module, reachable straight from the prelude:
use loco_rs::prelude::*;
use sea_orm::EntityTrait;
query::condition() starts a builder; .build() finalizes it into a sea_orm::Condition you pass to .filter(..).
let cond = query::condition()
.eq(users::Column::Email, "[email protected]")
.build();
let user = users::Entity::find().filter(cond).one(&db).await?;
This is the same pattern the demo app's users model uses for all of its lookups, e.g. Model::find_by_email in examples/demo/src/models/users.rs:
pub async fn find_by_email(db: &DatabaseConnection, email: &str) -> ModelResult<Self> {
let user = users::Entity::find()
.filter(
model::query::condition()
.eq(users::Column::Email, email)
.build(),
)
.one(db)
.await?;
user.ok_or_else(|| ModelError::EntityNotFound)
}
Every operator returns Self, so conditions chain and AND together by default:
let cond = query::condition()
.contains(posts::Column::Title, "loco")
.gt(posts::Column::Views, 10)
.is_not_null(posts::Column::PublishedAt)
.build();
let published = posts::Entity::find().filter(cond).all(&db).await?;
Available operators (see the reference for the full table): eq/ne, gt/gte/lt/lte, between/not_between, like/not_like, starts_with/ends_with/contains, is_null/is_not_null, is_in/is_not_in, and date_range. Each also exists as a free function (query::eq(col, v)) that starts a new builder — useful when you only need one condition.
date_range returns a DateRangeBuilder instead of Self, because a range can have zero, one, or two bounds:
use chrono::NaiveDateTime;
let from: NaiveDateTime = /* ... */;
let to: NaiveDateTime = /* ... */;
let cond = query::condition()
.date_range(posts::Column::CreatedAt)
.dates(Some(&from), Some(&to))
.build() // DateRangeBuilder -> ConditionBuilder
.build(); // ConditionBuilder -> Condition
SortDirection is a small serde-friendly enum that converts to Sea-ORM's Order — it's orthogonal to ConditionBuilder, meant to pair with .order_by():
use loco_rs::model::query::SortDirection;
let direction = SortDirection::Desc;
let recent = posts::Entity::find()
.filter(cond)
.order_by(posts::Column::CreatedAt, direction.order())
.all(&db)
.await?;
Because SortDirection derives Deserialize/Serialize with "asc"/"desc" renames, it deserializes directly from a query-string parameter (e.g. ?sort=desc in a controller's Query<T> extractor).
You have a Condition built from readable, chainable method calls instead of raw Sea-ORM Condition::all().add(...) boilerplate, and it composes with .filter(), .order_by(), and — as the next guide shows — query::paginate.