pub struct QuerySet<T> { /* private fields */ }Expand description
A lazy, chainable SQL query.
Carries a sea-query SelectStatement plus pool-resolution state.
Nothing is sent to the database until a terminal method is awaited.
Cloning is cheap (the SelectStatement clones in O(query size)).
Implementations§
Source§impl<T> QuerySet<T>
impl<T> QuerySet<T>
Sourcepub fn with_deleted(self) -> Self
pub fn with_deleted(self) -> Self
Feature #72 — include soft-deleted rows in this query. Skips
the auto WHERE deleted_at IS NULL injection. No-op on
models that aren’t tagged #[umbral(soft_delete)].
Sourcepub fn only_deleted(self) -> Self
pub fn only_deleted(self) -> Self
Feature #72 — only soft-deleted rows. Useful for admin
trash views and undelete workflows. No-op on models that
aren’t tagged #[umbral(soft_delete)].
Sourcepub fn hard_delete(self) -> Self
pub fn hard_delete(self) -> Self
Feature #72 — force a real DELETE for the next .delete()
terminal call. Soft-delete models normally rewrite delete()
as UPDATE ... SET deleted_at = NOW(); .hard_delete()
bypasses that for GDPR purges, test cleanup, or any other
case where the row truly should be gone. No-op on models
that aren’t tagged #[umbral(soft_delete)] (their delete()
is already a hard DELETE).
Sourcepub fn only(self, cols: &[&str]) -> Self
pub fn only(self, cols: &[&str]) -> Self
Gap #111 — restrict the SELECT to the named columns.
Affects Self::to_sql / Self::to_sql_pg (the SELECT list
shrinks to just these columns) and propagates into
Self::values when that terminal is called without its own
explicit column slice.
The typed terminals (fetch / first / get) refuse to
run with .only() set because a partial-column row can’t
satisfy T’s FromRow impl. The error message points at
.values(...) (returns Vec<serde_json::Value>) as the
execution path. Use .only() for .to_sql() inspection and
.values() for actual reads.
// Inspect: "SELECT \"id\", \"name\" FROM \"brand\" WHERE \"id\" = ?"
let sql = Brand::objects()
.filter(brand::ID.eq(1))
.only(&["id", "name"])
.to_sql();
// Execute (returns JSON rows):
let rows = Brand::objects()
.filter(brand::ID.eq(1))
.values(&["id", "name"])
.await?;Unknown column names are not validated here — they surface at
terminal time the same way .values() reports them (the
rendered SQL contains the bad identifier and SQLite/Postgres
raises). This keeps the chainable surface return-type-stable.
Sourcepub fn atomic(self) -> Self
pub fn atomic(self) -> Self
Wrap this QuerySet’s write terminal in a transaction. Reads are
unaffected (read terminals are single statements and the DB
gives them a consistent snapshot). Mutually exclusive with
Self::on_tx — if both are set, on_tx wins (you’re
already inside a transaction, so wrapping again would deadlock
or fail on backends without nested transactions).
Sourcepub fn non_atomic(self) -> Self
pub fn non_atomic(self) -> Self
Opt this QuerySet’s write terminal out of the global
App::builder().atomic_transactions(true) default. Useful in
hot-path batches where the caller already owns the outer
transaction.
Source§impl<T> QuerySet<T>
Chainable methods on every QuerySet<T>.
impl<T> QuerySet<T>
Chainable methods on every QuerySet<T>.
These are model-agnostic: they only touch the sea-query
SelectStatement and the pool-resolution slot, neither of which
depends on T. Terminals (which need row mapping) live in the
impl<T: Model> QuerySet<T> block below.
Sourcepub fn filter(self, p: Predicate<T>) -> Self
pub fn filter(self, p: Predicate<T>) -> Self
Add a WHERE condition. Multiple .filter calls AND together
(sea-query’s and_where semantics — applied at terminal time
once the resolved pool’s backend is known).
Sourcepub fn exclude(self, p: Predicate<T>) -> Self
pub fn exclude(self, p: Predicate<T>) -> Self
Add a negated WHERE condition. The negated predicate ANDs into
the chain alongside any filter() calls, so
.filter(A).exclude(B).filter(C) renders as WHERE A AND NOT B AND C. Sugar for filter(Q::not(p)).
The negated-filter terminal.
Sourcepub fn order_by(self, o: OrderExpr<T>) -> Self
pub fn order_by(self, o: OrderExpr<T>) -> Self
Add an ORDER BY clause. Multiple .order_by calls append.
The first explicit call also opts out of the model’s
#[umbral(ordering = [...])] default (BUG-8): explicit ordering
replaces the default rather than stacking on top of it.
Sourcepub fn on(self, pool: &SqlitePool) -> Self
pub fn on(self, pool: &SqlitePool) -> Self
Override the pool resolved at terminal time with a SQLite pool.
Wins over the ambient default. Used by tests that drive the ORM
without going through App::build(). For a Postgres override
use Self::on_pg.
Sourcepub fn on_pg(self, pool: &PgPool) -> Self
pub fn on_pg(self, pool: &PgPool) -> Self
Override the pool resolved at terminal time with a Postgres pool.
The Postgres counterpart of Self::on. Tests that want to
exercise the Postgres branch (or that drive against a real
Postgres instance) reach for this directly.
Sourcepub fn on_tx(self, tx: &mut Transaction) -> QuerySetTx<'_, T>
pub fn on_tx(self, tx: &mut Transaction) -> QuerySetTx<'_, T>
Attach this QuerySet to an open transaction.
Returns a QuerySetTx that holds both the query and a mutable
reference to the transaction. Every terminal on QuerySetTx
(fetch, first, count, exists, get, delete,
update_values) executes inside the open transaction so all
operations in the same closure commit or roll back as a unit.
umbral::db::transaction(|tx| async move {
let order = Order::objects().on_tx(tx).create(new_order).await?;
Stock::objects()
.on_tx(tx)
.filter(stock::SKU.eq(sku))
.update_values(delta)
.await?;
Ok::<_, MyError>(order)
}).await?;Eagerly load a single FK field by name.
After the main SELECT returns rows, a batch SELECT ... FROM <related_table> WHERE id IN (...) fetches all referenced rows in one round-trip. Each
returned row is deserialised as the target model and stored in
ForeignKey<U>.resolved so template rendering ({{ post.author.username }})
and serde_json::to_value(&post)["author"]["username"] both work without
additional queries.
Calling select_related multiple times accumulates the names:
.select_related("author").select_related("editor") works the same as
.select_related_many(&["author", "editor"]).
§Nested traversal (post-#42)
.select_related("author__manager") walks the FK chain through
the __ separator. One batched IN (...) query per hop —
1 + len(hops) round-trips regardless of parent count. No
N+1. Each hop’s related row is embedded into the prior level’s
JSON, and recursive ForeignKey<T>::Deserialize unpacks the
chain into resolved() slots at every depth. Bonus: a
select_related’d model now round-trips through
serde_json::to_value(&t) / from_value without losing the
resolved relation.
§Companion shapes
join_related(name)— same goal (load related rows) via a trueLEFT JOINin the main SELECT. One round-trip total vs.select_related’s1 + Nbatched-IN approach. Wider per-row payload; better when round-trip count dominates.prefetch_related(name)— M2M batched loading (one query per declared M2M field). For reverse-FK collections (prefetch_related("comment_set")-style) see gap #44 — not yet implemented.
§Loud errors
Unknown field names (typos, M2M names accidentally passed
here, fields without fk_target) return a clear
sqlx::Error::Protocol from the terminal naming the bad hop
and the table it was looked up against. Pre-#42 these
silently no-op’d.
Eagerly load multiple FK fields in one call.
Sugar for chained .select_related(name) calls.
JOIN-based eager FK loading — emits LEFT JOIN <related> ON ...
in the main SELECT (with aliased child columns <field>__<col>)
so one round-trip pulls the parent + related rows together.
Trade-off vs. Self::select_related:
select_relatedruns ONE extra batched query after the main fetch (SELECT * FROM related WHERE id IN (...)). Two round-trips total; rows stay narrow.join_relatedruns the main query AS the join — one round-trip total — at the cost of a wider per-row payload (every related column rides along even for duplicated parents).
Both populate ForeignKey<U>.resolved the same way so
downstream code (templates, serde) doesn’t care which path
was used. Pick join_related when round-trip count dominates
(hot listing pages, small related tables) and select_related
when the related row is wide or only loaded for a subset of
the parent rows.
Composes with .select_related(other_fk) and
.prefetch_related(m2m) — different fields can take different
paths in the same query.
Multi-hop FK chains are supported: a "__"-separated path like
"author__manager" resolves one JOIN per hop in a single query.
Constraints: FK fields must live in model.fields (M2M links
route through prefetch_related), and every related model along the
chain must be registered with the framework
(App::builder().model::<U>() or contributed by a plugin) so we can
resolve its column layout for the aliased SELECT.
Sugar for chained Self::join_related calls.
LEFT JOIN the related path — keeps parent rows whose relation
is absent (the relation hydrates as unresolved/None). Accepts a
nested path ("plugin__author"); the join type applies to the
deepest hop.
INNER JOIN the related path — drops parent rows whose relation
is absent. The default for a NOT NULL FK.
RIGHT JOIN the related path. Postgres-unconditional; SQLite
needs >= 3.39 — a runtime warning fires on older SQLite (see the
boot/runtime note in the joins docs). The precise version gate
lives at execute time (the SQLite driver’s own error); the warn
is the early nudge.
Eagerly load an M2M relation via a single batched join.
After the main SELECT returns rows, one query of the shape
SELECT j.parent_id, child.* FROM <child_table> child INNER JOIN <junction> j ON child.<pk> = j.child_id WHERE j.parent_id IN (...) fetches every related child for every
parent in one round-trip. Each parent’s M2M.resolved slot
is populated with its matching children.
The M2M counterpart of Self::select_related for FKs —
same goal of killing N+1: a batch-loaded prefetch_related('tags').
§Reverse-FK collections (post-#44)
prefetch_related also loads ReverseSet<C> fields — the
“for each Post, give me every Comment that points at it”
shape. Declare the field on the parent with
#[sqlx(skip)] #[serde(skip)] #[umbral(reverse_fk = "<fk_col>")] pub <name>: ReverseSet<C>
where <fk_col> names the FK column on C pointing back.
One SELECT * FROM <child> WHERE <fk_col> IN (parent_pks)
regardless of parent count — no N+1.
§Scope (v1)
- M2M + reverse-FK only. FK fields go through
Self::select_related(batched IN) orSelf::join_related(LEFT JOIN). - i64 parent PK only. Same constraint as the rest of the M2M plumbing; models with non-i64 PKs surface a clean compile error.
- Unknown field name → loud error (post-#42). If the
name matches neither an M2M nor a
ReverseSetfield, fetch returns a clearsqlx::Error::Protocolpointing at the right method.
Eagerly load multiple M2M relations. Sugar for chained
.prefetch_related(name) calls.
Sourcepub fn into_subquery(self, col_name: &str) -> Subquery
pub fn into_subquery(self, col_name: &str) -> Subquery
Convert this QuerySet into a [Subquery] suitable for use in
an IN (SELECT ...) predicate. Projects only the named
column; the accumulated WHERE / ORDER BY survive.
Post::objects().filter(...).into_subquery("author_id") →
Subquery you can hand to user::ID.in_subquery(...).
Sourcepub fn union(self, other: QuerySet<T>) -> Self
pub fn union(self, other: QuerySet<T>) -> Self
Combine this QuerySet with other via SQL UNION (gap #28).
Both QuerySets must produce the same column shape — which
they always do here because both are typed QuerySet<T>.
Duplicates are removed (the de-duplicating UNION, not UNION
ALL).
Sourcepub fn intersect(self, other: QuerySet<T>) -> Self
pub fn intersect(self, other: QuerySet<T>) -> Self
Combine this QuerySet with other via SQL INTERSECT
(gap #28). Returns rows present in BOTH inputs.
Sourcepub fn except(self, other: QuerySet<T>) -> Self
pub fn except(self, other: QuerySet<T>) -> Self
Combine this QuerySet with other via SQL EXCEPT
(gap #28). Returns rows present in self but not in other.
Sourcepub fn distinct(self) -> Self
pub fn distinct(self) -> Self
Emit SELECT DISTINCT ... for this query (gap #17). Most
useful when combined with Self::values to dedupe a
column-projected list (distinct().values(&["tag"])); the
full-row DISTINCT is rarely what you want.
Postgres-specific DISTINCT ON (cols) is deferred until a
real consumer surfaces the need — the standard DISTINCT
covers most use cases.
Source§impl<T: Model> QuerySet<T>
Terminal methods for every QuerySet<T> where T: Model.
impl<T: Model> QuerySet<T>
Terminal methods for every QuerySet<T> where T: Model.
Each terminal that materializes T carries a FromRow bound on the
method (not the impl block) — the conjunction of both backends’
FromRow impls. #[derive(sqlx::FromRow)] emits a generic-over-R
impl, so any user struct with standard field types satisfies both
bounds automatically.
Sourcepub fn to_sql(&self) -> String
pub fn to_sql(&self) -> String
Render the SQL the QuerySet would execute, without running it.
Returns the prepared statement with ? placeholders for the
bound values, exactly the string sqlx would send. Useful for
eprintln!-style debugging and for tests that want to pin
the rendered query without round-tripping through a pool.
The bound values are intentionally not surfaced (sqlx’s binder
types aren’t part of umbral’s public surface); a (sql, values)
accessor lands when EXPLAIN-style integration needs it.
The rendered placeholder dialect is SQLite’s (?). When the
dispatched pool is Postgres the actual at-execute rendering
uses $1-style placeholders; the to_sql debug surface
continues to emit SQLite-style for stability across calls
regardless of which pool is registered.
Sourcepub fn to_sql_pg(&self) -> String
pub fn to_sql_pg(&self) -> String
Render the QuerySet’s SQL against the Postgres dialect,
without running it. Companion to Self::to_sql.
The two render slightly different placeholder syntax (? for
SQLite, $1..$N for Postgres) and any Postgres-specific
operators like the array @> / <@ / && family only render
correctly through this entry point — to_sql’s SQLite path
leaves $N tokens in the template untouched. Use this when
debugging a Postgres query or asserting on the rendered shape
in tests.
Sourcepub async fn fetch(self) -> Result<Vec<T>, Error>
pub async fn fetch(self) -> Result<Vec<T>, Error>
Run the SELECT and return every matching row.
If .select_related(name) was called, a follow-up batch query
populates ForeignKey<U>.resolved for each named field before
the rows are returned.
Sourcepub async fn try_for_each<F, E>(
self,
chunk_size: usize,
callback: F,
) -> Result<(), TryForEachError<E>>
pub async fn try_for_each<F, E>( self, chunk_size: usize, callback: F, ) -> Result<(), TryForEachError<E>>
Feature 29 Phase 1 — chunked streaming via a callback.
Runs the SELECT in pages of chunk_size rows and invokes
callback once per row. Memory bound = chunk_size * sizeof::<T> instead of the full row count fetch() would
buffer, so this is the right shape for million-row exports,
migrations, and batch transforms.
Deliberately NOT named iterator() — that name suggests a
Stream-shaped return value, which would force a
futures-util dep. The callback shape is idiomatic Rust,
requires no new crates, and ships the same memory bound. A
future iterator() returning BoxStream<T> can land later
once futures-util is in the workspace for some other reason
(likely SSE / WebSockets).
Error contract: the callback may return any error type E.
SQL failures become TryForEachError::Sqlx; callback errors
become TryForEachError::Callback(e). The first error stops
the walk — subsequent rows are not fetched.
Caveats: pages are stable only if the result set isn’t being
mutated concurrently. For consistent-snapshot iteration over
a live table, wrap the call in a serialised-or-repeatable-read
transaction. select_related and prefetch_related hooks
are NOT applied on each row — try_for_each is intentionally
the “raw column data, one row at a time” terminal.
Sourcepub async fn first(self) -> Result<Option<T>, Error>
pub async fn first(self) -> Result<Option<T>, Error>
Run the SELECT with LIMIT 1 and return the first row, if any.
Sourcepub async fn earliest(self, col_name: &'static str) -> Result<Option<T>, Error>
pub async fn earliest(self, col_name: &'static str) -> Result<Option<T>, Error>
Return the row with the smallest value in col_name. Sugar
for order_by(col.asc()).first(). The earliest('created_at')
terminal.
Takes a &'static str column name (same shape as
select_related) so the call site stays terse:
.earliest("created_at") reads naturally without spelling out
.asc().
Sourcepub async fn latest(self, col_name: &'static str) -> Result<Option<T>, Error>
pub async fn latest(self, col_name: &'static str) -> Result<Option<T>, Error>
Return the row with the largest value in col_name. Sugar
for order_by(col.desc()).first(). The latest('created_at')
terminal.
Sourcepub async fn in_bulk(
self,
pks: Vec<T::PrimaryKey>,
) -> Result<HashMap<T::PrimaryKey, T>, Error>where
T: for<'r> FromRow<'r, SqliteRow> + for<'r> FromRow<'r, PgRow> + HydrateRelated,
T::PrimaryKey: Hash + Eq,
pub async fn in_bulk(
self,
pks: Vec<T::PrimaryKey>,
) -> Result<HashMap<T::PrimaryKey, T>, Error>where
T: for<'r> FromRow<'r, SqliteRow> + for<'r> FromRow<'r, PgRow> + HydrateRelated,
T::PrimaryKey: Hash + Eq,
Fetch many rows by their primary keys and return a
HashMap<T::PrimaryKey, T> keyed by PK. The everyday companion to
a cached list of ids — User::objects().in_bulk(user_ids) gives
you direct lookup access without a second .iter().find(...) pass
per id.
Missing ids are silently absent from the map; callers that
need the existence check can compare map.len() to
pks.len(). Empty input is a no-op (returns the empty map).
PK-agnostic (PK lift — was Vec<i64> / HashMap<i64, T>): the key
is the model’s PrimaryKey type, so i64-, String/slug-, and
Uuid-keyed models all work. The map key requires Hash + Eq,
which every standard PK type (integers, String, Uuid) satisfies.
Sourcepub async fn explain(self) -> Result<String, Error>
pub async fn explain(self) -> Result<String, Error>
Return the database’s execution plan for this query as a plain-text string. Doesn’t run the underlying query — just asks the DB how it would be executed.
Backend dispatch:
- SQLite:
EXPLAIN QUERY PLAN <sql>— returns the planner’s nested loop hierarchy, one row per access step. - Postgres:
EXPLAIN <sql>— returns the default text plan. For machine-readable output use raw sqlx withEXPLAIN (FORMAT JSON); the framework defaults to text because most callers want eyeball-able output.
Lines are joined with newlines. The returned string is what a developer would paste into a debugger or a perf-review issue.
Sourcepub async fn count(self) -> Result<i64, Error>
pub async fn count(self) -> Result<i64, Error>
Run SELECT COUNT(*) against the same FROM + WHERE.
Reshapes the query rather than wrapping the existing SELECT: the
projection becomes COUNT(*) and LIMIT/OFFSET drop away. ORDER
BY is harmless on a scalar aggregate and is left in place. The
row type is (i64,) so the FromRow constraint comes from sqlx’s
tuple impl rather than the user struct — count() doesn’t need
T’s FromRow bounds.
Sourcepub async fn exists(self) -> Result<bool, Error>
pub async fn exists(self) -> Result<bool, Error>
Return whether any row matches.
M1 keeps the simple form: add LIMIT 1, fetch, check non-empty.
A later milestone may swap the projection for SELECT 1 to
skip column materialisation.
Sourcepub async fn get(self) -> Result<T, GetError>
pub async fn get(self) -> Result<T, GetError>
.get() — the exactly-one terminal.
Returns Ok(row) when the filter chain matches exactly one
row. The two not-exactly-one cases each get their own
GetError variant so the caller can branch deliberately:
GetError::NotFound— zero rows matched. The right choice for “fetch the row this user just clicked on; 404 if it’s gone.”GetError::MultipleObjectsReturned— more than one row matched. The right choice for filters that should be uniquely-keyed (e.g..filter(user::EMAIL.eq("..."))when email has a UNIQUE constraint); a result of 2+ is a data-integrity bug worth crashing on.- The underlying sqlx error wraps as
GetError::Sqlx.
Internally this issues SELECT ... LIMIT 2 — the cheapest
way to distinguish “one row” from “many.” The second row, if
it exists, isn’t materialised beyond the bare FromRow call.
match Post::objects().filter(post::ID.eq(42)).get().await {
Ok(p) => /* render */,
Err(GetError::NotFound) => /* 404 */,
Err(GetError::MultipleObjectsReturned) => unreachable!("ID is unique"),
Err(GetError::Sqlx(e)) => /* 500 */,
}Sourcepub async fn values(self, columns: &[&str]) -> Result<Vec<JsonValue>, Error>
pub async fn values(self, columns: &[&str]) -> Result<Vec<JsonValue>, Error>
Project the query to only the named columns, returning a
vector of serde_json::Value::Object rows instead of typed
T instances. The columns-projection terminal: values('id', 'title').
Use when a list view only needs a few fields — skipping the
50KB body BLOB on every Post saves both memory and the
FromRow hydration overhead. Each returned Value is an
object keyed by the requested column names, with values
typed per the column’s declared SqlType (integers stay
integers, booleans stay booleans, dates render as ISO
strings).
Unknown column names fail loudly with
sqlx::Error::Protocol naming the offending column.
Composes with filter, exclude, order_by, limit,
offset exactly the way the typed terminals do.
let rows = Post::objects()
.filter(post::PUBLISHED.eq(true))
.order_by(post::ID.desc())
.values(&["id", "title"])
.await?;
// [ { "id": 3, "title": "c" }, ... ]Sourcepub async fn aggregate(
self,
aggs: &[(&str, Aggregate)],
) -> Result<JsonValue, Error>
pub async fn aggregate( self, aggs: &[(&str, Aggregate)], ) -> Result<JsonValue, Error>
Single-row aggregate. Runs SELECT AGG(col) AS name, ... with
the QuerySet’s accumulated WHERE clause (ORDER BY / LIMIT /
OFFSET are dropped — they make no sense over an aggregate
without GROUP BY).
Returns a serde_json::Value::Object keyed by the supplied
names. COUNT comes back as an integer; AVG as a float; SUM /
MAX / MIN inherit the source column’s declared type.
use umbral::orm::Aggregate;
let summary = Post::objects()
.filter(post::PUBLISHED.eq(true))
.aggregate(&[
("count", Aggregate::count()),
("total", Aggregate::sum("view_count")),
])
.await?;
// { "count": 42, "total": 9999 }Sourcepub async fn annotate(
self,
group_cols: &[&str],
aggs: &[(&str, Aggregate)],
) -> Result<Vec<JsonValue>, Error>
pub async fn annotate( self, group_cols: &[&str], aggs: &[(&str, Aggregate)], ) -> Result<Vec<JsonValue>, Error>
Grouped aggregate. Runs SELECT <group_cols>, AGG(col) AS name, ... GROUP BY <group_cols> with the accumulated WHERE clause.
Returns one Value::Object per group, with both the group
columns and each named aggregate as fields. Group columns are
decoded per their declared SqlType (so an integer
author_id stays a JSON number).
let by_author = Post::objects()
.annotate(&["author_id"], &[("count", Aggregate::count())])
.await?;
// [ { "author_id": 1, "count": 3 }, { "author_id": 2, "count": 2 } ]The chainable annotate(alias=Agg("relation")): attach a
related-aggregate annotation to this QuerySet. The annotation
is query-builder state — it renders as a correlated scalar
subquery inside the one SELECT every terminal builds, so it
composes with .filter / .order_by / .limit, stacks with
further annotations, and shows up in Self::explain /
Self::to_sql out of the box. Never a side query, never an
N+1.
relation names a ReverseSet relation on the model
(#[umbral(reverse_fk = "...")]), the same names
prefetch_related accepts. When no declared relation matches,
the resolver AUTO-DISCOVERS the relation (gaps2 #45): it scans
the model registry for any child whose FK targets this parent’s
table and matches relation against the child’s conventional
name forms (table name, snake_case / lowercase struct name,
any of those with a _set suffix). Declared relations always
take precedence; an ambiguous auto-match (two children, or a
child with two FKs to this parent) poisons the annotation with
an error that names the candidates and points at the
#[umbral(reverse_fk = "...")] escape hatch. Any
crate::orm::Aggregate works; non-count aggregates name a
column on the CHILD model:
let rows = Plugin::objects()
.filter(plugin::MODERATION.eq("approved"))
.annotate_count("comment_set") // COUNT(*)
.annotate_related("rating_avg", "review_set", Aggregate::avg("rating"))
.fetch_annotated()
.await?; // Vec<(Plugin, Map<alias, value>)>An unknown relation name doesn’t panic the (infallible)
builder — it poisons the annotation, and every fallible
consumer (fetch_annotated, explain) reports it loudly.
v1 caveats: child rows aggregate unconditionally — a
child-side predicate (a filtered count)
and child soft-delete awareness are tracked follow-ups
(gaps2 #39).
Sourcepub fn annotate_count(self, relation: &str) -> Self
pub fn annotate_count(self, relation: &str) -> Self
Sugar for the overwhelmingly common annotation:
annotate_related("<relation>_count", relation, Aggregate::count()).
.annotate_count("comment_set") exposes the value under the
comment_set_count alias in Self::fetch_annotated.
Sourcepub fn annotate_count_where<C: Model>(
self,
alias: &str,
relation: &str,
pred: Predicate<C>,
) -> Self
pub fn annotate_count_where<C: Model>( self, alias: &str, relation: &str, pred: Predicate<C>, ) -> Self
Like Self::annotate_count but counts only the children
matching pred — a filtered count over "comments".
C is the CHILD model, so the predicate is typed against the
child’s columns (comment::MODERATION.eq("visible")). The
predicate renders into the correlated count subquery’s WHERE
alongside the FK correlation and the auto soft-delete filter.
Plugin::objects()
.annotate_count_where::<PluginComment>(
"visible_comments",
"comment_set",
plugin_comment::MODERATION.eq("visible"),
)Sourcepub async fn fetch_annotated(
self,
) -> Result<Vec<(T, Map<String, JsonValue>)>, Error>
pub async fn fetch_annotated( self, ) -> Result<Vec<(T, Map<String, JsonValue>)>, Error>
Run the SELECT and return every matching row with its
annotation values — the execution terminal for
Self::annotate_related / Self::annotate_count. One
query; each row’s annotations arrive as an alias → JSON value map (count → integer, AVG → float/null, SUM/MAX/MIN →
typed per the child column, NULL on empty sets for the
non-count aggregates).
Sourcepub async fn delete(self) -> Result<u64, Error>
pub async fn delete(self) -> Result<u64, Error>
DELETE FROM table WHERE <predicates>. Returns the number of
rows deleted. With no .filter calls, deletes every row.
Fires bulk_post_delete:<table> once with the list of removed
PKs when at least one row was deleted. Per-row pre_delete /
post_delete are NOT fired by this path — use
Manager::delete_instance when per-row signal semantics are
required.
Feature #72: for #[umbral(soft_delete)] models this rewrites
to UPDATE ... SET deleted_at = NOW() WHERE ... so rows
survive in the DB (filtered out of subsequent queries by the
auto WHERE deleted_at IS NULL). Call .hard_delete()
beforehand for a real DELETE (GDPR purge, test cleanup).
Sourcepub async fn update_expr(
self,
col_name: &str,
expr: FExpr,
) -> Result<u64, WriteError>
pub async fn update_expr( self, col_name: &str, expr: FExpr, ) -> Result<u64, WriteError>
UPDATE table SET col = <expr> WHERE <predicates> using an
F-expression for the new value.
This is the companion to update_values for atomic column
arithmetic. F::col("views").add(1) produces an FExpr that
renders as SET views = views + 1 — the database computes the
increment atomically on the server side rather than needing a
read-modify-write round-trip in application code.
use umbral::orm::F;
Post::objects()
.filter(post::ID.eq(42))
.update_expr("views", F::col("views").add(1))
.await?;Mixing update_values and update_expr for different columns in
one statement requires two separate calls. A combined API (a map
where values can be either JSON or FExpr) would require a new sum
type; deferred until a consumer surfaces the need.
Sourcepub async fn update_values(
self,
values: Map<String, Value>,
) -> Result<u64, WriteError>
pub async fn update_values( self, values: Map<String, Value>, ) -> Result<u64, WriteError>
UPDATE table SET k=v[, ...] WHERE <predicates>. The values
map provides column_name → JSON value pairs; each is
converted to a sea_query::Value per the column’s declared
SqlType via crate::orm::write::json_to_sea_value. Returns
the number of rows affected.
Unknown columns in the map fail loudly with
WriteError::UnknownColumn. JSON null is rejected for
non-nullable columns; supplying a column that exists but is
absent from the map is silently a no-op (the column keeps its
current value — PATCH semantics, not PUT).
Fires bulk_post_save:<table> once with { ids, created: false, actor } when at least one row matched. Per-row
pre_save / post_save are NOT fired — use Manager::save
when per-row signal semantics are required.
Sourcepub async fn fetch_pg(self, pool: &PgPool) -> Result<Vec<T>, Error>
pub async fn fetch_pg(self, pool: &PgPool) -> Result<Vec<T>, Error>
Run the SELECT against an explicit PgPool and return every
matching row. Bound by FromRow<PgRow> alone so models with
Postgres-only field types compile.
Sourcepub async fn first_pg(self, pool: &PgPool) -> Result<Option<T>, Error>
pub async fn first_pg(self, pool: &PgPool) -> Result<Option<T>, Error>
Run the SELECT against an explicit PgPool with LIMIT 1.
Sourcepub async fn count_pg(self, pool: &PgPool) -> Result<i64, Error>
pub async fn count_pg(self, pool: &PgPool) -> Result<i64, Error>
Run SELECT COUNT(*) against an explicit PgPool. No FromRow
bound on T — the count tuple type is (i64,).
Trait Implementations§
Auto Trait Implementations§
impl<T> !RefUnwindSafe for QuerySet<T>
impl<T> !UnwindSafe for QuerySet<T>
impl<T> Freeze for QuerySet<T>
impl<T> Send for QuerySet<T>where
T: Send,
impl<T> Sync for QuerySet<T>where
T: Sync,
impl<T> Unpin for QuerySet<T>where
T: Unpin,
impl<T> UnsafeUnpin for QuerySet<T>
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
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
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 moreSource§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);