QuerySet

Struct QuerySet 

Source
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 QuerySet instance
  • Subsequent calls on the SAME QuerySet reuse cached results
  • Building new queries (.filter(), .limit()) creates new QuerySet with separate cache

Concurrency Safety:

  • Uses Arc for cheap cloning across async tasks
  • Uses parking_lot::RwLock for fast, synchronous cache access
  • Safe to share across threads and async tasks

§Type Parameters

  • E: The SeaORM Entity type
  • C: The database connection type
  • S: The typestate marker (defaults to Fresh)

§Typestate Pattern

The S parameter tracks the query building state at compile time:

  • Fresh → initial state, can filter, order, or execute
  • Filtered → has filters, can add more filters, order, or execute
  • Ordered → has ordering, can paginate or execute
  • Paginated → has limit/offset, can execute
  • Aggregated → 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>

Source

pub fn new(db: &'a C) -> Self

Create a new QuerySet in Fresh state

Source§

impl<'a, E: EntityTrait, C: ConnectionTrait, S: QuerySetState> QuerySet<'a, E, C, S>

Source

pub fn state(&self) -> QueryState

Get the current query state

Returns the state of query construction, useful for debugging and validation of query building patterns.

§Example
let qs = Book::objects(db);
assert_eq!(qs.state(), QueryState::Fresh);

let qs = qs.filter(Book::Price.lt(50));
assert_eq!(qs.state(), QueryState::Filtered);
Source

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>

Source

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> or QuerySet<Filtered>
  • Output: QuerySet<Filtered>
Source

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> or QuerySet<Filtered>
  • Output: QuerySet<Filtered>
Source§

impl<E: EntityTrait, C: ConnectionTrait, S: QuerySetState> QuerySet<'_, E, C, S>

Source

pub fn with_deleted(&self) -> Self

Include soft-deleted records in query results

By default, models with #[soft_delete] automatically exclude deleted records. Use this method to include them.

§Example
// Get all products including deleted ones
let all_products = Product::objects(&db)
    .with_deleted()
    .all()
    .await?;
Source

pub fn only_deleted(&self) -> Self

Only show soft-deleted records

Filters to show ONLY records where the soft delete field is NOT NULL.

§Example
// Get only deleted products
let deleted = Product::objects(&db)
    .only_deleted()
    .all()
    .await?;
Source

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>

Source

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>, or QuerySet<Ordered>
  • Output: QuerySet<Ordered>
§Examples
// Order by price (lowest first)
let books = Book::objects(db)
    .order_by_asc(Book::Price)
    .all()
    .await?;
Source

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>, or QuerySet<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>

Source

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>, or QuerySet<Paginated>
  • Output: QuerySet<Paginated>
Source

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>, or QuerySet<Paginated>
  • Output: QuerySet<Paginated>
Source§

impl<E: EntityTrait, C: ConnectionTrait + Sync, S: CanExplain> QuerySet<'_, E, C, S>
where E::Model: Send + Sync,

Source

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 PLAN
  • PostgreSQL: EXPLAIN
  • MySQL: EXPLAIN
§See Also
  • .explain_analyze() - Runs query and provides actual timings
  • .debug_sql() - Shows the raw SQL query without executing
Source

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 statistics
  • MySQL: EXPLAIN ANALYZE (MySQL 8.0.18+)
Source§

impl<'a, E, C: ConnectionTrait + Sync, S: CanExecute + 'a> QuerySet<'a, E, C, S>

Source

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 executed

Second call on same QuerySet - Returns cached results (no DB query):

let books_again = qs.all().await?;  // Cache hit! No DB query
Source

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 found
  • Err(OrmadaError::EmptyResult { .. }) - No matching models
  • Err(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.

Source

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 found
  • Err(OrmadaError::NotFound { .. }) - No matching models
  • Err(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.

Source

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 found
  • Err(OrmadaError::NotFound { entity, id }) - No record with that ID
  • Err(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 found
Source

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 record
  • Err(OrmadaError::EmptyResult { .. }) - No records found
  • Err(OrmadaError::Database) - Database error
§Equivalent to

.order_by_asc(column).first() but returns error on empty result

Source

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 record
  • Err(OrmadaError::EmptyResult { .. }) - No records found
  • Err(OrmadaError::Database) - Database error
§Equivalent to

.order_by_desc(column).first() but returns error on empty result

Source

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.

Source

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 exists
  • Ok(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.

Source

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

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_related for 1:N/M:N: To avoid row duplication
§When to Use
Relation TypeRecommended Method
Many-to-One (FK)select_related
One-to-Oneselect_related
One-to-Manyprefetch_related
Many-to-Manyprefetch_related
Source

pub async fn create(self, model: E::Model) -> Result<E::Model, OrmadaError>

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?;
Source

pub async fn bulk_create( self, models: Vec<E::Model>, ) -> Result<u64, OrmadaError>

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

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 via ON CONFLICT DO UPDATE
  • SQLite: Full support via ON CONFLICT DO UPDATE
  • MySQL: Polyfill via ON DUPLICATE KEY UPDATE (MySQL 5.7+)
Source

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

pub async fn get_or_create<F, Fut>( self, creator: F, ) -> Result<(E::Model, bool), OrmadaError>

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 model
  • created - true if the model was created, false if 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?;
Source

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 updater and 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 model
  • creator - 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?;
Source

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"]);
}
Source

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?;
}
Source

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"]);
}
Source

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"
}
Source

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 select
  • flat - 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?;
Source

pub fn debug_sql(&self, pretty: bool) -> String
where 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);
Source

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);
}
Source

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?;
Source

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?;
Source

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>
where E::Model: Send + Sync,

Source§

async fn aggregate_count(self) -> Result<u64, OrmadaError>

Count records (Ormada’s .count()) Read more
Source§

async fn aggregate_sum( self, column: impl ColumnTrait, ) -> Result<Option<f64>, OrmadaError>

Sum numeric column values (Ormada’s .aggregate(Sum(‘field’))) Read more
Source§

async fn aggregate_avg( self, column: impl ColumnTrait, ) -> Result<Option<f64>, OrmadaError>

Calculate average of numeric column (Ormada’s .aggregate(Avg(‘field’))) Read more
Source§

async fn aggregate_max( self, column: impl ColumnTrait, ) -> Result<Option<f64>, OrmadaError>

Get maximum value (Ormada’s .aggregate(Max(‘field’))) Read more
Source§

async fn aggregate_min( self, column: impl ColumnTrait, ) -> Result<Option<f64>, OrmadaError>

Get minimum value (Ormada’s .aggregate(Min(‘field’))) Read more
Source§

impl<E: EntityTrait, C: ConnectionTrait, S: QuerySetState> Clone for QuerySet<'_, E, C, S>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

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>
where <E as EntityTrait>::Model: Sync,

§

impl<'a, E, C, S> Sync for QuerySet<'a, E, C, S>
where <E as EntityTrait>::Model: Sync,

§

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more