0

I'm quite new to rust/diesel. Trying to implement a database module with default trait API (avoid duplicate code work), by crate diesel and r2d2.

Here's the model.rs:

use chrono::NaiveDate;
use diesel;
use diesel::Queryable;

#[derive(Queryable)]
pub struct NetWorthModel {
    pub fund_code: String,
    pub date: NaiveDate,
    pub create_time: i64,
    pub update_time: i64,
    pub payload: String,
}

Here's the database.rs:

use diesel::connection::Connection;
use diesel::mysql::MysqlConnection;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::sqlite::SqliteConnection;
use once_cell::sync::Lazy;
use r2d2::PooledConnection;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use chrono::prelude::NaiveDate;

static MYSQL_POOL_MAP: Lazy<Mutex<HashMap<String, Pool<ConnectionManager<MysqlConnection>>>>> =
    Lazy::new(|| Mutex::new(HashMap::new()));

static SQLITE_POOL_MAP: Lazy<Mutex<HashMap<String, Pool<ConnectionManager<SqliteConnection>>>>> =
    Lazy::new(|| Mutex::new(HashMap::new()));

pub struct MysqlDatabase {
    pool: Pool<ConnectionManager<MysqlConnection>>,
}

pub struct SqliteDatabase {
    pool: Pool<ConnectionManager<SqliteConnection>>,
}

pub trait Database<Conn> {
  fn connection(&self) -> Conn; // MysqlDatabase and SqliteDatabase will ipmlement this api

  fn paged_query(&self, fund_code: &str, start_date: NaiveDate, end_date: NaiveDate) -> Vec<NetWorthModel> {
    use super::schema::tb_net_worth::dsl;
    dsl::tb_net_worth
         .filter(dsl::fund_code.eq(fund_code))
         .filter(dsl::date.ge(start_date))
         .filter(dsl::date.lt(end_date))
         .load::<NetWorthModel>(&self.connection()) // here we use the abstract API
         .unwrap()
  }
}

impl Database<PooledConnection<ConnectionManager<MysqlConnection>>> for MysqlDatabase {
    fn connection(&self) -> PooledConnection<ConnectionManager<MysqlConnection>> {
        self.pool.get().unwrap()
    }
}

impl Database<PooledConnection<ConnectionManager<SqliteConnection>>> for SqliteDatabase {
    fn connection(&self) -> PooledConnection<ConnectionManager<SqliteConnection>> {
        self.pool.get().unwrap()
    }
}

The Database trait will implement all the APIs, which should work for both mysql and sqlite. But here's the compile error:

error[E0277]: the trait bound `Conn: Connection` is not satisfied
   --> src/quant/common/persistence/database.rs:221:45
    |
221 |                 .load::<NetWorthQueryModel>(&self.connection())
    |                  ----                       ^^^^^^^^^^^^^^^^^^ the trait `Connection` is not implemented for `Conn`
    |                  |
    |                  required by a bound introduced by this call
    |
    = note: required because of the requirements on the impl of `LoadQuery<Conn, NetWorthQueryModel>` for `diesel::query_builder::SelectStatement<table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<And<And<diesel::expression::operators::Eq<columns::fund_code, diesel::expression::bound::Bound<diesel::sql_types::Text, &str>>, GtEq<columns::date, diesel::expression::bound::Bound<diesel::sql_types::Date, NaiveDate>>>, Lt<columns::date, diesel::expression::bound::Bound<diesel::sql_types::Date, NaiveDate>>>>, diesel::query_builder::order_clause::OrderClause<Asc<columns::date>>, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>, diesel::query_builder::offset_clause::OffsetClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`
help: consider restricting type parameter `Conn`
    |
147 | pub trait Database<Conn: diesel::Connection> {
    |                        ++++++++++++++++++++

Follow the compiler's suggestion, I add pub trait Database<Conn: diesel::Connection>, and compiler gives me another error:

error[E0277]: the trait bound `NaiveDate: FromSql<diesel::sql_types::Date, <Conn as Connection>::Backend>` is not satisfied
   --> src/quant/common/persistence/database.rs:221:18
    |
221 |                 .load::<NetWorthQueryModel>(&self.connection())
    |                  ^^^^ the trait `FromSql<diesel::sql_types::Date, <Conn as Connection>::Backend>` is not implemented for `NaiveDate`
    |
    = note: required because of the requirements on the impl of `diesel::Queryable<diesel::sql_types::Date, <Conn as Connection>::Backend>` for `NaiveDate`
    = note: 2 redundant requirements hidden
    = note: required because of the requirements on the impl of `diesel::Queryable<(diesel::sql_types::Text, diesel::sql_types::Date, diesel::sql_types::BigInt, diesel::sql_types::BigInt, diesel::sql_types::Text), <Conn as Connection>::Backend>` for `NetWorthQueryModel`
    = note: required because of the requirements on the impl of `LoadQuery<Conn, NetWorthQueryModel>` for `diesel::query_builder::SelectStatement<table, diesel::query_builder::select_clause::DefaultSelectClause, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause<And<And<diesel::expression::operators::Eq<columns::fund_code, diesel::expression::bound::Bound<diesel::sql_types::Text, &str>>, GtEq<columns::date, diesel::expression::bound::Bound<diesel::sql_types::Date, NaiveDate>>>, Lt<columns::date, diesel::expression::bound::Bound<diesel::sql_types::Date, NaiveDate>>>>, diesel::query_builder::order_clause::OrderClause<Asc<columns::date>>, diesel::query_builder::limit_clause::LimitClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>, diesel::query_builder::offset_clause::OffsetClause<diesel::expression::bound::Bound<diesel::sql_types::BigInt, i64>>>`
help: consider introducing a `where` bound, but there might be an alternative better way to express this requirement
    |
147 | pub trait Database<Conn: Connection> where NaiveDate: FromSql<diesel::sql_types::Date, <Conn as Connection>::Backend> {
    |                                      ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

It seems rustc cannot detect the Backend of Connection. But I have no idea how should I fix this.

Please help me with this issue.

linrongbin
  • 2,967
  • 6
  • 31
  • 59

1 Answers1

0

The right way to fix this is to follow the compiler error messages and introduce the necessary trait bounds. It will not work without them. As a general advice: Try to use one of the last bounds as given by required because of as this avoids many other bounds.

Generally speaking: If you are new to diesel and to rust it's probably not advisable to try to write generic code that tries to abstract over diesel. That requires advance knowledge about rusts trait system + about diesels API.

weiznich
  • 2,910
  • 9
  • 16
  • In this doc: https://docs.rs/diesel/1.2.2/diesel/sql_types/struct.Date.html, says diesel already implment FromSql and ToSql trait for chrono::NaiveDate, I have already enabled feature "chrono" for diesel. It should be working, doesn't it? – linrongbin Dec 21 '21 at 16:03
  • No that's not true. As you can see [here](https://docs.rs/diesel/1.4.8/diesel/deserialize/trait.FromSql.html#impl-FromSql%3CTimestamp%2C%20Pg%3E-for-NaiveDateTime) the impls are written for concrete backends (== no generic parameter `DB`). Your code requests that this impl exists for any type, which is just not fulfilled. – weiznich Dec 21 '21 at 16:20
  • ok, I will try to use macro to generate the same api for two different databases. – linrongbin Dec 21 '21 at 21:41