使用动态数量的 .and() 创建 Diesel.rs 查询 [英] Creating Diesel.rs queries with a dynamic number of .and()'s

查看:41
本文介绍了使用动态数量的 .and() 创建 Diesel.rs 查询的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在玩 Diesel 时,我在编写一个函数时遇到了困难,该函数以 String 的向量作为输入并执行以下操作:

While playing with Diesel, I got stuck writing a function which takes an vector of Strings as input and does the following:

  1. 将所有String组合成一个大查询
  2. Connection
  3. 上运行查询
  4. 处理结果
  5. 返回一个 Vec
  1. Combine all Strings to a large query
  2. run the query on the Connection
  3. process the result
  4. return a Vec

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

If I construct the query in one step, it works just fine:

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");
}

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

If I try to generate the query in more steps (needed in order to work with the variable length of the input vector) I can't get it to compile:

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.

I don't understand why Rust expects a Like and not an And. The function an the line line marked <1> returns an And and therefore an And is stored in inner.

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

What am I missing? Why does the first bit of code compile and the second won't? What is the right way to generate this kind of query?

推荐答案

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

The first thing you need to do is look at the complete error message:

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>>>, _>`

它很长,但那是因为它是完全合格的.让我们把最后一部分缩短一点:

It's long, but that's because it's fully qualified. Lets shorten the last part a bit:

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

如果您查看了和<的文档/code>,你会看到每次对 and 的调用都会消耗接收者并返回一个全新的类型——And:

If you review the documentation for and, you'll see that every call to and consumes the receiver and returns a brand new type — And:

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

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

This is the core of Diesel's ability to generate strongly-typed SQL expressions with no run-time overhead. All of the work is delegated to the type system. In fact, the creator of Diesel has an entire talk where he shows how far Diesel pushes the type system and what benefits it has.

回到您的问题,不可能将 And<_, _> 存储在与 And<And<_, _>, _> 因为它们有不同的尺寸,实际上是不同的类型.从根本上说,这与尝试将整数存储在布尔值中是一样的.

Turning back to your question, it's impossible to store an And<_, _> in the same location as an And<And<_, _>, _> because they will have different sizes and are in fact different types. At the root, this is the same as trying to store an integer in a boolean.

事实上,没有方法可以知道您需要什么具体类型,因为您甚至不知道您将拥有多少个条件 — 这取决于向量.

In fact, there's no way to know what concrete type you need because you don't even know how many conditions you will have — it depends on the size of the vector.

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

In this case, we have to give up static dispatch and move to dynamic dispatch via a trait object. Diesel has a specific trait for this case (which also has good examples): BoxableExpression.

剩下的部分是将您的作者转换为 like 表达式并将它们组合起来.但是,当 authors 为空时,我们需要一个基本情况.我们为此构建了一个非常正确的陈述(author = author).

The remaining piece is to convert your authors to like expressions and combine them. We need a base case, however, for when authors is empty. We construct a trivially true statement (author = author) for that.

#[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 ) 形式.


I also wouldn't be surprised if there wasn't a better SQL way of doing this. It appears that PostgreSQL has the WHERE col LIKE ANY( subselect ) and WHERE col LIKE ALL( subselect ) forms, for example.

这篇关于使用动态数量的 .and() 创建 Diesel.rs 查询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆