Search code examples
rustrust-diesel

Understanding trait bound error in Diesel


I want to write a function that will insert a type into a database where the database connection parameter is generic, so that it can work on multiple backends.

I came up with the following function to insert an object using a generic connection:

pub fn create_label<C>(connection: &C, label: &model::Label)
where
    C: Connection,
    C::Backend: diesel::backend::Backend,
    C::Backend: diesel::backend::SupportsDefaultKeyword,
{
    diesel::insert(&label)
        .into(schema::label::table)
        .execute(connection);
}

If I don't include the SupportsDefaultKeyword constraint, the function will not compile. When calling it with a SqliteConnection as the connection parameter, I get the following error:

database::create_label(&db_conn, &label); ^^^^^^^^^^^^^^^^^^^^^^ the trait 'diesel::backend::SupportsDefaultKeyword' is not implemented for 'diesel::sqlite::Sqlite'

This would imply that inserting data with a SqliteConnection does not work. That's obviously not the case, and furthermore changing create_label such that it takes a SqliteConnection directly works just fine.

pub fn create_label(connection: &SqliteConnection, label: &model::Label) {
    diesel::insert(&label)
        .into(schema::label::table)
        .execute(connection);
}

Why is it that the generic function requires the SupportsDefaultKeyword constraint and the function taking SqliteConnection does not?

Here is a minimal example illustrating the problem. As per the comments, line 60 of main.rs will not compile with the error from above, whereas line 61 does compile:

#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_codegen;

mod schema {
    table! {
        labels {
            id -> Integer,
            name -> VarChar,
        }
    }
}

mod model {
    use schema::labels;

    #[derive(Debug, Identifiable, Insertable)]
    #[table_name = "labels"]
    pub struct Label {
        pub id: i32,
        pub name: String,
    }
}

use diesel::ExecuteDsl;
use diesel::Connection;
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;

pub fn create_label<C>(connection: &C, label: &model::Label)
where
    C: Connection,
    C::Backend: diesel::backend::Backend,
    C::Backend: diesel::backend::SupportsDefaultKeyword,
{
    diesel::insert(label)
        .into(schema::labels::table)
        .execute(connection)
        .expect("nope");
}

pub fn create_label_sqlite(connection: &SqliteConnection, label: &model::Label) {
    diesel::insert(label)
        .into(schema::labels::table)
        .execute(connection)
        .expect("nope");
}

pub fn establish_connection() -> SqliteConnection {
    let url = "test.db";
    SqliteConnection::establish(&url).expect(&format!("Error connecting to {}", url))
}

fn main() {
    let label = model::Label {
        id: 1,
        name: String::from("test"),
    };
    let conn = establish_connection();

    create_label(&conn, &label); /* Does not compile */
    create_label_sqlite(&conn, &label); /*Compiles */
}
[dependencies]
diesel = { version = "0.16.0", features = ["sqlite"] }
diesel_codegen = "0.16.0"

Solution

  • The Diesel function execute has multiple concrete implementations. The two that are relevant here are:

    impl<'a, T, U, Op, Ret, Conn, DB> ExecuteDsl<Conn, DB> for BatchInsertStatement<T, &'a [U], Op, Ret> 
    where
        Conn: Connection<Backend = DB>,
        DB: Backend + SupportsDefaultKeyword,
        InsertStatement<T, &'a [U], Op, Ret>: ExecuteDsl<Conn>, 
    
    impl<'a, T, U, Op, Ret> ExecuteDsl<SqliteConnection> for BatchInsertStatement<T, &'a [U], Op, Ret> 
    where
        InsertStatement<T, &'a U, Op, Ret>: ExecuteDsl<SqliteConnection>,
        T: Copy,
        Op: Copy,
        Ret: Copy, 
    

    As you can see from these two, the implementation for SQLite is special-cased. I don't know enough about the details of Diesel to know why, but I'd guess that SQLite is missing the default keyword.

    You can instead reformulate the requirements for any connection that works with that particular statement:

    use diesel::query_builder::insert_statement::InsertStatement;
    
    pub fn create_label<C>(connection: &C, label: &model::Label)
    where
        C: Connection,
        for<'a> InsertStatement<schema::labels::table, &'a model::Label>: ExecuteDsl<C>,
    {
        diesel::insert(label)
            .into(schema::labels::table)
            .execute(connection)
            .expect("nope");
    }