ORM & Database

CRUD Queries

SELECT — Retrieve

// All records
let users: Vec<users::Model> = users::Entity::objects
    .all()
    .all(&*db)
    .await?;

// With limit and offset
let users = users::Entity::objects
    .all()
    .limit(10)
    .offset(0)
    .all(&*db)
    .await?;

// With ordering
let users = users::Entity::objects
    .all()
    .order_by_asc(users::Column::Name)
    .all(&*db)
    .await?;

COUNT — Count records

let count = users::Entity::objects
    .filter(users::Column::Active.eq(true))
    .count(&*db)
    .await?;

WHERE — Filtering (native SeaORM)

use sea_orm::ColumnTrait;

// Equality
let user = users::Entity::objects
    .filter(users::Column::Email.eq("test@example.com"))
    .first(&*db)
    .await?;

// Comparisons
let users = users::Entity::objects
    .filter(users::Column::Age.gt(18))
    .all(&*db)
    .await?;

// Multiple conditions (AND)
let users = users::Entity::objects
    .filter(users::Column::Active.eq(true))
    .filter(users::Column::Age.gte(18))
    .all(&*db)
    .await?;

// OR
use sea_orm::Condition;
let users = users::Entity::objects
    .filter(
        Condition::any()
            .add(users::Column::Email.eq("a@test.com"))
            .add(users::Column::Email.eq("b@test.com"))
    )
    .all(&*db)
    .await?;

`search!` macro — Filter DSL

The search! macro provides a Django-inspired syntax for building SeaORM filters. It returns a chainable RuniqueQueryBuilder (.limit(), .order_by_asc(), .all(), etc.).

Full reference table

SyntaxGenerated SQLDjango equivalent
search!(Entity)(no filter).objects.all()
Col eq valWHERE col = valfilter(col=val)
Col exact valWHERE col = valfilter(col__exact=val)
Col ne valWHERE col != val
Col gt valWHERE col > valfilter(col__gt=val)
Col lt valWHERE col < valfilter(col__lt=val)
Col gte valWHERE col >= valfilter(col__gte=val)
Col lte valWHERE col <= valfilter(col__lte=val)
Col like valWHERE col LIKE val
Col ilike valWHERE col ILIKE val
Col not_like valWHERE col NOT LIKE val
Col not_ilike valWHERE col NOT ILIKE val
Col contains valWHERE col LIKE '%val%'filter(col__contains=val)
Col icontains valWHERE col ILIKE '%val%'filter(col__icontains=val)
Col startswith valWHERE col LIKE 'val%'filter(col__startswith=val)
Col endswith valWHERE col LIKE '%val'filter(col__endswith=val)
Col iexact valWHERE col ILIKE valfilter(col__iexact=val)
Col isnullWHERE col IS NULLfilter(col__isnull=True)
Col not_nullWHERE col IS NOT NULLfilter(col__isnull=False)
Col in [v1, v2]WHERE col IN (v1, v2) (literal)filter(col__in=[v1, v2])
Col not_in [v1, v2]WHERE col NOT IN (v1, v2)exclude(col__in=[v1, v2])
Col in (expr)WHERE col IN (...) (Vec/iterator)filter(col__in=qs)
Col not_in (expr)WHERE col NOT IN (...)exclude(col__in=qs)
Col range (a, b)WHERE col BETWEEN a AND bfilter(col__range=(a, b))
Col not_range (a, b)WHERE col NOT BETWEEN a AND b
! Col op valexclusion (NOT).exclude(col__op=val)
or(C1 op v, C2 op v)WHERE c1 op v OR c2 op vQ(c1__op=v) | Q(c2__op=v)

Fetch all

let all = search!(users::Entity)
    .order_by_asc(users::Column::Name)
    .all(&*db).await?;

Basic operators

use runique::search;

let active     = search!(users::Entity => Active eq true).all(&*db).await?;
let adults     = search!(users::Entity => Age gte 18).all(&*db).await?;
let non_admins = search!(users::Entity => ! Level eq 99).all(&*db).await?;

// LIKE / ILIKE / NOT LIKE / NOT ILIKE
let rust        = search!(users::Entity => Bio like "%rust%").all(&*db).await?;
let rust_ci     = search!(users::Entity => Bio ilike "%rust%").all(&*db).await?;
let not_rust    = search!(users::Entity => Bio not_like "%rust%").all(&*db).await?;
let not_rust_ci = search!(users::Entity => Bio not_ilike "%rust%").all(&*db).await?;

// NULL
let no_bio   = search!(users::Entity => Bio isnull).all(&*db).await?;
let with_bio = search!(users::Entity => Bio not_null).all(&*db).await?;

contains / icontains / startswith / endswith / iexact

These operators handle % wildcards automatically.

let rust    = search!(posts::Entity => Title contains "rust").all(&*db).await?;
let rust_ci = search!(posts::Entity => Title icontains "rust").all(&*db).await?;
let hello   = search!(posts::Entity => Title startswith "Hello").all(&*db).await?;
let rs      = search!(posts::Entity => Filename endswith ".rs").all(&*db).await?;
let user    = search!(users::Entity => Username iexact "Alice").first(&*db).await?;

IN / NOT IN

// Literal IN
let selected = search!(users::Entity => Id in [1, 2, 3]).all(&*db).await?;

// Literal NOT IN
let others = search!(users::Entity => Status not_in ["banned", "deleted"]).all(&*db).await?;

// Dynamic IN (Vec, iterator)
let ids: Vec<i32> = get_allowed_ids();
let users = search!(users::Entity => Id in (ids)).all(&*db).await?;

// Dynamic NOT IN
let blocked: Vec<i32> = get_blocked_ids();
let clean = search!(users::Entity => Id not_in (blocked)).all(&*db).await?;

BETWEEN / NOT BETWEEN

let mid     = search!(users::Entity => Age range (18, 30)).all(&*db).await?;
let outside = search!(users::Entity => Age not_range (18, 30)).all(&*db).await?;

OR across columns — or(...)

// Search across multiple columns
let results = search!(posts::Entity => or(Title icontains "rust", Content icontains "rust"))
    .all(&*db).await?;

// With a runtime variable
let results = search!(posts::Entity => or(Title icontains term, Summary icontains term))
    .all(&*db).await?;

Multiple conditions (chained AND)

Separate conditions with commas — each one is an AND.

let results = search!(users::Entity =>
    Active eq true,
    Age gte 18,
    Role in ["admin", "moderator"],
)
.order_by_desc(users::Column::CreatedAt)
.limit(20)
.all(&*db)
.await?;

All operators work in multi-condition mode:

search!(users::Entity =>
    Role in ["admin", "moderator"],         // literal IN
    Id in (dynamic_ids),                    // dynamic IN
    CreatedAt range (date_a, date_b),       // BETWEEN
    Bio not_null,                            // IS NOT NULL
    Username icontains "alice",             // ILIKE %alice%
    or(Title icontains q, Bio icontains q), // OR across columns
)

.into_select() — partial projection

For cases where select_only(), column(), distinct() or into_tuple() are needed, .into_select() exposes the underlying SeaORM Select<E>.

// Retrieve a single distinct column
let difficulties: Vec<String> = search!(course::Entity => Lang eq "en")
    .into_select()
    .select_only()
    .column(course::Column::Difficulty)
    .distinct()
    .into_tuple::<String>()
    .all(db.as_ref())  // ← .as_ref() required here
    .await?;

Note — .as_ref() required: once outside RuniqueQueryBuilder, SeaORM methods use a generic bound C: ConnectionTrait. Arc<DatabaseConnection> does not satisfy this bound directly — db.as_ref() explicitly converts to &DatabaseConnection. RuniqueQueryBuilder methods (.all(), .first(), etc.) do not have this issue because they take &DatabaseConnection as a concrete parameter.

Via @Form — FormEntity

If the form is linked to an entity via #[form(model = ...)], use @Form as a shorthand:

#[form(schema = user_schema, model = users::Entity)]
pub struct UserForm;

// Identical to search!(users::Entity => ...)
let active = search!(@UserForm => Active = true)
    .all(&*db).await?;

// All syntaxes are supported
search!(@UserForm =>
    +Role = ["admin", "moderator"],
    Age >= 18,
)
.limit(10)
.all(&*db)
.await?;

INSERT — Create

use sea_orm::Set;

let new_user = users::ActiveModel {
    email: Set("john@example.com".to_string()),
    username: Set("john".to_string()),
    password: Set(hash_password("password123")),
    ..Default::default()
};

let user = new_user.insert(&*db).await?;

UPDATE — Modify

use sea_orm::{Set, Unchanged};

let mut user = users::Entity::find_by_id(1)
    .one(&*db)
    .await?
    .ok_or("User not found")?;

let mut user = user.into_active_model();
user.email = Set("newemail@example.com".to_string());

let updated = user.update(&*db).await?;

DELETE — Remove

// Delete single record
let result = users::Entity::delete_by_id(1)
    .exec(&*db)
    .await?;

// Delete multiple records
let result = users::Entity::delete_many()
    .filter(users::Column::Active.eq(false))
    .exec(&*db)
    .await?;