Skip to content
loco
v1.0
★ 6.9k Get started

Query data with the condition DSL

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.

1. Import the DSL

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(..).

2. Build a simple filter

let cond = query::condition()
.eq(users::Column::Email, "user1@example.com")
.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)
}

3. Chain multiple conditions

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.

4. Filter a date range

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
Boundary behavior is asymmetric: a single-ended range (only from or only to) is strict (> / <), but a double-ended range (both from and to) is inclusive (BETWEEN). See the reference for the full table.

5. Sort results

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).

Result

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.

Next