英文:
Diesel generic implementation of insert, update and delete won't work with tables
问题
以下是您提供的内容的中文翻译:
我一直在尝试组装一个通用的数据库层实现,使用 Diesel 编写,为了准备从我们当前的数据库迁移。
然而,尽管获得了一个工作的 trait,但它拒绝使用使用 table!
宏创建的表。
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);
}
}
Table
diesel::table! {
testing (code) {
code -> Text,
user_id -> Integer,
}
}
Usage
//! 与数据库相关的模块。
pub use diesel::Connection;
use super::DataStore;
/// 与数据库的连接。
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 {
/// 用于注册的代码
pub code: String,
/// 用户 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<()> {
// 错误发生在这里
T::insert::<testing, _>(*self.db.get(), row.clone());
Ok(())
}
}
参考
我的实现是基于此答案的:https://stackoverflow.com/questions/58589018/writing-diesel-crud-operations-for-generic-types
英文:
I've been trying to assemble a generic implementation of our database layer written using diesel in preparation for a migration from our current db.
However despite getting a working trait, it refuses to work with tables created using the table!
macro.
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);
}
}
Table
diesel::table! {
testing (code) {
code -> Text,
user_id -> Integer,
}
}
Usage
//! Module for database related stuff.
pub use diesel::Connection;
use super::DataStore;
/// 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(())
}
}
Reference
My implementation was based off the answer available here: https://stackoverflow.com/questions/58589018/writing-diesel-crud-operations-for-generic-types
答案1
得分: 1
调用具有通用参数的方法时,编译器必须验证所有通用边界是否有效。在您的情况下,您是在另一个通用上下文中调用该方法,这意味着有一些边界无法在不对外部上下文的通用边界施加限制的情况下验证。这会影响所有依赖于您的 Db
或 T
通用类型的边界。
您可以通过将来自 DataStore::insert
的相关边界复制到 DbWrapper
的实现中,并在其中用正确的特定类型替换一些部分来解决这个问题:
impl<Db: R2D2Connection + 'static, T: DataStore<Conn = Db>> DbWrapper<Db, T>
where
<testing::table as QuerySource>::FromClause: QueryFragment<<Db as Connection>::Backend>,
InsertStatement<testing::table, <TestingRow as diesel::Insertable<testing::table>>::Values>: ExecuteDsl<Db>,
{
fn get(&self) -> &mut Db {
todo!()
}
pub fn insert_row(&self, row: &TestingRow) -> QueryResult<()> {
T::insert::<testing::table, _>(&mut *self.get(), row.clone());
Ok(())
}
}
言归正传:通常不建议以这种方式编写通用的 diesel 代码,因为这很快会导致复杂的 trait 边界。
英文:
To call a method with generic arguments the compiler must verify that all generic bounds are valid. In your case you are calling that method inside of another generic context, which means that there are a few bounds that cannot be verified without placing restrictions on the generic bounds in the outer context. That affects all the bounds that depend on your Db
or T
generic type.
You can fix that by duplicating the relevant bounds from DataStore::insert
on the DbWrapper
impl and replacing some parts with the correct specific types there:
impl<Db: R2D2Connection + 'static, T: DataStore<Conn = Db>> DbWrapper<Db, T>
where
<testing::table as QuerySource>::FromClause: QueryFragment<<Db as Connection>::Backend>,
InsertStatement<testing::table, <TestingRow as diesel::Insertable<testing::table>>::Values>: ExecuteDsl<Db>,
{
fn get(&self) -> &mut Db {
todo!()
}
pub fn insert_row(&self, row: &TestingRow) -> QueryResult<()> {
T::insert::<testing::table, _>(&mut *self.get(), row.clone());
Ok(())
}
}
That written: It's generally not advised to write generic diesel code in that way as it leads to complex trait bounds really soon.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论