pub struct QuerySet<'a, E: EntityTrait, C: ConnectionTrait, S: QuerySetState = Fresh> { /* private fields */ }Expand description
Main QuerySet structure (Ormada’s QuerySet equivalent)
Provides chainable query building with automatic caching and lazy evaluation.
All operations are lazy until a terminal method (.all(), .first(), etc.) is called.
Caching Behavior (Django-like):
- First execution of
.all(),.first(), etc. hits the database - Results are cached in the
QuerySetinstance - Subsequent calls on the SAME
QuerySetreuse cached results - Building new queries (
.filter(),.limit()) creates newQuerySetwith separate cache
Concurrency Safety:
- Uses
Arcfor cheap cloning across async tasks - Uses
parking_lot::RwLockfor fast, synchronous cache access - Safe to share across threads and async tasks
§Type Parameters
E: TheSeaORMEntity typeC: The database connection typeS: The typestate marker (defaults toFresh)
§Typestate Pattern
The S parameter tracks the query building state at compile time:
Fresh→ initial state, can filter, order, or executeFiltered→ has filters, can add more filters, order, or executeOrdered→ has ordering, can paginate or executePaginated→ has limit/offset, can executeAggregated→ has aggregations, can execute
Methods transition between states, preventing invalid operations at compile time.
§Examples
// Build query with typestate
let queryset = Book::objects(db) // QuerySet<_, _, Fresh>
.filter(Book::Published.eq(true)) // QuerySet<_, _, Filtered>
.order_by_asc(Book::Title) // QuerySet<_, _, Ordered>
.limit(10); // QuerySet<_, _, Paginated>
// Execute the query
let books = queryset.all().await?;Implementations§
Source§impl<'a, E: EntityTrait, C: ConnectionTrait> QuerySet<'a, E, C, Fresh>
impl<'a, E: EntityTrait, C: ConnectionTrait> QuerySet<'a, E, C, Fresh>
Source§impl<'a, E: EntityTrait, C: ConnectionTrait, S: QuerySetState> QuerySet<'a, E, C, S>
impl<'a, E: EntityTrait, C: ConnectionTrait, S: QuerySetState> QuerySet<'a, E, C, S>
Sourcepub fn state(&self) -> QueryState
pub fn state(&self) -> QueryState
Sourcepub fn plan(&self) -> QueryPlan
pub fn plan(&self) -> QueryPlan
Get the query plan for introspection
Returns the query plan generated from stored operations, allowing you to inspect all operations that will be applied when the query is executed.
§Example
let plan = Book::objects(db)
.filter(Book::Price.lt(50))
.limit(10)
.plan();
for op in plan.iter() {
println!("{:?}", op);
}Source§impl<'a, E: EntityTrait, C: ConnectionTrait, S: CanFilter> QuerySet<'a, E, C, S>
impl<'a, E: EntityTrait, C: ConnectionTrait, S: CanFilter> QuerySet<'a, E, C, S>
Sourcepub fn filter(
&self,
condition: impl Into<Condition>,
) -> QuerySet<'a, E, C, Filtered>
pub fn filter( &self, condition: impl Into<Condition>, ) -> QuerySet<'a, E, C, Filtered>
Filter records (Ormada’s .filter())
Creates a new QuerySet with added filter. Transitions to Filtered state.
Available on Fresh and Filtered states.
§Typestate
- Input:
QuerySet<Fresh>orQuerySet<Filtered> - Output:
QuerySet<Filtered>
Sourcepub fn exclude(
&self,
condition: impl Into<Condition>,
) -> QuerySet<'a, E, C, Filtered>
pub fn exclude( &self, condition: impl Into<Condition>, ) -> QuerySet<'a, E, C, Filtered>
Exclude records (Ormada’s .exclude())
Creates a new QuerySet with added exclusion. Transitions to Filtered state.
Available on Fresh and Filtered states.
§Typestate
- Input:
QuerySet<Fresh>orQuerySet<Filtered> - Output:
QuerySet<Filtered>
Source§impl<E: EntityTrait, C: ConnectionTrait, S: QuerySetState> QuerySet<'_, E, C, S>
impl<E: EntityTrait, C: ConnectionTrait, S: QuerySetState> QuerySet<'_, E, C, S>
Sourcepub fn with_deleted(&self) -> Self
pub fn with_deleted(&self) -> Self
Sourcepub fn only_deleted(&self) -> Self
pub fn only_deleted(&self) -> Self
Sourcepub fn distinct(&self) -> Self
pub fn distinct(&self) -> Self
Remove duplicate rows (Ormada’s .distinct())
Returns only unique records. Useful when joins might create duplicates.
§Examples
// Get unique book titles (no duplicates)
let books = Book::objects(db)
.distinct()
.all()
.await?;
// Combined with filters
let unique_authors = Book::objects(db)
.filter(Book::Published.eq(true))
.distinct()
.all()
.await?;§SQL
Generates: SELECT DISTINCT * FROM ...
§Performance
DISTINCT can be expensive on large datasets. Use only when necessary.
Source§impl<'a, E: EntityTrait, C: ConnectionTrait, S: CanOrder> QuerySet<'a, E, C, S>
impl<'a, E: EntityTrait, C: ConnectionTrait, S: CanOrder> QuerySet<'a, E, C, S>
Sourcepub fn order_by_asc(
&self,
column: impl ColumnTrait,
) -> QuerySet<'a, E, C, Ordered>
pub fn order_by_asc( &self, column: impl ColumnTrait, ) -> QuerySet<'a, E, C, Ordered>
Order by a column in ascending order (Ormada’s .order_by(‘field’))
Transitions to Ordered state. Available on Fresh, Filtered, and Ordered states.
§Typestate
- Input:
QuerySet<Fresh>,QuerySet<Filtered>, orQuerySet<Ordered> - Output:
QuerySet<Ordered>
§Examples
// Order by price (lowest first)
let books = Book::objects(db)
.order_by_asc(Book::Price)
.all()
.await?;Sourcepub fn order_by_desc(
&self,
column: impl ColumnTrait,
) -> QuerySet<'a, E, C, Ordered>
pub fn order_by_desc( &self, column: impl ColumnTrait, ) -> QuerySet<'a, E, C, Ordered>
Order by a column in descending order (Ormada’s .order_by(‘-field’))
Transitions to Ordered state. Available on Fresh, Filtered, and Ordered states.
§Typestate
- Input:
QuerySet<Fresh>,QuerySet<Filtered>, orQuerySet<Ordered> - Output:
QuerySet<Ordered>
§Examples
// Order by price (highest first)
let books = Book::objects(db)
.order_by_desc(Book::Price)
.all()
.await?;Source§impl<'a, E: EntityTrait, C: ConnectionTrait, S: CanPaginate> QuerySet<'a, E, C, S>
impl<'a, E: EntityTrait, C: ConnectionTrait, S: CanPaginate> QuerySet<'a, E, C, S>
Sourcepub fn limit(&self, limit: u64) -> QuerySet<'a, E, C, Paginated>
pub fn limit(&self, limit: u64) -> QuerySet<'a, E, C, Paginated>
Limit results (Ormada’s [:n])
Transitions to Paginated state. Available on Fresh, Filtered, Ordered, and Paginated states.
§Typestate
- Input:
QuerySet<Fresh>,QuerySet<Filtered>,QuerySet<Ordered>, orQuerySet<Paginated> - Output:
QuerySet<Paginated>
Sourcepub fn offset(&self, offset: u64) -> QuerySet<'a, E, C, Paginated>
pub fn offset(&self, offset: u64) -> QuerySet<'a, E, C, Paginated>
Offset results
Transitions to Paginated state. Available on Fresh, Filtered, Ordered, and Paginated states.
§Typestate
- Input:
QuerySet<Fresh>,QuerySet<Filtered>,QuerySet<Ordered>, orQuerySet<Paginated> - Output:
QuerySet<Paginated>
Source§impl<E: EntityTrait, C: ConnectionTrait + Sync, S: CanExplain> QuerySet<'_, E, C, S>
impl<E: EntityTrait, C: ConnectionTrait + Sync, S: CanExplain> QuerySet<'_, E, C, S>
Sourcepub async fn explain(&self, pretty: bool) -> Result<String, OrmadaError>where
E: OrmadaEntity,
pub async fn explain(&self, pretty: bool) -> Result<String, OrmadaError>where
E: OrmadaEntity,
Get query execution plan (Django-inspired .explain())
Executes the EXPLAIN query and returns the database query execution plan. SQL is pretty-printed by default for readability. Useful for understanding how the database will execute your query and identifying performance bottlenecks.
§Arguments
pretty- Whether to pretty-print the SQL (default: true). Set to false for single-line output.
§Examples
// Check query plan for a complex filter (pretty-printed)
let plan = User::objects(&db)
.filter(User::Email.contains("@gmail.com"))
.filter(User::Age.gte(18))
.explain(true)
.await?;
println!("Execution Plan:\n{}", plan);§Performance Analysis
Look for these indicators in the plan:
- Index Scan: Good - using an index
- Sequential Scan: Bad - scanning entire table
- Nested Loop: Can be slow for large joins
- Hash Join: Usually faster for large datasets
§Database Support
SQLite:EXPLAIN QUERY PLANPostgreSQL:EXPLAINMySQL:EXPLAIN
§See Also
.explain_analyze()- Runs query and provides actual timings.debug_sql()- Shows the raw SQL query without executing
Sourcepub async fn explain_analyze(&self, pretty: bool) -> Result<String, OrmadaError>where
E: OrmadaEntity,
pub async fn explain_analyze(&self, pretty: bool) -> Result<String, OrmadaError>where
E: OrmadaEntity,
Analyze query with actual execution (Django-inspired .explain(analyze=True))
Executes the EXPLAIN ANALYZE query and returns detailed execution statistics including actual row counts, execution time, and resource usage. SQL is pretty-printed by default for readability.
⚠️ WARNING: This actually EXECUTES the query, so use carefully on production databases with large datasets.
§Arguments
pretty- Whether to pretty-print the SQL (default: true). Set to false for single-line output.
§Examples
// Analyze actual query performance (pretty-printed)
let analysis = Book::objects(&db)
.filter(Book::Published.eq(true))
.explain_analyze(true)
.await?;
println!("Execution Analysis:\n{}", analysis);§What You Get
- Estimated vs Actual rows: Are estimates accurate?
- Execution time: How long did each step take?
- Buffer usage: Cache hits/misses
- Sort operations: Memory vs disk sorting
§Performance Tips
If you see:
- High actual rows: Consider pagination/limits
- Sequential scans: Add indexes
- Slow sorts: Index the ORDER BY columns
- Many disk buffer reads: Increase
shared_buffers(PostgreSQL)
§Database Support
SQLite: Limited - returns EXPLAIN QUERY PLAN (no ANALYZE support)PostgreSQL:EXPLAIN ANALYZE- full statisticsMySQL:EXPLAIN ANALYZE(MySQL8.0.18+)
Source§impl<'a, E, C: ConnectionTrait + Sync, S: CanExecute + 'a> QuerySet<'a, E, C, S>
impl<'a, E, C: ConnectionTrait + Sync, S: CanExecute + 'a> QuerySet<'a, E, C, S>
Sourcepub async fn all(&self) -> Result<Vec<E::Model>, OrmadaError>where
E: OrmadaEntity,
pub async fn all(&self) -> Result<Vec<E::Model>, OrmadaError>where
E: OrmadaEntity,
Execute query and return all matching results (Ormada’s .all())
Returns a vector of all models that match the query filters.
§Returns
Ok(Vec<E::Model>)- Vector of matching models (may be empty)Err(OrmadaError)- Database error occurred
§Examples
// Get all books
let all_books = Book::objects(db).all().await?;
println!("Found {} books", all_books.len());
// Get filtered books
let published = Book::objects(db)
.filter(Column::Published.eq(true))
.all()
.await?;
// Empty result is NOT an error
let no_books = Book::objects(db)
.filter(Column::Title.eq("Nonexistent"))
.all()
.await?;
assert_eq!(no_books.len(), 0); // Returns empty vec, not error§Caching
First call - Executes SQL query and caches results:
let qs = Book::objects(db).filter(Book::Published.eq(true));
let books = qs.all().await?; // DB query executedSecond call on same QuerySet - Returns cached results (no DB query):
let books_again = qs.all().await?; // Cache hit! No DB querySourcepub async fn first(&self) -> Result<E::Model, OrmadaError>where
E: OrmadaEntity,
pub async fn first(&self) -> Result<E::Model, OrmadaError>where
E: OrmadaEntity,
Execute query and return first result (Ormada’s .first())
Returns the first matching model or error if no matches found. Useful with ordering to get the “latest” or “oldest” record.
§Returns
Ok(E::Model)- First matching model foundErr(OrmadaError::EmptyResult { .. })- No matching modelsErr(OrmadaError::Database(_))- Database error occurred
§Examples
// Get first book (errors if empty)
let book = Book::objects(db).first().await?;
println!("First book: {}", book.title);
// Get latest published book
let latest = Book::objects(db)
.filter(Column::Published.eq(true))
.order_by_desc(Column::CreatedAt)
.first()
.await?;
println!("Latest: {}", latest.title);
// Handle no results
match Book::objects(db).first().await {
Ok(book) => println!("Found: {}", book.title),
Err(OrmadaError::EmptyResult { .. }) => {
println!("No books in database");
}
Err(e) => return Err(e),
}
// Get oldest record
let oldest = Book::objects(db)
.order_by_asc(Column::CreatedAt)
.first()
.await?;§Caching
Uses the same cache as .all(). If cache exists, returns first element.
Sourcepub async fn last(&self) -> Result<E::Model, OrmadaError>where
E: OrmadaEntity,
pub async fn last(&self) -> Result<E::Model, OrmadaError>where
E: OrmadaEntity,
Execute query and return last result
Returns the last matching model or error if no matches found. Orders by primary key descending and returns the first result.
§Returns
Ok(E::Model)- Last matching model foundErr(OrmadaError::NotFound { .. })- No matching modelsErr(OrmadaError::Database(_))- Database error occurred
§Examples
// Get last book (by primary key)
let book = Book::objects(db).last().await?;
println!("Last book: {}", book.title);
// Handle no results
match Book::objects(db).last().await {
Ok(book) => println!("Last: {}", book.title),
Err(OrmadaError::NotFound { .. }) => {
println!("No books found");
}
Err(e) => return Err(e),
}§Note
This method orders by primary key descending to efficiently get the last record.
If you need the last record based on a different ordering, use
.order_by_desc(field).first() instead.
Sourcepub async fn get<T>(&self, id: T) -> Result<E::Model, OrmadaError>
pub async fn get<T>(&self, id: T) -> Result<E::Model, OrmadaError>
Get a single record by primary key (Ormada’s .get(pk=))
Returns the model or error if not found. This matches Ormada’s behavior
where .get() raises DoesNotExist if the record doesn’t exist.
§Returns
Ok(E::Model)- Record foundErr(OrmadaError::NotFound { entity, id })- No record with that IDErr(OrmadaError::Database(_))- Database error occurred
§Examples
// Simple get
let book = Book::objects(db).get(1).await?;
println!("Found: {}", book.title);
// Handle not found
match Book::objects(db).get(999).await {
Ok(book) => println!("Found: {}", book.title),
Err(OrmadaError::NotFound { entity, id }) => {
println!("{} with id {} doesn't exist", entity, id);
}
Err(e) => return Err(e), // Other error
}
// Or use ? for early return on not foundSourcepub async fn earliest(
&self,
column: impl ColumnTrait,
) -> Result<E::Model, OrmadaError>
pub async fn earliest( &self, column: impl ColumnTrait, ) -> Result<E::Model, OrmadaError>
Get the earliest record by a field (Ormada’s .earliest())
Orders by the specified column ascending and returns the first record. Returns an error if no records exist.
§Examples
// Get oldest book by creation date
let oldest = Book::objects(db)
.earliest(Book::CreatedAt)
.await?;
// With filters
let first_published = Book::objects(db)
.filter(Book::Published.eq(true))
.earliest(Book::PublishedDate)
.await?;§Returns
Ok(Model)- The earliest recordErr(OrmadaError::EmptyResult { .. })- No records foundErr(OrmadaError::Database)- Database error
§Equivalent to
.order_by_asc(column).first() but returns error on empty result
Sourcepub async fn latest(
&self,
column: impl ColumnTrait,
) -> Result<E::Model, OrmadaError>
pub async fn latest( &self, column: impl ColumnTrait, ) -> Result<E::Model, OrmadaError>
Get the latest record by a field (Ormada’s .latest())
Orders by the specified column descending and returns the first record. Returns an error if no records exist.
§Examples
// Get newest book
let newest = Book::objects(db)
.latest(Book::CreatedAt)
.await?;
// With filters
let latest_published = Book::objects(db)
.filter(Book::Published.eq(true))
.latest(Book::PublishedDate)
.await?;
// Get most expensive book
let most_expensive = Book::objects(db)
.latest(Book::Price)
.await?;§Returns
Ok(Model)- The latest recordErr(OrmadaError::EmptyResult { .. })- No records foundErr(OrmadaError::Database)- Database error
§Equivalent to
.order_by_desc(column).first() but returns error on empty result
Sourcepub async fn count(&self) -> Result<u64, OrmadaError>where
E: OrmadaEntity,
pub async fn count(&self) -> Result<u64, OrmadaError>where
E: OrmadaEntity,
Count records matching the query (Ormada’s .count())
Returns the number of records that match the query filters. Returns 0 if no records match (not an error).
§Returns
Ok(u64)- Number of matching records (0 or more)Err(OrmadaError)- Database error occurred
§Examples
// Count all books
let total = Book::objects(db).count().await?;
println!("Total books: {}", total);
// Count with filter
let published = Book::objects(db)
.filter(Column::Published.eq(true))
.count()
.await?;
println!("Published books: {}", published);
// Zero count is NOT an error
let drafts = Book::objects(db)
.filter(Column::Status.eq("draft"))
.count()
.await?;
if drafts == 0 {
println!("No drafts found"); // Not an error!
}
// Use in conditional logic
if Book::objects(db).count().await? > 100 {
println!("Large database!");
}§Performance
This uses a SQL COUNT(*) query which is optimized by the database.
Much faster than loading all records and counting in memory.
Sourcepub async fn exists(&self) -> Result<bool, OrmadaError>where
E: OrmadaEntity,
pub async fn exists(&self) -> Result<bool, OrmadaError>where
E: OrmadaEntity,
Check if any records exist matching the query (Ormada’s .exists())
Returns true if at least one record matches the query, false otherwise.
More efficient than .count() > 0 because it stops at the first match.
§Returns
Ok(true)- At least one matching record existsOk(false)- No matching records (NOT an error)Err(OrmadaError)- Database error occurred
§Examples
// Check if any books exist
if Book::objects(db).exists().await? {
println!("We have books!");
} else {
println!("Database is empty"); // Not an error
}
// Check with filter
let has_published = Book::objects(db)
.filter(Column::Published.eq(true))
.exists()
.await?;
if !has_published {
println!("No published books yet");
}
// Use in validation
if Book::objects(db)
.filter(Column::Title.eq(&title))
.exists()
.await?
{
return Err("Book with this title already exists".into());
}§Performance
Uses LIMIT 1 internally, so it stops as soon as it finds any match.
This is much faster than counting all records when you just need to know
if any exist.
Sourcepub async fn update<F, Fut>(self, updater: F) -> Result<u64, OrmadaError>where
F: Fn(E::Model) -> Fut,
Fut: Future<Output = Result<E::Model, OrmadaError>>,
E: OrmadaEntity,
C: TransactionTrait,
pub async fn update<F, Fut>(self, updater: F) -> Result<u64, OrmadaError>where
F: Fn(E::Model) -> Fut,
Fut: Future<Output = Result<E::Model, OrmadaError>>,
E: OrmadaEntity,
C: TransactionTrait,
Update all records matching the query (Ormada’s .update())
Applies the same updates to all matching records using an async closure. Returns the number of records updated.
Async Support: The closure receives the model by value and returns a future that produces the modified model. This allows async operations like FK lookups.
Concurrency Safe: Uses SELECT FOR UPDATE to lock rows before modification, preventing lost updates in concurrent scenarios. All updates succeed or all fail together within a transaction.
§Example
// Simple update - just modify fields
let count = Book::objects(db)
.filter(Book::AuthorId.eq(1))
.update(|mut book| async move {
book.status = "archived".to_string();
Ok(book)
})
.await?;
// Update with async FK lookup
let count = Book::objects(db)
.filter(Book::Id.eq(book_id))
.update(|mut book| async move {
// Async operations supported!
if let Some(author_name) = &update_dto.author_name {
let (author, _) = Author::objects(db)
.filter(Author::Name.eq(author_name))
.get_or_create(|| async {
Ok(Author { name: author_name.clone(), ..Default::default() })
})
.await?;
book.author_id = author.id;
}
Ok(book)
})
.await?;Eager load related entities (Ormada’s prefetch_related)
Transforms this QuerySet into a QuerySetEager that supports prefetching relations.
This prevents N+1 queries by loading all relations in batched queries (1+M pattern).
§Usage
Use the relations! macro to specify which entity types to prefetch:
use ormada::relations;
let books = Book::objects(db)
.prefetch_related(relations![Author, Publisher])
.all()
.await?;The macro expands to vec![TypeId::of::<Author>(), TypeId::of::<Publisher>()]
which the registry uses for type-safe runtime dispatch to relation loaders.
§Examples
use ormada::relations;
use entity::{
book::Entity as Book,
author::Entity as Author,
publisher::Entity as Publisher,
};
// Multiple relations
let books = Book::objects(db)
.filter(Column::Published.eq(true))
.prefetch_related(relations![Author, Publisher])
.all()
.await?;
// Single relation
let books = Book::objects(db)
.prefetch_related(relations![Author])
.all()
.await?;
// With formatting (trailing comma optional)
let books = Book::objects(db)
.prefetch_related(relations![
Author,
Publisher,
Category,
])
.all()
.await?;
// Access loaded relations
for book in books {
println!("Title: {}", book.title);
if let Some(author) = book.author {
println!("Author: {}", author.name);
}
if let Some(publisher) = book.publisher {
println!("Publisher: {}", publisher.name);
}
}
// Single record with relations
let book = Book::objects(db)
.prefetch_related(relations![Author, Publisher])
.get(1)
.await?;§Query Execution Pattern
This executes 1+M queries where M is the number of relation types:
- 1 query for the main entities
- 1 query per relation type (batch loaded, NOT per record!)
Example with 2 relations loading 100 books:
-- Query 1: Load 100 books (1 query)
SELECT * FROM book WHERE published = true LIMIT 100;
-- Query 2: Batch load ALL authors for those books (1 query, not 100!)
SELECT * FROM author WHERE id IN (1, 2, 3, ..., 50);
-- Query 3: Batch load ALL publishers (1 query, not 100!)
SELECT * FROM publisher WHERE id IN (10, 11, 12, ..., 30);Total: 3 queries regardless of how many books you load. NOT 1 + (100 authors) + (100 publishers) = 201 queries! ✅
§Empty Results
If no models match the query, no relation queries are executed:
// No books found = only 1 query executed (the main query)
let books = Book::objects(db)
.filter(Column::Title.eq("Nonexistent"))
.prefetch_related(relations![Author])
.all()
.await?;
assert_eq!(books.len(), 0); // Empty, no error, no extra queries§Alternative: Raw Vec (Not Recommended)
You can also pass a raw Vec of TypeIds, but the macro is cleaner:
use std::any::TypeId;
// Verbose version (not recommended)
let books = Book::objects(db)
.prefetch_related(vec![
TypeId::of::<Author>(),
TypeId::of::<Publisher>(),
])
.all()
.await?;
// Clean version (recommended)
let books = Book::objects(db)
.prefetch_related(relations![Author, Publisher])
.all()
.await?;Eager load related entities using SQL JOIN (Django’s select_related)
Uses a single SQL query with LEFT JOIN to fetch the parent entity and its
related entities together. This is more efficient than prefetch_related for
many-to-one (FK) and one-to-one relations.
§Returns
Returns Vec<ModelWithRelations> - same as prefetch_related for unified UX.
§Usage
use ormada::relations;
// Single query with JOIN - same UX as prefetch_related!
let books = Book::objects(db)
.filter(Book::Published.eq(true))
.select_related(relations![Author])
.all()
.await?;
// SQL: SELECT books.*, authors.* FROM books LEFT JOIN authors ON ...
for book in books {
println!("{} by {}", book.title, book.author.name);
}§Performance
- Single query: No additional round trips to the database
- Efficient for FK/1:1: Fetches related data in the same query
- Use
prefetch_relatedfor 1:N/M:N: To avoid row duplication
§When to Use
| Relation Type | Recommended Method |
|---|---|
| Many-to-One (FK) | select_related |
| One-to-One | select_related |
| One-to-Many | prefetch_related |
| Many-to-Many | prefetch_related |
Sourcepub async fn create(self, model: E::Model) -> Result<E::Model, OrmadaError>where
E: OrmadaEntity,
E::Model: IntoActiveModel<E::ActiveModel> + LifecycleHooks,
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
pub async fn create(self, model: E::Model) -> Result<E::Model, OrmadaError>where
E: OrmadaEntity,
E::Model: IntoActiveModel<E::ActiveModel> + LifecycleHooks,
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
Create a new record (Ormada’s .create())
Creates and saves a new record in the database. Auto-increment IDs and timestamps are handled automatically.
§Examples
let author = Author::objects(db).create(Author {
name: "John Doe".to_string(),
..Default::default() // ID and timestamps handled automatically
}).await?;Sourcepub async fn bulk_create(
self,
models: Vec<E::Model>,
) -> Result<u64, OrmadaError>where
E: OrmadaEntity,
E::Model: IntoActiveModel<E::ActiveModel>,
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
pub async fn bulk_create(
self,
models: Vec<E::Model>,
) -> Result<u64, OrmadaError>where
E: OrmadaEntity,
E::Model: IntoActiveModel<E::ActiveModel>,
E::ActiveModel: ActiveModelTrait<Entity = E> + Send,
Bulk create multiple records (Ormada’s bulk_create())
Creates multiple records in a single database operation for high performance. Much faster than creating records one-by-one.
§Arguments
models- Vector of Model instances to insert
§Returns
Number of records created
§Examples
// Create multiple authors at once
let authors = vec![
Author {
name: "Author 1".to_string(),
..Default::default()
},
Author {
name: "Author 2".to_string(),
..Default::default()
},
];
let count = Author::objects(db)
.bulk_create(authors)
.await?;
assert_eq!(count, 2);§Performance
For 1000 records:
- Individual inserts: ~5-10 seconds
- Bulk create: ~0.1-0.5 seconds (10-100x faster)
§Limitations
- Does not return generated IDs (for performance)
- Does not trigger model hooks/signals
- Check database limits (typically 1000-10000 records per operation)
Sourcepub fn upsert_many(self, models: Vec<E::Model>) -> UpsertBuilder<'a, E, C>
pub fn upsert_many(self, models: Vec<E::Model>) -> UpsertBuilder<'a, E, C>
Bulk upsert (insert or update on conflict)
Efficiently inserts multiple records, updating existing ones on conflict. Generates a single INSERT … ON CONFLICT DO UPDATE statement.
§Arguments
models- Vector of Model instances to upsert
§Returns
UpsertBuilder for chaining .on_conflict() and .update_fields()
§Examples
let books = vec![
Book { isbn: "123", title: "Book 1", price: 1000, ..Default::default() },
Book { isbn: "456", title: "Book 2", price: 2000, ..Default::default() },
];
Book::objects(&db)
.upsert_many(books)
.on_conflict(Book::Column::Isbn)
.update_fields(&[Book::Column::Title, Book::Column::Price])
.execute()
.await?;§SQL Generated
INSERT INTO books (isbn, title, price)
VALUES ('123', 'Book 1', 1000), ('456', 'Book 2', 2000)
ON CONFLICT (isbn) DO UPDATE SET
title = EXCLUDED.title,
price = EXCLUDED.price;§Performance
- 100 records: 200x faster than individual operations
- 1000 records: 2000x faster than individual operations
- 10000 records: 20000x faster than individual operations
§Database Support
PostgreSQL: Full support viaON CONFLICT DO UPDATESQLite: Full support viaON CONFLICT DO UPDATEMySQL: Polyfill viaON DUPLICATE KEY UPDATE(MySQL5.7+)
Sourcepub async fn delete(self) -> Result<u64, OrmadaError>where
E::Model: ModelTrait,
pub async fn delete(self) -> Result<u64, OrmadaError>where
E::Model: ModelTrait,
Delete all records matching this query (bulk delete)
Efficiently deletes all matching records using batched bulk operations. Returns the number of records deleted.
Performance: Uses ID-based bulk deletion with automatic batching. Default batch size: 1000 records per operation.
§Returns
Ok(u64)- Number of records deleted (0 if no matches)Err(OrmadaError)- Database error occurred
§Examples
// Delete all drafts - efficient bulk operation
let count = Book::objects(db)
.filter(Column::Status.eq("draft"))
.delete()
.await?;
println!("Deleted {} drafts", count);
// Delete old records
let cutoff_date = chrono::Utc::now() - chrono::Duration::days(30);
let count = Book::objects(db)
.filter(Column::CreatedAt.lt(cutoff_date))
.delete()
.await?;§Safety
- Always use with a filter to avoid accidentally deleting all records
- Uses bulk DELETE with primary key IN clause for performance
- Check foreign key constraints (may fail if records are referenced)
Sourcepub async fn get_or_create<F, Fut>(
self,
creator: F,
) -> Result<(E::Model, bool), OrmadaError>where
E: OrmadaEntity,
F: Fn() -> Fut,
Fut: Future<Output = Result<E::Model, OrmadaError>>,
E::Model: IntoActiveModel<E::ActiveModel>,
E::ActiveModel: ActiveModelTrait<Entity = E> + ActiveModelBehavior + Send,
C: TransactionTrait,
pub async fn get_or_create<F, Fut>(
self,
creator: F,
) -> Result<(E::Model, bool), OrmadaError>where
E: OrmadaEntity,
F: Fn() -> Fut,
Fut: Future<Output = Result<E::Model, OrmadaError>>,
E::Model: IntoActiveModel<E::ActiveModel>,
E::ActiveModel: ActiveModelTrait<Entity = E> + ActiveModelBehavior + Send,
C: TransactionTrait,
Get existing record or create it (Ormada’s .get_or_create())
Attempts to retrieve a record matching the query. If not found, creates a new record using the provided creator function.
Atomicity: Wrapped in a transaction to prevent race conditions. If two threads try to create the same record, only one succeeds.
§Returns
Returns a tuple (model, created) where:
model- The existing or newly created modelcreated-trueif the model was created,falseif it already existed
§Examples
use sea_orm::Set;
// Get or create an author - race-condition safe
let (author, created) = Author::objects(db)
.filter(Author::Email.eq("john@example.com"))
.get_or_create(|| {
author::ActiveModel {
name: Set("John Doe".to_string()),
email: Set("john@example.com".to_string()),
age: Set(30),
..Default::default()
}
})
.await?;
if created {
println!("Created new author: {}", author.name);
} else {
println!("Author already exists: {}", author.name);
}
// With dynamic data
let email = "jane@example.com";
let (author, _) = Author::objects(db)
.filter(Author::Email.eq(email))
.get_or_create(|| {
author::ActiveModel {
name: Set("Jane Doe".to_string()),
email: Set(email.to_string()),
age: Set(25),
..Default::default()
}
})
.await?;§Thread Safety
This method is safe for concurrent use. Transaction ensures atomicity.
§Performance
Makes 1-2 queries within a transaction for safety.
§Async Closures
The creator closure supports async operations, allowing you to
fetch or create related entities before creating the main record.
// Example: Create book with author lookup
let (book, created) = Book::objects(db)
.filter(Book::Isbn.eq("1234567890"))
.get_or_create(|| async {
// Async operations supported!
let author = Author::objects(db).get(author_id).await?;
Ok(Book {
isbn: "1234567890".into(),
author_id: author.id,
..Default::default()
})
})
.await?;Sourcepub async fn update_or_create<U, UF, Creator, CF>(
self,
updater: U,
creator: Creator,
) -> Result<(E::Model, bool), OrmadaError>where
E: OrmadaEntity,
U: Fn(E::Model) -> UF,
UF: Future<Output = Result<E::Model, OrmadaError>>,
Creator: Fn() -> CF,
CF: Future<Output = Result<E::Model, OrmadaError>>,
E::Model: IntoActiveModel<E::ActiveModel>,
E::ActiveModel: ActiveModelTrait<Entity = E> + ActiveModelBehavior + Send,
C: TransactionTrait,
pub async fn update_or_create<U, UF, Creator, CF>(
self,
updater: U,
creator: Creator,
) -> Result<(E::Model, bool), OrmadaError>where
E: OrmadaEntity,
U: Fn(E::Model) -> UF,
UF: Future<Output = Result<E::Model, OrmadaError>>,
Creator: Fn() -> CF,
CF: Future<Output = Result<E::Model, OrmadaError>>,
E::Model: IntoActiveModel<E::ActiveModel>,
E::ActiveModel: ActiveModelTrait<Entity = E> + ActiveModelBehavior + Send,
C: TransactionTrait,
Update existing record or create new one (Ormada’s .update_or_create())
Attempts to retrieve a record matching the query.
- If found, applies the updates from
updaterand saves. - If not found, creates a new record using
creator.
Atomicity: Wrapped in a transaction to ensure all-or-nothing behavior.
§Arguments
updater- Closure that modifies the existing modelcreator- Closure that creates a new model if none exists
§Returns
Returns (model, created)
§Examples
let (book, created) = Book::objects(db)
.filter(Book::Isbn.eq("1234567890"))
.update_or_create(
|model| {
// Update existing
model.price = 2999;
},
|| {
// Create new
Book {
isbn: "1234567890".to_string(),
title: "Rust Book".to_string(),
price: 2999,
..Default::default()
}
}
)
.await?;§Thread Safety
Safe for concurrent use. Transaction ensures atomicity.
§Async Closures
Both updater and creator support async operations, allowing you to
fetch or create related entities within the closures.
// Example: Update or create book with async author lookup
let (book, created) = Book::objects(db)
.filter(Book::Isbn.eq("1234567890"))
.update_or_create(
|mut book| async move {
// Async operations in updater!
let author = Author::objects(db).get(author_id).await?;
book.author_id = author.id;
book.price = 2999;
Ok(book)
},
|| async {
// Async operations in creator!
let author = Author::objects(db).get(author_id).await?;
Ok(Book {
isbn: "1234567890".into(),
author_id: author.id,
..Default::default()
})
},
).await?;Sourcepub async fn values(
&self,
columns: Vec<E::Column>,
) -> Result<Vec<Value>, OrmadaError>
pub async fn values( &self, columns: Vec<E::Column>, ) -> Result<Vec<Value>, OrmadaError>
Get specific column values as JSON (Ormada’s values())
Returns a Vec of JSON objects for small-medium datasets. For large datasets, automatically uses chunked fetching.
§Examples
use ormada::prelude::*;
// Get only title and price fields
let values = Book::objects(db)
.values(vec![Book::Title, Book::Price])
.await?;
for val in values {
println!("Title: {}, Price: {}", val["title"], val["price"]);
}Sourcepub async fn iterator(
&self,
chunk_size: Option<usize>,
) -> Result<impl Stream<Item = Result<E::Model, OrmadaError>> + use<'a, E, C, S>, OrmadaError>
pub async fn iterator( &self, chunk_size: Option<usize>, ) -> Result<impl Stream<Item = Result<E::Model, OrmadaError>> + use<'a, E, C, S>, OrmadaError>
Stream full model instances in chunks (Ormada’s .iterator()).
Memory-efficient alternative to .all() for large result sets.
Fetches results in batches using LIMIT/OFFSET pagination.
§Arguments
chunk_size- Optional batch size (default: 100 rows)
§Examples
use futures::StreamExt;
// Process 1 million rows without loading all into memory
let mut stream = Book::objects(db)
.filter(Book::Published.eq(true))
.iterator(Some(500))
.await?;
while let Some(book) = stream.next().await {
let book = book?;
process_book(book).await?;
}Sourcepub async fn values_iter(
&self,
columns: Vec<E::Column>,
chunk_size: Option<usize>,
) -> Result<impl Stream<Item = Result<Value, OrmadaError>> + use<'a, E, C, S>, OrmadaError>
pub async fn values_iter( &self, columns: Vec<E::Column>, chunk_size: Option<usize>, ) -> Result<impl Stream<Item = Result<Value, OrmadaError>> + use<'a, E, C, S>, OrmadaError>
Get column values iterator (Ormada’s values().iterator())
Returns iterator that streams results in chunks, preventing OOM. Use this directly for very large datasets where you want control.
§Examples
use futures::StreamExt;
// Stream results without loading all into memory
let mut stream = Book::objects(db)
.values_iter(vec![Book::Title, Book::Price], None)
.await?;
while let Some(value) = stream.next().await {
let value = value?;
println!("Title: {}, Price: {}", value["title"], value["price"]);
}Sourcepub async fn values_list_iter(
&self,
columns: Vec<E::Column>,
flat: bool,
chunk_size: Option<usize>,
) -> Result<impl Stream<Item = Result<Value, OrmadaError>> + use<'a, E, C, S>, OrmadaError>
pub async fn values_list_iter( &self, columns: Vec<E::Column>, flat: bool, chunk_size: Option<usize>, ) -> Result<impl Stream<Item = Result<Value, OrmadaError>> + use<'a, E, C, S>, OrmadaError>
Get column values iterator as tuples (Ormada’s values_list().iterator())
Returns iterator that streams results in chunks.
For single column with flat=true, yields scalar values.
§Examples
use futures::StreamExt;
// Stream tuples
let mut stream = Book::objects(db)
.values_list_iter(vec![Book::Title, Book::Price], false, None)
.await?;
while let Some(row) = stream.next().await {
let row = row?;
// row is ["title", 1999]
}
// Stream flat values
let mut stream = Book::objects(db)
.values_list_iter(vec![Book::Title], true, None)
.await?;
while let Some(title) = stream.next().await {
let title = title?;
// title is just "Book Name"
}Sourcepub async fn values_list(
&self,
columns: Vec<E::Column>,
flat: bool,
) -> Result<Vec<Value>, OrmadaError>
pub async fn values_list( &self, columns: Vec<E::Column>, flat: bool, ) -> Result<Vec<Value>, OrmadaError>
Get specific column values as tuples (Ormada’s values_list())
Returns a Vec of tuples for small-medium datasets. For large datasets, automatically uses chunked fetching.
§Parameters
columns- Vector of columns to selectflat- If true and only one column, returns flat list instead of tuples
§Examples
// Get tuples
let pairs = Book::objects(db)
.values_list(vec![Book::Title, Book::Price], false)
.await?;
// Get flat list
let titles = Book::objects(db)
.values_list(vec![Book::Title], true)
.await?;Sourcepub fn debug_sql(&self, pretty: bool) -> Stringwhere
E: OrmadaEntity,
pub fn debug_sql(&self, pretty: bool) -> Stringwhere
E: OrmadaEntity,
Print the SQL query that would be executed (for debugging).
Returns the pretty-printed SQL string by default. Useful for debugging slow queries or understanding what SQL is generated.
§Arguments
pretty- Whether to pretty-print the SQL (default: true). Set to false for single-line output.
§Examples
// Pretty-printed (default)
let sql = Book::objects(db)
.filter(Book::Published.eq(true))
.order_by_desc(Book::CreatedAt)
.debug_sql(true);
println!("SQL:\n{}", sql);
// Output:
// SELECT
// ...
// FROM
// books
// WHERE
// published = true
// ORDER BY
// created_at DESC
// Compact (single-line)
let sql = Book::objects(db)
.filter(Book::Published.eq(true))
.debug_sql(false);Sourcepub async fn project<T>(&self) -> Result<Vec<T>, OrmadaError>
pub async fn project<T>(&self) -> Result<Vec<T>, OrmadaError>
Type-safe projection query (alternative to JSON-based values())
Returns results as a custom type with compile-time validation.
Use #[ormada_projection(model = YourModel)] to define projection structs.
§Examples
#[ormada_projection(model = Book)]
struct BookSummary {
title: String,
price: f64,
}
let summaries = Book::objects(db)
.filter(Book::Published.eq(true))
.project::<BookSummary>()
.await?;
for summary in summaries {
println!("{}: ${}", summary.title, summary.price);
}Sourcepub async fn project_columns<T>(
&self,
columns: &[E::Column],
) -> Result<Vec<T>, OrmadaError>
pub async fn project_columns<T>( &self, columns: &[E::Column], ) -> Result<Vec<T>, OrmadaError>
Project to a custom DTO with explicit column selection for optimization.
Unlike project<T>() which selects all columns, this method only selects
the specified columns, reducing database load for large tables.
§Examples
use ormada::prelude::*;
use sea_orm::FromQueryResult;
#[derive(Debug, FromQueryResult)]
struct BookSummary {
title: String,
price: i32,
}
// Only SELECT title, price instead of all columns
let summaries: Vec<BookSummary> = Book::objects(&db)
.filter(Book::Published.eq(true))
.project_columns::<BookSummary>(&[Book::Title, Book::Price])
.await?;Sourcepub fn group_by(&self, column: E::Column) -> Self
pub fn group_by(&self, column: E::Column) -> Self
Group query results by one or more columns for SQL GROUP BY (aggregations)
Used with .annotate() for aggregation queries that collapse rows.
For loading related objects grouped by parent, use prefetch_related.
§Examples
let stats = Book::objects(db)
.group_by(Book::AuthorId)
.annotate([
("book_count", Aggregation::count_all()),
("avg_price", Aggregation::avg(Book::Price)),
])
.project::<AuthorBookStats>()
.await?;Sourcepub fn annotate<const N: usize>(
&self,
annotations: [(&str, Aggregation); N],
) -> Self
pub fn annotate<const N: usize>( &self, annotations: [(&str, Aggregation); N], ) -> Self
Add computed/aggregated columns to the query (Ormada’s .annotate())
Adds aliased expressions for aggregations or computed values.
Must be used with .project::<T>() where T has #[computed] fields
matching the annotation aliases.
§Examples
§Basic Aggregation
use ormada::prelude::*;
#[ormada_projection(model = Book)]
struct AuthorStats {
author_id: i32,
#[computed]
book_count: i64,
#[computed]
avg_price: Option<f64>,
#[computed]
total_sales: Option<i64>,
}
let stats = Book::objects(db)
.group_by(Book::AuthorId)
.annotate([
("book_count", Aggregation::count_all()),
("avg_price", Aggregation::avg(Book::Price)),
("total_sales", Aggregation::sum(Book::Sales)),
])
.project::<AuthorStats>()
.await?;
for stat in stats {
println!("Author {}: {} books, avg ${:.2}",
stat.author_id,
stat.book_count,
stat.avg_price.unwrap_or(0.0)
);
}§With Filtering
// Count only published books per author
let stats = Book::objects(db)
.filter(Book::Published.eq(true))
.group_by(Book::AuthorId)
.annotate([("published_count", Aggregation::count_all())])
.project::<PublishedStats>()
.await?;§Without GROUP BY (aggregate over entire result set)
#[ormada_projection(model = Book)]
struct OverallStats {
#[computed]
total_books: i64,
#[computed]
avg_price: Option<f64>,
}
let stats = Book::objects(db)
.annotate([
("total_books", Aggregation::count_all()),
("avg_price", Aggregation::avg(Book::Price)),
])
.project::<OverallStats>()
.await?;Trait Implementations§
Source§impl<E: EntityTrait + OrmadaEntity, C: ConnectionTrait + Sync, S: CanExecute> AggregateExt<E> for QuerySet<'_, E, C, S>
impl<E: EntityTrait + OrmadaEntity, C: ConnectionTrait + Sync, S: CanExecute> AggregateExt<E> for QuerySet<'_, E, C, S>
Source§async fn aggregate_count(self) -> Result<u64, OrmadaError>
async fn aggregate_count(self) -> Result<u64, OrmadaError>
count()) Read moreSource§async fn aggregate_sum(
self,
column: impl ColumnTrait,
) -> Result<Option<f64>, OrmadaError>
async fn aggregate_sum( self, column: impl ColumnTrait, ) -> Result<Option<f64>, OrmadaError>
Source§async fn aggregate_avg(
self,
column: impl ColumnTrait,
) -> Result<Option<f64>, OrmadaError>
async fn aggregate_avg( self, column: impl ColumnTrait, ) -> Result<Option<f64>, OrmadaError>
Source§async fn aggregate_max(
self,
column: impl ColumnTrait,
) -> Result<Option<f64>, OrmadaError>
async fn aggregate_max( self, column: impl ColumnTrait, ) -> Result<Option<f64>, OrmadaError>
Source§async fn aggregate_min(
self,
column: impl ColumnTrait,
) -> Result<Option<f64>, OrmadaError>
async fn aggregate_min( self, column: impl ColumnTrait, ) -> Result<Option<f64>, OrmadaError>
Source§impl<E: EntityTrait, C: ConnectionTrait, S: QuerySetState> Clone for QuerySet<'_, E, C, S>
impl<E: EntityTrait, C: ConnectionTrait, S: QuerySetState> Clone for QuerySet<'_, E, C, S>
Auto Trait Implementations§
impl<'a, E, C, S> Freeze for QuerySet<'a, E, C, S>
impl<'a, E, C, S = Fresh> !RefUnwindSafe for QuerySet<'a, E, C, S>
impl<'a, E, C, S> Send for QuerySet<'a, E, C, S>
impl<'a, E, C, S> Sync for QuerySet<'a, E, C, S>
impl<'a, E, C, S> Unpin for QuerySet<'a, E, C, S>where
S: Unpin,
impl<'a, E, C, S = Fresh> !UnwindSafe for QuerySet<'a, E, C, S>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more