Skip to content

Query Builder

QueryBuilder<T> is obtained from T::query(&pool) and provides a fluent API for building queries against the table mapped to T. All builder methods consume self and return a new QueryBuilder. Terminal methods (fetch_all, fetch_one, count, delete, update) are async fn.

Obtaining a builder

use rust_web_server::model::DbPool;
let pool = DbPool::from_env().await?;
// QueryBuilder<User> — tied to the `users` table
let qb = User::query(&pool);

Filtering

Equality filter

where_eq(col, val) adds a col = ? condition. The placeholder is ? for SQLite and MySQL, $N for PostgreSQL — the builder handles the substitution automatically.

let admins: Vec<User> = User::query(&pool)
.where_eq("role", "admin")
.fetch_all().await?;

Chain multiple calls to AND conditions together:

let result = User::query(&pool)
.where_eq("role", "admin")
.where_eq("active", true)
.fetch_all().await?;

Raw filter

filter(expr, params) accepts a raw SQL fragment and a Vec<Value>. Use ? as the placeholder regardless of backend — the builder converts to $N for PostgreSQL automatically.

use rust_web_server::model::Value;
let adults = User::query(&pool)
.filter("age >= ?", vec![Value::Int(18)])
.fetch_all().await?;

Ordering

use rust_web_server::model::Order;
let recent = User::query(&pool)
.order_by("created_at", Order::Desc)
.fetch_all().await?;
let alphabetical = User::query(&pool)
.order_by("name", Order::Asc)
.fetch_all().await?;

Pagination

.limit(n) and .offset(n) map directly to SQL LIMIT and OFFSET, for full manual control:

let page = 2u64;
let page_size = 20u64;
let users = User::query(&pool)
.order_by("id", Order::Asc)
.limit(page_size)
.offset((page - 1) * page_size)
.fetch_all().await?;

For most list endpoints, .paginate(page, per_page) (offset-based, with total counts) and .paginate_after(cursor, per_page) (cursor/keyset-based, for large tables) do the COUNT(*) + LIMIT/OFFSET bookkeeping above for you and return a Page<T>/CursorPage<T> — see Pagination for both, plus a built-in Link response header builder.

Fetching results

MethodSQLReturn type
fetch_all().awaitSELECT * FROM … WHERE … ORDER BY … LIMIT … OFFSET …Result<Vec<T>, DbError>
fetch_one().awaitsame with LIMIT 1Result<Option<T>, DbError>
count().awaitSELECT COUNT(*) FROM … WHERE …Result<i64, DbError>
// all matching rows
let users: Vec<User> = User::query(&pool)
.where_eq("active", true)
.fetch_all().await?;
// first match only
let user: Option<User> = User::query(&pool)
.where_eq("email", "alice@example.com")
.fetch_one().await?;
// count without loading rows
let total: i64 = User::query(&pool)
.where_eq("role", "admin")
.count().await?;

Mutation

Delete matching rows

User::query(&pool)
.where_eq("active", false)
.delete().await?;

Update a single column

update(col, val).await issues UPDATE table SET col = ? WHERE …. Combine with filters to scope the update.

User::query(&pool)
.where_eq("id", 42i64)
.update("role", "moderator").await?;

Placeholder rules

The builder transparently converts between placeholder styles:

  • SQLite / MySQL?
  • PostgreSQL$1, $2, … (auto-numbered from left to right)

You never need to pick a style; write ? in raw .filter() expressions and the builder takes care of the rest.

Complete example: paginated list endpoint

use rust_web_server::header::Header;
use rust_web_server::model::{DbPool, Order, Page};
use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
use std::sync::Arc;
async fn list_users(pool: Arc<DbPool>, page: u64, per_page: u64) -> Response {
let per_page = per_page.min(100);
let page: Page<User> = User::query(&pool)
.where_eq("active", true)
.order_by("created_at", Order::Desc)
.paginate(page, per_page).await
.unwrap_or_else(|_| Page::new(vec![], page, per_page, 0));
let mut r = Response::new();
r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
if let Some(link) = page.link_header("https://api.example.com/users") {
r.headers.push(Header { name: "Link".to_string(), value: link });
}
// serialize `page.items`, `page.total_items`, `page.total_pages` into the response body
r
}

See Pagination for .paginate_after() (cursor/keyset pagination) and more on the Link header.