7

在玩 Diesel 时,我一直在编写一个函数,该函数将Strings 的向量作为输入并执行以下操作:

  1. 将所有Strings 组合成一个大查询
  2. Connection
  3. 处理结果
  4. 返回一个Vec

如果我一步构建查询,它就可以正常工作:

fn get_books(authors: Vec<String>, connection: SqliteConnection) {
    use schema::ebook::dsl::*;

    let inner = author
        .like(format!("%{}%", authors[0]))
        .and(author.like(format!("%{}%", authors[1])))
        .and(author.like(format!("%{}%", authors[2])));

    ebook
        .filter(inner)
        .load::<Ebook>(&connection)
        .expect("Error loading ebook");
}

如果我尝试以更多步骤生成查询(需要使用输入向量的可变长度),我无法编译它:

fn get_books(authors: Vec<String>, connection: SqliteConnection) {
    use schema::ebook::dsl::*;

    let mut inner = author
        .like(format!("%{}%", authors[0]))
        .and(author.like(format!("%{}%", authors[1]))); // <1>

    inner = inner.and(author.like(format!("%{}%", authors[2]))); // <2>

    ebook
        .filter(inner)
        .load::<Ebook>(&connection)
        .expect("Error loading ebook");
}

这会产生以下错误:

inner = inner.and(author.like(format!("%{}%",authors[2])));
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `diesel::expression::operators::Like`, found struct `diesel::expression::operators::And`

我不明白为什么 Rust 需要 aLike而不是And. 函数 an 行 line 标记<1>返回 an And,因此 anAnd存储在inner.

我错过了什么?为什么第一个代码可以编译而第二个不会?生成这种查询的正确方法是什么?

4

1 回答 1

8

您需要做的第一件事是查看完整的错误消息:

error[E0308]: mismatched types
  --> src/main.rs:23:13
   |
23 |     inner = inner.and(author.like(format!("%{}%", authors[2])));//<2>
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `diesel::expression::operators::Like`, found struct `diesel::expression::operators::And`
   |
   = note: expected type `diesel::expression::operators::And<diesel::expression::operators::Like<_, _>, _>`
              found type `diesel::expression::operators::And<diesel::expression::operators::And<diesel::expression::operators::Like<_, _>, diesel::expression::operators::Like<schema::ebook::columns::author, diesel::expression::bound::Bound<diesel::sql_types::Text, std::string::String>>>, _>`

它很长,但那是因为它完全合格。让我们稍微缩短最后一部分:

expected type `And<Like<_, _>, _>`
   found type `And<And<Like<_, _>, Like<author, Bound<Text, String>>>, _>`

如果您查看 的文档and,您会看到每次调用都会and消耗接收器并返回一个全新的类型 - And

fn and<T: AsExpression<Bool>>(self, other: T) -> And<Self, T::Expression>

这是 Diesel 在没有运行时开销的情况下生成强类型 SQL 表达式的能力的核心。所有的工作都委托给类型系统。事实上,Diesel 的创建者有一个完整的演讲,他展示了 Diesel 推动类型系统的程度以及它有什么好处

回到您的问题,不可能将 an 存储And<_, _>在与 an 相同的位置,And<And<_, _>, _>因为它们将具有不同的大小并且实际上是不同的类型。从根本上讲,这与尝试将整数存储在布尔值中相同。

事实上,没有办法知道你需要什么具体类型,因为你甚至不知道你将有多少条件——这取决于向量的大小。

在这种情况下,我们必须放弃静态调度并通过trait object转向动态调度。Diesel 在这种情况下有一个特定的特征(也有很好的例子)BoxableExpression:.

剩下的部分是将您的作者转换为like表达式并将它们组合起来。然而,我们需要一个基本情况,因为 whenauthors是空的。我们为此构造了一个平凡的真实陈述(author = author)。

#[macro_use]
extern crate diesel;

use diesel::SqliteConnection;
use diesel::prelude::*;
use diesel::sql_types::Bool;

mod schema {
    table! {
        ebook (id) {
            id -> Int4,
            author -> Text,
        }
    }
}

fn get_books(authors: Vec<String>, connection: SqliteConnection) {
    use schema::ebook::dsl::*;

    let always_true = Box::new(author.eq(author));
    let query: Box<BoxableExpression<schema::ebook::table, _, SqlType = Bool>> = authors
        .into_iter()
        .map(|name| author.like(format!("%{}%", name)))
        .fold(always_true, |query, item| {
            Box::new(query.and(item))
        });

    ebook
        .filter(query)
        .load::<(i32, String)>(&connection)
        .expect("Error loading ebook");
}

fn main() {}

如果没有更好的SQL方法来执行此操作,我也不会感到惊讶。例如,PostgreSQL 似乎具有WHERE col LIKE ANY( subselect )WHERE col LIKE ALL( subselect )形式。

于 2018-02-09T19:20:29.197 回答