如何在Diesel中通过方法传入排序条件构建查询?
我明白你在Diesel里动态传递排序条件时遇到的类型问题了——Diesel作为强类型ORM,对类型信息的要求确实比较严格,你踩的这两个坑其实是新手用Diesel动态构建查询时的常见问题,我来给你拆解下解决方案:
首先说你第一个返回Box<dyn BoxableExpression>的思路:这个问题出在trait对象上。Diesel的AppearsOnTable trait是和具体表的类型强绑定的,而dyn BoxableExpression这种trait对象会丢失底层具体类型的信息,Rust没办法在编译时确认这个trait对象满足AppearsOnTable的要求,所以会报错。
解决这个问题的最简单办法是用Rust的impl Trait返回类型,让编译器自动推导具体的排序表达式类型,而不是用trait对象封装。比如把你的函数改成这样:
use diesel::pg::Pg; use diesel::prelude::*; use diesel::expression_types::NotSelectable; fn get_order_by_clause(order_by: Option<String>) -> impl BoxableExpression<my_table::table, Pg, SqlType = NotSelectable> { match order_by { // 这里可以根据你的业务需求,匹配不同的排序参数 Some(col) if col.eq_ignore_ascii_case("start_time_asc") => my_table::start_time.asc(), Some(col) if col.eq_ignore_ascii_case("start_time_desc") => my_table::start_time.desc(), // 其他未匹配的情况用默认排序 _ => my_table::start_time.desc(), } }
这个函数会直接返回具体的排序表达式类型(比如Asc<my_table::columns::start_time>或者Desc<...>),这些具体类型都天然实现了AppearsOnTable trait,所以用在order()方法里完全没问题:
// 构建基础查询 let mut query = my_table::table.into_boxed(); // 获取排序条件 let order_clause = get_order_by_clause(Some("start_time_asc".to_string())); // 应用排序并执行查询 let results = query.order(order_clause).load::<MyTable>(&db_conn)?;
再来说你第二个尝试给已有的查询加排序的思路:你遇到的“overflow evaluating the requirement _: std::marker::Sized”错误,本质是因为你想让函数返回原查询类型T,但order()方法会返回一个全新的类型(比如Order<T, OrderClause>),这个类型和输入的T完全不一样。Rust在推导这个泛型类型时会陷入无限递归,所以报了溢出错误。
解决这个问题的核心是统一查询的类型,用BoxedQuery来封装查询——不管你给查询加多少order、filter操作,BoxedQuery的类型都是固定的,不会随操作变化。修改后的函数可以写成这样:
use diesel::pg::Pg; use diesel::prelude::*; // 这里的BoxedQuery是my_table对应的boxed查询类型 fn add_order_by_clause(order_by: Option<String>, mut query: my_table::BoxedQuery) -> my_table::BoxedQuery { match order_by { Some(col) if col.eq_ignore_ascii_case("start_time_asc") => query.order(my_table::start_time.asc()), Some(col) if col.eq_ignore_ascii_case("start_time_desc") => query.order(my_table::start_time.desc()), _ => query.order(my_table::start_time.desc()), } }
使用的时候,先把基础查询转成BoxedQuery,再传入函数:
let mut query = my_table::table.into_boxed(); // 给查询添加排序条件 query = add_order_by_clause(Some("start_time_desc".to_string()), query); // 执行查询 let results = query.load::<MyTable>(&db_conn)?;
最后再给你提个小建议:Diesel的设计思路就是用类型系统来保证查询的合法性,所以尽量避免用trait对象来封装查询部件,优先用impl Trait返回具体类型,或者用BoxedQuery来统一动态查询的类型,这样能避开大部分类型推导的坑。
内容来源于stack exchange

