Создание запросов Diesel.rs с динамическим числом.and()

Играя с Diesel, я застрял, написав функцию, которая принимает вектор Strings в качестве входных данных и выполняет следующие действия:

  1. Объединить все Stringс большим запросом
  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`

Я не понимаю, почему Руст ожидает Like и не And, Функция и линия помечены <1> возвращает And и, следовательно, And хранится в inner,

Что мне не хватает? Почему первый бит кода компилируется, а второй нет? Как правильно создать такой запрос?

1 ответ

Решение

Первое, что вам нужно сделать, это посмотреть полное сообщение об ошибке:

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 продвигает систему типов и какие преимущества она имеет.

Возвращаясь к вашему вопросу, невозможно сохранить And<_, _> в том же месте, что и And<And<_, _>, _> потому что они будут иметь разные размеры и на самом деле разные типы. В корне это то же самое, что попытаться сохранить целое число в логическом значении.

На самом деле, нет никакого способа узнать, какой конкретный тип вам нужен, потому что вы даже не знаете, сколько у вас будет условий - это зависит от размера вектора.

В этом случае мы должны отказаться от статической полиморфной диспетчеризации и перейти к динамической диспетчеризации через объект признака. Дизель имеет особую черту для этого случая (которая также имеет хорошие примеры): 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 ) формы, например.

Другие вопросы по тегам