You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 15:32:33