Diesel通用增删改实现无法适配table!宏生成的表
Diesel通用DataStore Trait适配table!宏生成表的问题解决
问题背景
做数据库迁移时,基于Diesel框架实现了通用数据层的CRUD操作Trait,但该Trait无法与table!宏生成的数据库表配合使用,报错出现在insert_row方法调用T::insert::<testing, _>的位置。
原Trait定义
use diesel::{ query_builder::{InsertStatement, UpdateStatement, IntoUpdateTarget, DeleteStatement, AsQuery, QueryFragment}, query_dsl::{methods::ExecuteDsl}, r2d2::R2D2Connection, Table, RunQueryDsl, associations::HasTable, AsChangeset, Connection, }; pub trait DataStore { type Conn: R2D2Connection + 'static; fn insert<T, M>(conn: &mut Self::Conn, records: M) where T: Table + HasTable<Table = T>, M: diesel::Insertable<T>, T::FromClause: QueryFragment<<Self::Conn as Connection>::Backend>, InsertStatement<T, M::Values>: ExecuteDsl<Self::Conn>, { diesel::insert_into(T::table()).values(records).execute(conn); } fn update<T, U, V>(conn: &mut Self::Conn, table: T, values: V) where T: IntoUpdateTarget + HasTable<Table = T> + Table, V: diesel::query_builder::AsChangeset<Target = T>, UpdateStatement<T, <T as IntoUpdateTarget>::WhereClause, <V as AsChangeset>::Changeset>: AsQuery + ExecuteDsl<Self::Conn>, { diesel::update(T::table()).set(values).execute(conn); } fn delete<T, U, V>(conn: &mut Self::Conn, table: T, values: V) where T: IntoUpdateTarget + HasTable<Table = T> + Table, V: diesel::query_builder::AsChangeset<Target = T>, DeleteStatement<T, <T as IntoUpdateTarget>::WhereClause>: AsQuery + ExecuteDsl<Self::Conn> { diesel::delete(T::table()).execute(conn); } }
原表定义
diesel::table! { testing (code) { code -> Text, user_id -> Integer, } }
原使用代码
//! Module for database related stuff. pub use diesel::Connection; use super::DataStore; use std::marker::PhantomData; /// Connection to the DB. pub struct PgConn(diesel::pg::PgConnection); impl DataStore for diesel::pg::Pg { type Conn = diesel::pg::PgConnection; } impl std::fmt::Debug for PgConn { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "PgConn(..)") } } #[derive(Clone, Debug, Default, Eq, Insertable, PartialEq, Queryable, Selectable)] #[diesel(table_name = testing)] pub struct TestingRow { /// Code used to sign up pub code: String, /// user_id pub user_id: i32, } pub struct DbWrapper<Db: R2D2Connection + 'static, T: DataStore<Conn = Db> > { db: Db, phantom: PhantomData<T> } impl<Db: R2D2Connection + 'static, T: DataStore<Conn = Db>> DbWrapper<Db, T> { pub fn insert_row(&self, row: &TestingRow) -> Result<()> { // Error occurs here T::insert::<testing, _>(*self.db.get(), row.clone()); Ok(()) } }
解决步骤
1. 修正DataStore的实现目标
原代码把DataStore实现给了后端类型diesel::pg::Pg,但应该针对连接类型实现,比如直接给PgConnection:
// 替换原impl DataStore for diesel::pg::Pg的代码 impl DataStore for diesel::pg::PgConnection { type Conn = diesel::pg::PgConnection; }
2. 简化DbWrapper的泛型设计
原DbWrapper的泛型参数冗余,直接用连接类型作为DataStore的实现即可,去掉多余的T和PhantomData:
pub struct DbWrapper<Db: DataStore + R2D2Connection + 'static> { db: Db, } impl<Db: DataStore + R2D2Connection + 'static> DbWrapper<Db> { // 改为&mut self以获取可变连接引用 pub fn insert_row(&mut self, row: &TestingRow) -> Result<usize, diesel::result::Error> { Db::insert::<testing, _>(&mut self.db, row.clone()) } }
3. 让Trait方法返回错误结果
原Trait方法忽略了execute的返回值,Diesel数据库操作都会返回Result,需修改Trait方法返回错误信息,让调用者处理:
pub trait DataStore { type Conn: R2D2Connection + 'static; fn insert<T, M>(conn: &mut Self::Conn, records: M) -> Result<usize, diesel::result::Error> where T: Table + HasTable<Table = T>, M: diesel::Insertable<T>, T::FromClause: QueryFragment<<Self::Conn as Connection>::Backend>, InsertStatement<T, M::Values>: ExecuteDsl<Self::Conn>, { diesel::insert_into(T::table()).values(records).execute(conn) } fn update<T, V>(conn: &mut Self::Conn, values: V) -> Result<usize, diesel::result::Error> where T: IntoUpdateTarget + HasTable<Table = T> + Table, V: diesel::query_builder::AsChangeset<Target = T>, UpdateStatement<T, <T as IntoUpdateTarget>::WhereClause, <V as AsChangeset>::Changeset>: AsQuery + ExecuteDsl<Self::Conn>, { diesel::update(T::table()).set(values).execute(conn) } fn delete<T>(conn: &mut Self::Conn) -> Result<usize, diesel::result::Error> where T: IntoUpdateTarget + HasTable<Table = T> + Table, DeleteStatement<T, <T as IntoUpdateTarget>::WhereClause>: AsQuery + ExecuteDsl<Self::Conn> { diesel::delete(T::table()).execute(conn) } }
注:update和delete方法中的冗余泛型参数
U已移除,简化类型约束。
4. 修正连接获取逻辑
原代码中*self.db.get()是错误用法,若Db是R2D2的PooledConnection,直接传递&mut self.db即可;若为连接池,需先获取连接再操作:
// 连接池类型的示例实现 impl DbWrapper<r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::pg::PgConnection>>> { pub fn insert_row(&self, row: &TestingRow) -> Result<usize, diesel::result::Error> { let mut conn = self.db.get()?; diesel::pg::PgConnection::insert::<testing, _>(&mut conn, row.clone()) } }
最终验证
调整后,insert_row方法可正确调用通用insert方法,与table!宏生成的testing表兼容,同时能正确处理数据库操作的错误。
内容的提问来源于stack exchange,提问作者CertainlyNotAdrian
相关产品推荐
相关产品推荐

