-
-
Notifications
You must be signed in to change notification settings - Fork 0
RAprogramm edited this page Jan 7, 2026
·
5 revisions
生成类型安全的查询结构体用于过滤实体。过滤支持分页、搜索和范围查询,具有编译时安全性。
#[derive(Entity)] #[entity(table = "products")] pub struct Product { #[id] pub id: Uuid, #[field(create, update, response)] #[filter] pub name: String, #[field(create, update, response)] #[filter(like)] pub description: String, #[field(create, update, response)] #[filter(range)] pub price: i64, #[field(create, response)] #[filter] pub category_id: Uuid, #[field(response)] #[auto] #[filter(range)] pub created_at: DateTime<Utc>, }
/// 用于过滤Product实体的查询参数。 #[derive(Debug, Clone, Default)] pub struct ProductQuery { /// 按精确name匹配过滤。 pub name: Option<String>, /// 按description模式过滤(ILIKE)。 pub description: Option<String>, /// 按最低价格过滤。 pub price_from: Option<i64>, /// 按最高价格过滤。 pub price_to: Option<i64>, /// 按精确category_id匹配过滤。 pub category_id: Option<Uuid>, /// 按最早created_at过滤。 pub created_at_from: Option<DateTime<Utc>>, /// 按最晚created_at过滤。 pub created_at_to: Option<DateTime<Utc>>, /// 最大结果数。 pub limit: Option<i64>, /// 跳过的结果数。 pub offset: Option<i64>, }
#[async_trait] pub trait ProductRepository: Send + Sync { // ... 标准CRUD方法 /// 使用过滤器查询产品。 async fn query(&self, query: ProductQuery) -> Result<Vec<Product>, Self::Error>; }
SELECT id, name, description, price, category_id, created_at FROM products WHERE ($1 IS NULL OR name = $1) AND ($2 IS NULL OR description ILIKE $2) AND ($3 IS NULL OR price >= $3) AND ($4 IS NULL OR price <= $4) AND ($5 IS NULL OR category_id = $5) AND ($6 IS NULL OR created_at >= $6) AND ($7 IS NULL OR created_at <= $7) ORDER BY created_at DESC LIMIT $8 OFFSET $9
过滤字段等于提供值的记录。
#[filter] pub status: String, #[filter(eq)] // 同上 pub category_id: Uuid,
生成:
pub status: Option<String>, pub category_id: Option<Uuid>,
SQL:
WHERE status = $1 AND category_id = $2
使用不区分大小写的模式匹配(ILIKE)过滤。
#[filter(like)] pub name: String, #[filter(like)] pub description: String,
生成:
pub name: Option<String>, pub description: Option<String>,
SQL:
WHERE name ILIKE $1 AND description ILIKE $2
用法:
let query = ProductQuery { name: Some("%widget%".into()), // 包含 "widget" description: Some("premium%".into()), // 以 "premium" 开头 ..Default::default() };
在范围内过滤(包含边界)。
#[filter(range)] pub price: i64, #[filter(range)] pub created_at: DateTime<Utc>,
生成:
pub price_from: Option<i64>, pub price_to: Option<i64>, pub created_at_from: Option<DateTime<Utc>>, pub created_at_to: Option<DateTime<Utc>>,
SQL:
WHERE price >= $1 AND price <= $2 AND created_at >= $3 AND created_at <= $4
// 按类别查找产品 let query = ProductQuery { category_id: Some(electronics_category_id), ..Default::default() }; let products = repo.query(query).await?;
// 获取第2页(每页20项) let query = ProductQuery { limit: Some(20), offset: Some(20), ..Default::default() }; let products = repo.query(query).await?;
// 搜索价格实惠的电子产品 let query = ProductQuery { category_id: Some(electronics_category_id), price_from: Some(0), price_to: Some(10000), // 100ドル.00 name: Some("%phone%".into()), limit: Some(50), ..Default::default() }; let products = repo.query(query).await?;
// 获取本月创建的产品 let now = Utc::now(); let month_start = now.with_day(1).unwrap().date_naive().and_hms_opt(0, 0, 0).unwrap(); let query = ProductQuery { created_at_from: Some(month_start.and_utc()), created_at_to: Some(now), ..Default::default() }; let products = repo.query(query).await?;
use axum::{extract::Query, Json}; #[derive(Deserialize)] pub struct ProductQueryParams { pub name: Option<String>, pub category_id: Option<Uuid>, pub min_price: Option<i64>, pub max_price: Option<i64>, pub page: Option<i64>, pub per_page: Option<i64>, } async fn list_products( Query(params): Query<ProductQueryParams>, pool: Extension<PgPool>, ) -> Result<Json<Vec<ProductResponse>>, AppError> { let page = params.page.unwrap_or(1); let per_page = params.per_page.unwrap_or(20).min(100); let query = ProductQuery { name: params.name.map(|n| format!("%{}%", n)), category_id: params.category_id, price_from: params.min_price, price_to: params.max_price, limit: Some(per_page), offset: Some((page - 1) * per_page), ..Default::default() }; let products = pool.query(query).await?; let responses: Vec<_> = products.into_iter().map(ProductResponse::from).collect(); Ok(Json(responses)) }
当启用 soft_delete 时,查询自动排除已删除的记录:
#[derive(Entity)] #[entity(table = "documents", soft_delete)] pub struct Document { #[id] pub id: Uuid, #[field(create, response)] #[filter(like)] pub title: String, #[field(skip)] pub deleted_at: Option<DateTime<Utc>>, }
生成的SQL:
SELECT * FROM documents WHERE deleted_at IS NULL AND ($1 IS NULL OR title ILIKE $1) LIMIT $2 OFFSET $3
包含已删除记录的附加方法:
async fn query_with_deleted(&self, query: DocumentQuery) -> Result<Vec<Document>, Self::Error>;
对于复杂查询,使用 sql = "trait" 并实现自定义过滤:
#[derive(Entity)] #[entity(table = "products", sql = "trait")] pub struct Product { /* ... */ } pub trait ProductQueryExt { async fn search_fulltext(&self, term: &str, limit: i64) -> Result<Vec<Product>, sqlx::Error>; async fn find_by_tags(&self, tags: &[String]) -> Result<Vec<Product>, sqlx::Error>; } #[async_trait] impl ProductQueryExt for PgPool { async fn search_fulltext(&self, term: &str, limit: i64) -> Result<Vec<Product>, sqlx::Error> { let rows: Vec<ProductRow> = sqlx::query_as( r#" SELECT * FROM products WHERE to_tsvector('english', name || ' ' || description) @@ plainto_tsquery('english', 1ドル) ORDER BY ts_rank(to_tsvector('english', name || ' ' || description), plainto_tsquery('english', 1ドル)) DESC LIMIT 2ドル "# ) .bind(term) .bind(limit) .fetch_all(self) .await?; Ok(rows.into_iter().map(Product::from).collect()) } async fn find_by_tags(&self, tags: &[String]) -> Result<Vec<Product>, sqlx::Error> { let rows: Vec<ProductRow> = sqlx::query_as( "SELECT * FROM products WHERE tags && 1ドル" ) .bind(tags) .fetch_all(self) .await?; Ok(rows.into_iter().map(Product::from).collect()) } }
- 默认分页 — 始终应用合理的限制以防止大结果集
- 验证模式 — 清理LIKE模式以防止SQL问题
- 索引过滤列 — 为经常过滤的字段创建数据库索引
- 使用特定过滤器 — 尽可能优先使用精确匹配而非模式匹配
- 与排序结合 — 考虑向查询结构体添加排序字段
🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어 | 🇪🇸 Español | 🇨🇳 中文
Getting Started
Features
Advanced
Начало работы
Возможности
Продвинутое
시작하기
기능
고급
Comenzando
Características
Avanzado
入门
功能
高级