Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic usage of Diesel's find or filter to perform deletions

I'm trying to use generic Diesel functions to shrink repetitive tasks like deleting a row based on the primary key.

I got generic insertion of rows working relatively quick, but deletion queries seem to be quite hard. I tried solving it both by using find() and filter(). I also consulted similar topics 1 and 2 without success.

Using find

use diesel::prelude::*;
use diesel::query_dsl::methods::FindDsl;
use std::error::Error;

pub struct DB {
    conn: SqliteConnection,
}

impl DB {
    pub fn remove_row<'a, T>(&self, table: T, pk: &'a str) -> Result<(), Box<Error>>
    where
        T: FindDsl<&'a str>,
        <T as FindDsl<&'a str>>::Output: diesel::Identifiable,
        <T as FindDsl<&'a str>>::Output: diesel::associations::HasTable,
    {
        diesel::delete(table.find(pk)).execute(&self.conn)?;
        Ok(())
    }
}

This leads to the following error which I cannot interpret at all:

error[E0275]: overflow evaluating the requirement `_: std::marker::Sized`
   --> src/db/mod.rs:103:3
    |
103 |         diesel::delete (table.find (pk)) .execute (&self.conn) ?;
    |         ^^^^^^^^^^^^^^
    |
    = help: consider adding a `#![recursion_limit="128"]` attribute to your crate
    = note: required because of the requirements on the impl of `diesel::query_dsl::filter_dsl::FilterDsl<_>` for `<<<T as diesel::query_dsl::filter_dsl::FindDsl<&'a str>>::Output as diesel::associations::HasTable>::Table as diesel::query_builder::AsQuery>::Query`
    = note: required because of the requirements on the impl of `diesel::query_builder::IntoUpdateTarget` for `<T as diesel::query_dsl::filter_dsl::FindDsl<&'a str>>::Output`
    = note: required by `diesel::delete`

Using filter()

use diesel::prelude::*;
use diesel::query_dsl::methods::FilterDsl;
use std::error::Error;

pub struct DB {
    conn: SqliteConnection,
}

impl DB {
    pub fn remove_row<T>(&self, table: T, pk: &str) -> Result<(), Box<Error>>
    where
        T: FilterDsl<bool>,
        <T as FilterDsl<bool>>::Output: diesel::Identifiable,
        <T as FilterDsl<bool>>::Output: diesel::associations::HasTable,
    {
        diesel::delete(table.filter(id.eq(pk))).execute(&self.conn)?;
        Ok(())
    }
}

In addition to the previous error, this has an error message about id not being known in the data structure. I can think about one missing trait, which guarantees the existence of that row, but I haven’t found anything about such behavior.

error[E0425]: cannot find value `id` in this scope
   --> src/db/mod.rs:117:33
    |
117 |         diesel::delete (table.filter (id.eq (pk))) .execute (&self.conn) ?;
    |                                       ^^ not found in this scope
help: possible candidates are found in other modules, you can import them into scope
    |
4   | use crate::db::schema::events::columns::id;
    |
4   | use crate::db::schema::ignored_events::columns::id;
    |
4   | use crate::db::schema::locations::columns::id;
    |
4   | use std::process::id;


error[E0275]: overflow evaluating the requirement `_: std::marker::Sized`
   --> src/db/mod.rs:117:3
    |
117 |         diesel::delete (table.filter (id.eq (pk))) .execute (&self.conn) ?;
    |         ^^^^^^^^^^^^^^
    |
    = help: consider adding a `#![recursion_limit="128"]` attribute to your crate
    = note: required because of the requirements on the impl of `diesel::query_dsl::filter_dsl::FilterDsl<_>` for `<<<T as diesel::query_dsl::filter_dsl::FilterDsl<bool>>::Output as diesel::associations::HasTable>::Table as diesel::query_builder::AsQuery>::Query`
    = note: required because of the requirements on the impl of `diesel::query_builder::IntoUpdateTarget` for `<T as diesel::query_dsl::filter_dsl::FilterDsl<bool>>::Output`
    = note: required by `diesel::delete`
like image 334
Frank Brütting Avatar asked Mar 05 '23 08:03

Frank Brütting


1 Answers

Generics aren't easy. Generics in a highly generic system like Diesel are even harder.

I prefer to break the steps down into very small parts and avoid chaining as much as possible. With that in play, you will basically need to add trait bounds for each step. One nice thing to do is to use / create type aliases for very complicated trait bounds. Diesel provides a number of these and you can make your own for your special uses.

While I looked at the error messages generated, I mostly looked at the type bounds described by the functions / methods being called.

Point-by-point:

  1. .find comes from FindDsl.
  2. delete requires IntoUpdateTarget.
  3. The resulting type of calling delete is a DeleteStatement, parameterized with T::Table and T::WhereClause. This is the custom type alias DeleteFindStatement.
  4. .execute comes from ExecuteDsl.
use diesel::{
    associations::HasTable,
    helper_types::Find,
    query_builder::{DeleteStatement, IntoUpdateTarget},
    query_dsl::methods::ExecuteDsl,
};

type DeleteFindStatement<F> =
    DeleteStatement<<F as HasTable>::Table, <F as IntoUpdateTarget>::WhereClause>;

impl DB {
    pub fn remove_row<Tbl, Pk>(&self, table: Tbl, pk: Pk) -> Result<(), Box<Error>>
    where
        Tbl: FindDsl<Pk>,
        Find<Tbl, Pk>: IntoUpdateTarget,
        DeleteFindStatement<Find<Tbl, Pk>>: ExecuteDsl<SqliteConnection>,
    {
        let find = table.find(pk);
        let delete = diesel::delete(find);
        delete.execute(&self.conn)?;
        Ok(())
    }
}

You will need to try it on your own for the filter-based version, as you didn't provide enough code to tell what id should even be; as shown in your error message.

See also:

  • How to define a function-local type alias of the function's type parameters (or their associated types)?

is there a benefit in making the primary key generic, even if it is &str in all cases?

For me, it's easier to use a generic type than it is to insert a bunch of generic lifetime parameters.

like image 78
Shepmaster Avatar answered Apr 26 '23 15:04

Shepmaster