使用动态数量的.and()创建Diesel.rs查询

时间:2018-02-08 23:10:03

标签: rust rust-diesel

在与Diesel一起玩时,我遇到了写一个函数的问题,该函数将String s的向量作为输入并执行以下操作:

  1. 将所有String合并为大型查询
  2. Connection
  3. 上运行查询
  4. 处理结果
  5. 返回Vec
  6. 如果我一步构建查询,它就可以正常工作:

    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要求Like而不是And。标记为<1>的直线的函数返回And,因此And中存储inner

    我错过了什么?为什么第一部分代码编译而第二部分不编译?生成此类查询的正确方法是什么?

1 个答案:

答案 0 :(得分:4)

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

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的创造者有an entire talk where he shows how far Diesel pushes the type system and what benefits it has

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

事实上,有没有的方式来了解您需要什么样的具体类型,因为您甚至不知道您将拥有多少条件 - 这取决于矢量的大小。

在这种情况下,我们必须放弃静态多态分派并通过特征对象转移到动态分派。 Diesel对此案例具有特定的特征(也有很好的例子):BoxableExpression

剩下的部分是将作者转换为like表达式并将它们组合起来。但是,当authors为空时,我们需要一个基本案例。我们为此构建了一个简单的真实陈述(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 )形式。