Diesel在表和列上的查询泛型:尚未为 `<::Query as AsQuery>::Query` 实现trait `diesel::Table`
我正在尝试实现一个函数,使用diesel.rs从数据库中检索匹配的文本。
如果我事先知道表和列名,那样就能正常工作,但我需要查看几个表和列,因此我想创建一个通用函数,因为查询总是相同的。我尝试从一些回答中拼凑出思路,例如 将任意Diesel表作为参数传递、https://github.com/diesel-rs/diesel/discussions/3887 和 https://github.com/diesel-rs/diesel/discussions/2999,但我觉得自己还缺少某些东西。
作为一个最小的例子,我创建了两个相似的表:
CREATE TABLE IF NOT EXISTS my_one_table(
id BIGINT UNSIGNED NOT NULL,
column1 VARCHAR(50) NOT NULL,
column2 VARCHAR(50) NOT NULL,
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS my_other_table(
id BIGINT UNSIGNED NOT NULL,
column1 VARCHAR(50) NOT NULL,
column2 VARCHAR(50) NOT NULL,
PRIMARY KEY(id)
);
并使用下面的Rust代码:
use diesel::prelude::*;
use diesel::dsl;
use diesel::query_dsl::methods::FilterDsl;
use diesel::query_dsl::LoadQuery;
use diesel::query_dsl::methods::LimitDsl;
use diesel::query_builder::AsQuery;
diesel::table! {
my_one_table (id) {
id -> Unsigned<Bigint>,
column1 -> Varchar,
column2 -> Varchar,
}
}
diesel::table! {
my_other_table (id) {
id -> Unsigned<Bigint>,
column1 -> Varchar,
column2 -> Varchar,
}
}
fn internal_get_by_id<T, C>(
diesel_table: T,
column: C,
conn: &MysqlConnection,
term: String,
) -> Vec<String>
where
T: Table + FilterDsl<dsl::Like<C, String>>,
C: Column + Expression<SqlType = diesel::sql_types::Text>,
dsl::Filter<T, dsl::Like<C, String>>: LimitDsl,
dsl::Limit<dsl::Filter<T, dsl::Like<C, String>>>: for<'a> LoadQuery<'a, MysqlConnection, String>, <T as AsQuery>::Query: diesel::Table
{
diesel_table
.filter(column.like(term))
.select(column)
.load(conn)
.expect("Error") // TODO: Proper error handling outside the context of MWE
}
但我缺少一些trait bound,因为我收到了这样的错误信息:
Compiling mwe v0.1.0 (/home/mox/projects/software/mwe)
error[E0277]: the trait bound `<<T as AsQuery>::Query as AsQuery>::Query: diesel::Table` is not satisfied
--> src/db/completer_helpers.rs:25:1
|
25 | / fn internal_get_by_id<T, C>(
26 | | diesel_table: T,
27 | | column: C,
28 | | conn: &MysqlConnection,
... |
34 | | dsl::Filter<T, dsl::Like<C, String>>: LimitDsl,
35 | | dsl::Limit<dsl::Filter<T, dsl::Like<C, String>>>: for<'a> LoadQuery<'a, MysqlConnection, String>, <T as AsQuery>::Query: diesel::Table
| |__________________________________________________________________________________________________________________________________________^ the trait `diesel::Table` is not implemented for `<<T as AsQuery>::Query as AsQuery>::Query`
|
= note: required for `<<T as AsQuery>::Query as AsQuery>::Query` to implement `FilterDsl<expression::grouped::Grouped<expression::operators::Like<C, expression::bound::Bound<Text, std::string::String>>>>`
help: consider further restricting the associated type
|
35 | dsl::Limit<dsl::Filter<T, dsl::Like<C, String>>>: for<'a> LoadQuery<'a, MysqlConnection, String>, <T as AsQuery>::Query: diesel::Table, <<T as AsQuery>::Query as AsQuery>::Query: diesel::Table
| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
error[E0275]: overflow evaluating the requirement `<<<T as AsQuery>::Query as AsQuery>::Query as FilterDsl<expression::grouped::Grouped<expression::operators::Like<C, expression::bound::Bound<..., ...>>>>>::Output == _`
--> src/db/completer_helpers.rs:25:1
|
25 | / fn internal_get_by_id<T, C>(
26 | | diesel_table: T,
27 | | column: C,
28 | | conn: &MysqlConnection,
... |
34 | | dsl::Filter<T, dsl::Like<C, String>>: LimitDsl,
35 | | dsl::Limit<dsl::Filter<T, dsl::Like<C, String>>>: for<'a> LoadQuery<'a, MysqlConnection, String>, <T as AsQuery>::Query: diesel::Table
| |__________________________________________________________________________________________________________________________________________^
Some errors have detailed explanations: E0275, E0277.
For more information about an error, try `rustc --explain E0275`.
error: could not compile `mwe` (bin "mwe") due to 2 previous errors
到目前为止,编译器的错误信息在帮我找到正确的边界方面一直非常有用,但现在我遇到了困难:编译器建议在where子句中加入 <<T as AsQuery>::Query as AsQuery>::Query: diesel::Table,但如果我这样做,得到的基本还是相同的错误,只是被更多的 <> 包裹起来。
背景:为什么我要从我的表中只检索匹配的字符串?我正在尝试实现一个完成功能/建议机制,用于提示表中已存在的条目。
解决方案
花了一段时间,但我终于弄清楚我的代码应该长什么样子:
use diesel::prelude::*;
pub fn get_matching_strings<T, C>(
conn: &mut MysqlConnection,
table: T,
column: C,
term: String,
) -> Vec<String>
where
T: diesel::Table + diesel::query_dsl::methods::FilterDsl<diesel::dsl::Like<C, String>>,
diesel::dsl::Filter<T, diesel::dsl::Like<C, String>>: diesel::query_dsl::methods::SelectDsl<C>,
diesel::dsl::Select<diesel::dsl::Filter<T, diesel::dsl::Like<C, String>>, C>: for<'a> diesel::query_dsl::LoadQuery<'a, MysqlConnection, String>,
C: diesel::Expression + diesel::TextExpressionMethods + Copy,
<C as diesel::Expression>::SqlType: diesel::sql_types::SqlType,
std::string::String: diesel::expression::AsExpression<<C as Expression>::SqlType>,
{
use diesel::query_dsl::methods::SelectDsl;
// I used this for stepwise development of the bounds:
//let filtered = table.filter(column.like(format!("%{term}%")));
//let selected = filtered.select(column);
//let loaded = selected.load(conn);
//vec![]
table
.filter(column.like(format!("%{term}%")))
.select(column)
.load(conn)
.unwrap_or_else(|err| vec![format!("Completion error: Could not load db data: {err:?}.")])
}
本质上我的边界条件是错的。再多看一些这篇很棒的回答之后,我终于理解,大多数情况下,我需要确保我要使用的方法在我的Table和 Column上可用。
我把查询分成几部分(并在此期间使用 vec![] 返回一个空数组)来按步骤地发展边界条件,如文中所述:
首先,如果没有trait bounds,且 vec![] 是函数体中唯一的语句,代码就能编译。
其次,我只是把 let filtered = ... 语句注释进来。编译器立刻会抛出一些并不特别有用的错误信息。但通过一些逻辑处理,就能理解:T必须是一个Table,且因为我们要对其调用 filter(),所以它必须实现 FilterDsl trait。既然我们用 like 作为谓词,diesel::dsl::Like 就成为 FilterDsl 的参数。此外,我们正在把列与字符串进行比较,因此这必须是 like 的参数。
第三步:这在编译时会产生一个错误,表示对 AsExpression 的绑定对 String 不成立。这很有道理,因为 like 要求第二个参数实现它。因此我可以直接复制编译器给出的那一行。
第四步:我们 like 的返回类型是 diesel::dsl::Filter<T, diesel::dsl::Like<C, String>>。在这个类型上我们将调用 select(),因此它必须具有相应的trait:diesel::query_dsl::methods::SelectDsl<C>(作为参数的C,因为我们只是选择那一列)。
按照这种方法,我最终得到了上面展示的代码。非常感谢 Neikos 给出的最初解释,帮助我走上正确的方向。