查询构建器
基本查询
let users: Vec<User> = db.select::<User>().collect().await?;
let user: Vec<User> = db
.select::<User>()
.filter(|u| u.id.eq(1))
.range(..1)
.collect()
.await?;
过滤条件
比较运算
.filter(|u| u.name.eq("Alice".to_string()))
.filter(|u| u.age.ne(18)) // != 不等
.filter(|u| u.age.ge(18)) // >= 大于等于
.filter(|u| u.age.gt(18)) // > 大于
.filter(|u| u.age.le(65)) // <= 小于等于
.filter(|u| u.age.lt(65)) // < 小于
LIKE 模糊匹配
.filter(|u| u.name.like("Al%")) // 自定义模式
.filter(|u| u.name.contains("alice")) // 包含
.filter(|u| u.name.starts_with("Al")) // 以...开头
.filter(|u| u.name.ends_with("ce")) // 以...结尾
可与其他条件组合:
.filter(|u| u.name.contains("li").and(u.age.gt(29)))
数组包含(SQL 级下推)
对于 Vec<String> 字段,contains / contains_all / overlaps 会下推为 SQL 谓词 参与分页与计数,不会退化为内存过滤:
let users: Vec<User> = db
.select::<User>()
.filter(|u| u.tags.contains("admin")) // 包含该元素
.filter(|u| u.tags.contains_all(vec!["admin".to_string(), "ops".to_string()])) // 全部包含
.filter(|u| u.tags.overlaps(vec!["ops".to_string(), "dev".to_string()])) // 任一交集
.collect()
.await?;
各后端生成等价语义的方言 SQL:PostgreSQL @> / &&,MySQL JSON_CONTAINS / JSON_OVERLAPS,SQLite json_each,MSSQL OPENJSON,ClickHouse has/hasAll, DuckDB list_contains/list_has_any。
QuestDB 与 InfluxDB 不支持数组谓词,构造查询时返回 UnsupportedFeature 错误。
NULL 判断
.filter(|u| u.email.is_null()) // IS NULL
.filter(|u| u.email.is_not_null()) // IS NOT NULL
BETWEEN 范围
.filter(|u| u.age.between(18, 30)) // age BETWEEN 18 AND 30
IN 与 NOT IN
.filter(|u| u.age.is_in(&vec![18, 20, 22]))
.filter(|u| u.name.is_in(&vec!["Alice".to_string(), "Bob".to_string()]))
.filter(|u| u.age.is_not_in(&vec![18, 20])) // NOT IN
is_in 和 is_not_in 也支持子查询:
.filter(|u| u.id.is_in(db.select::<Role>().map_to(|r| r.user_id)))
.filter(|u| u.id.is_not_in(db.select::<Role>().map_to(|r| r.user_id)))
组合条件
.filter(|u| u.age.ge(18))
.filter(|u| u.age.le(65))
.filter(|u| u.age.ge(18).and(u.name.eq("Alice".to_string())))
.filter(|u| u.age.lt(18).or(u.age.gt(65)))
模型级过滤器
#[filter] 会为模型生成同名链式方法(扩展 trait 由派生生成本地作用域,同模块内无需导入):
let orders: Vec<Order> = db
.select::<Order>()
.filter_tenant(tenant_id)
.filter_valid()
.collect()
.await?;
运行时动态字段
field 可把运行时字段名或列名转换为安全查询条件;字段不存在时,执行查询时返回错误:
let users: Vec<User> = db
.select::<User>()
.filter(|u| u.field("email").ne("a@example.com"))
.order_by_dynamic(|u| u.field("age").desc())
.collect()
.await?;
排序
.order_by(|u| u.name.asc())
.order_by_desc(|u| u.age)
.order_by(|u| u.age.desc())
.order_by(|u| u.name.asc())
分页
.range(0..10)
.range(10..20)
.range(..5)
.range(10..)
深分页可用游标分页:cursor_by 声明游标列(需与排序一致),fetch_page 返回带 next_cursor() 的 CursorPage,游标传给 after() 取下一页:
let page = db
.select::<User>()
.order_by_desc(|u| u.id)
.cursor_by(|u| u.id)
.limit(10)
.fetch_page()
.await?;
let next = db
.select::<User>()
.order_by_desc(|u| u.id)
.cursor_by(|u| u.id)
.after(page.next_cursor().expect("还有下一页"))
.limit(10)
.fetch_page()
.await?;
去重查询
// SELECT DISTINCT *
let users = db.select::<User>().distinct().collect().await?;
// SELECT DISTINCT name
let names: Vec<String> = db.select::<User>().distinct().map_to(|u| u.name).collect().await?;
可与 filter、order_by、range 等组合使用。
忽略字段
使用 ignore 查询完整模型时,可以用常量替代指定字段的真实值。字段仍然存在于返回模型中,但不会从数据库读取:
let users: Vec<User> = db
.select::<User>()
.ignore(|u| (u.id, u.email))
.collect()
.await?;
例如,整数主键会读为 0,可空字段会读为 None。该方法适合不需要读取敏感或大字段的查询。
单条查询
// 等价于 range(..1),只取第一条
let user: Option<User> = db.select::<User>().filter(|u| u.age.ge(18)).first().await?;
递归 CTE
自关联树可用 descendants / ancestors 生成递归 CTE。第一个字段是节点 id,第二个字段是父节点 id:
let nodes: Vec<Category> = db
.select::<Category>()
.descendants(|c| (c.id, c.parent_id), root_id)
.order_by(|c| c.id.asc())
.collect()
.await?;
SQLite 当前 turso 后端可能不支持执行递归 CTE,可使用 to_sql() 生成 SQL 后交给支持的后端执行。
流式查询 (stream)
let mut stream = db
.select::<User>()
.filter(|u| u.age.ge(18))
.stream()
.into_iter()
.await?;
while let Some(user_result) = stream.next().await {
let user = user_result?;
println!("{:?}", user);
}
字段投影 (map_to)
let names: Vec<String> = db
.select::<User>()
.map_to(|u| u.name)
.collect::<Vec<String>>()
.await?;
let name_age: Vec<(String, i32)> = db
.select::<User>()
.map_to(|u| (u.name, u.age))
.collect()
.await?;
let labeled: Vec<(i32, String, String)> = db
.select::<User>()
.map_to(|u| {
(
u.id.alias("user_id"),
u.email,
ormer::expr!(match u.status {
"paid" => "done",
"new" => "open",
_ => "other",
})
.alias("status_label"),
)
})
.collect()
.await?;
let user_ids: Vec<UserId> = db
.select::<User>()
.map_to(|u| u.id)
.collect_with(|id| UserId { id })
.await?;
单个投影项可以直接调用 .alias("...") 改名。
如果目标仍是另一个 Model,可以使用 map_to_model,框架会按目标模型的列名生成别名:
let archived: Vec<ArchiveUser> = db
.select::<User>()
.map_to_model::<_, ArchiveUser>(|u| u.id)
.collect()
.await?;
查询组合
let users: Vec<User> = db
.select::<User>()
.filter(|u| u.age.ge(18))
.order_by(|u| u.name.asc())
.range(0..10)
.collect()
.await?;
let base_query = db.select::<User>().filter(|u| u.age.ge(18));
let adults_cn = base_query.clone()
.filter(|u| u.country.eq("CN".to_string()))
.collect::<Vec<_>>()
.await?;
let adults_us = base_query
.filter(|u| u.country.eq("US".to_string()))
.collect::<Vec<_>>()
.await?;
完整示例
use ormer::{Database, DbType, Model};
#[derive(Debug, Model)]
#[table = "users"]
struct User {
#[primary(auto)]
id: i32,
name: String,
age: i32,
email: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db = Database::connect(DbType::Sqlite, "file:test.db").await?;
db.create_table::<User>().execute().await?;
db.insert(&vec![
User { id: 1, name: "Alice".to_string(), age: 25, email: None },
User { id: 2, name: "Bob".to_string(), age: 30, email: None },
User { id: 3, name: "Charlie".to_string(), age: 35, email: None },
]).await?;
let all: Vec<User> = db.select::<User>().collect().await?;
let adults: Vec<User> = db
.select::<User>()
.filter(|u| u.age.ge(18))
.collect()
.await?;
let sorted: Vec<User> = db
.select::<User>()
.order_by_desc(|u| u.age)
.collect()
.await?;
let page: Vec<User> = db
.select::<User>()
.order_by(|u| u.id.asc())
.range(0..2)
.collect()
.await?;
let names: Vec<String> = db
.select::<User>()
.map_to(|u| u.name)
.collect::<Vec<String>>()
.await?;
db.drop_table::<User>().execute().await?;
Ok(())
}