QuerySet
QuerySet<T> is the lazy, chainable query builder. The canonical entry point is
ObjectsManager::<T>::new().
Some examples use a short Post::objects() helper. That helper is not required
by the macro; define it yourself if you want the shorter style:
use ryx_rs::ObjectsManager;
impl Post {
fn objects() -> ObjectsManager<Self> {
ObjectsManager::new()
}
}
Entry Pointsโ
// ObjectsManager<T> โ the entry point
Post::objects() // โ ObjectsManager<Post>
Post::objects().all() // โ QuerySet<Post> (all rows)
Post::objects().filter("active", true) // โ QuerySet<Post>
Post::objects().exclude("status", "draft") // โ QuerySet<Post>
Post::objects().get("id", 1i64) // โ QuerySet<Post> (expects 1 row)
Post::objects().create() // โ InsertBuilder<Post>
QuerySet methods are chainable and lazy โ no query hits the database until awaited.
Filteringโ
// By field + value pair
Post::objects().filter("active", true);
Post::objects().filter("views__gte", 100i64);
// With Q expressions
Post::objects().filter(Q::or(
Q::new("active", true),
Q::new("views__gte", 1000),
));
// Exclude (NOT)
Post::objects().exclude("status", "draft");
Post::objects().exclude(Q::not("status", "draft"));
// Chaining (AND)
Post::objects()
.filter("active", true)
.filter("views__gt", 100i64);
Supported Lookupsโ
| Lookup | SQL | Example |
|---|---|---|
exact | = ? | .filter("title", "Hello") |
gt | > ? | .filter("views__gt", 100) |
gte | >= ? | .filter("views__gte", 100) |
lt | < ? | .filter("views__lt", 50) |
lte | <= ? | .filter("views__lte", 1000) |
contains | LIKE '%?%' | .filter("title__contains", "Rust") |
icontains | LOWER(col) LIKE ? | .filter("title__icontains", "rust") |
startswith | LIKE '?%' | .filter("title__startswith", "Draft") |
istartswith | LOWER(col) LIKE ? | .filter("title__istartswith", "draft") |
endswith | LIKE '%?' | .filter("slug__endswith", "-ryx") |
iendswith | LOWER(col) LIKE ? | .filter("slug__iendswith", "-Ryx") |
in | IN (?, ...) | .filter("status__in", vec!["a", "b"]) |
isnull | IS NULL / IS NOT NULL | .filter("author__isnull", true) |
range | BETWEEN ? AND ? | .filter("views__range", (100i64, 1000i64)) |
Date Transformsโ
.filter("created_at__year", 2024i64);
.filter("created_at__month__gte", 6i64);
.filter("created_at__day", 15i64);
.filter("created_at__hour", 14i64);
.filter("created_at__week", 20i64);
.filter("created_at__quarter", 2i64);
Q Objectsโ
use ryx_rs::Q;
// OR
Post::objects().filter(Q::or(
Q::new("active", true),
Q::new("views__gte", 1000),
));
// AND
Post::objects().filter(Q::and(
Q::new("active", true),
Q::new("featured", true),
));
// NOT
Post::objects().filter(Q::not("status", "draft"));
// Nested
Post::objects().filter(Q::or(
Q::and(Q::new("active", true), Q::new("views__gte", 500)),
Q::new("featured", true),
));
Orderingโ
// Ascending
Post::objects().order_by("title");
// Descending (prefix with -)
Post::objects().order_by("-views");
// Multiple fields (single calls)
Post::objects().order_by("-views").order_by("title");
// Multiple fields at once
Post::objects().order_by_all(&["-views", "title"]);
Paginationโ
Post::objects().limit(10);
Post::objects().limit(10).offset(20);
Aggregationsโ
use ryx_rs::agg::{count, sum, avg, min, max, count_distinct};
let stats = Post::objects()
.aggregate(&[
count("total", "id"),
sum("total_views", "views"),
avg("avg_views", "views"),
min("min_views", "views"),
max("max_views", "views"),
count_distinct("unique_authors", "author_id"),
]).await?;
// stats: HashMap<String, SqlValue>
Annotateโ
let annotated = Post::objects()
.annotate(&[count("comment_count", "id")])
.await?;
// annotated: Vec<HashMap<String, SqlValue>>
Values & ValuesListโ
// Vec<HashMap<String, SqlValue>>
let values = Post::objects()
.values(&["title", "views"])
.await?;
// Vec<Vec<SqlValue>>
let list = Post::objects()
.values_list(&["title", "views"])
.await?;
// GROUP BY โ values + annotate
let grouped = Post::objects()
.values(&["author_id"])
.annotate(&[
count("post_count", "id"),
sum("total_views", "views"),
])
.order_by("-total_views")
.await?;
Relationshipsโ
let posts: Vec<Post> = Post::objects()
.all()
.select_related(&["author"])
.all().await?;
for post in &posts {
if let Some(author) = &post.author {
println!("{} โ by {}", post.title, author.name);
}
}
select_related is only available on models with #[relation(...)] attributes.
Execution Methodsโ
These execute the query and return results:
// All rows
let all: Vec<Post> = Post::objects().all().await?;
// First match (None if empty)
let first: Option<Post> = Post::objects()
.filter("active", true)
.order_by("title")
.first().await?;
// Get by field (panics if not found โ use filter + first for safe access)
let post: Post = Post::objects().get("id", 1i64).await?;
// Count
let count: i64 = Post::objects().filter("active", true).count().await?;
// Exists
let exists: bool = Post::objects()
.filter("title__startswith", "Draft")
.exists().await?;
// Distinct
Post::objects().distinct().all().await?;
Update & Deleteโ
// Bulk update (returns affected row count)
let updated: u64 = Post::objects()
.filter("author", "bob")
.update(vec![("views", 9999i64)])
.await?;
// Bulk delete (returns affected row count)
let deleted: u64 = Post::objects()
.filter("title__startswith", "Draft")
.delete().await?;
Multi-DBโ
Post::objects().using("logs").all().await?;
Post::objects().using("users").filter("active", true).all().await?;
Cachingโ
let posts: Vec<Post> = Post::objects()
.filter("active", true)
.cache(60, Some("active_posts")) // TTL 60s, named key
.all().await?;
// Without explicit key
let posts: Vec<Post> = Post::objects()
.filter("active", true)
.cache(60, None)
.all().await?;
Returns a CachedQuerySet<T> with a single .all() method.
Streaming (Keyset Pagination)โ
let mut stream = Post::objects()
.filter("active", true)
.order_by("id")
.stream(100, Some("id")); // chunk_size, keyset field
while let Some(chunk) = stream.next_chunk().await? {
for post in chunk {
// process 100 at a time
}
}
QueryStream<T> yields Vec<T> chunks until exhausted.
Debug: View SQLโ
let sql = Post::objects()
.filter("title__contains", "Rust")
.sql()?;
// โ SELECT * FROM "posts" WHERE "title" LIKE '%Rust%'
Value Types (IntoSqlValue)โ
The IntoSqlValue trait converts Rust values for query parameters. Built-in types supported:
| Rust Type | SQL |
|---|---|
i32, i64 | Integer |
f64 | Float |
bool | Boolean |
String, &str | Text |
Option<T> | Nullable T |
Vec<T> | Multiple values (for in) |
chrono::NaiveDateTime | Timestamp |
chrono::NaiveDate | Date |
Post::objects().filter("views", 42i64);
Post::objects().filter("active", true);
Post::objects().filter("title", "Hello");
Post::objects().filter("author_id", None::<i64>);
Post::objects().filter("status__in", vec!["draft", "published"]);
Next Stepsโ
- Migrations โ Schema management
- CRUD โ Create, read, update, delete