< 返回版块

xbitlabs 发表于 2022-11-19 14:38

Tags:sqlx

报错信息如下: | let exec = SqlCommandExecutor::new(tran_manager.transaction()); | ---- move occurs because exec has type SqlCommandExecutor<'_, &mut Transaction<'_, MySql>>, which does not implement the Copy trait ... 63 | let result = exec.execute("").await?; | ----------- exec moved due to this method call 64 | let result1 = exec.execute("").await?; | ^^^^ value used here after move | note: this function takes ownership of the receiver self, which moves exec

我把函数的self 改为&self可以解决move的问题,但是调用sqlx的execute又不行了,有什么两全其美的办法吗?

源码连接地址:https://files.cnblogs.com/files/xlabs/sqlx_tran.rar

评论区

写评论
作者 xbitlabs 2022-11-23 10:29

好嘞,我瞧瞧,谢谢

--
👇
苦瓜小仔: @Yandros 大佬对为什么需要 drop 的解答 NLL vs drop glue

苦瓜小仔 2022-11-22 20:53

@Yandros 大佬对为什么需要 drop 的解答 NLL vs drop glue

作者 xbitlabs 2022-11-22 17:55

谢谢,非常的nice的解答

--
👇
苦瓜小仔: > 不手动drop,auth_provider还是一直独占着SqlCommandExecutor

是的 playground

苦瓜小仔 2022-11-22 17:02

不手动drop,auth_provider还是一直独占着SqlCommandExecutor

是的 playground

作者 xbitlabs 2022-11-22 15:50

改成这样后,必须要drop(auth_provider)才行,是因为不手动drop,auth_provider还是一直独占着SqlCommandExecutor的原因吗?我试着注解drop(auth_provider),编译不过去

👇
xbitlabs: 搞定了,膜拜,哈哈

--
👇
苦瓜小仔: 你使用了 &'d mut SqlCommandExecutor<'c, 'd>,这是典型的生命周期标注错误 —— 过度约束

改成 &'r mut SqlCommandExecutor<'c, 'd>,比如:

  pub type AuthenticationProviderFn = Box<
      dyn for<'a, 'b> Fn(
              &'a Arc<Extensions>,
              &'b Arc<Extensions>,
          ) -> Box<
              dyn for<'r, 'c, 'd> Fn(
                      &'r mut SqlCommandExecutor<'c, 'd>,
                  )
                      -> Box<dyn AuthenticationProvider + Send + Sync + 'r>
                  + Send
                  + Sync,
          > + Send
          + Sync,
  >;

代码在这

作者 xbitlabs 2022-11-22 14:49

搞定了,膜拜,哈哈

--
👇
苦瓜小仔: 你使用了 &'d mut SqlCommandExecutor<'c, 'd>,这是典型的生命周期标注错误 —— 过度约束

改成 &'r mut SqlCommandExecutor<'c, 'd>,比如:

  pub type AuthenticationProviderFn = Box<
      dyn for<'a, 'b> Fn(
              &'a Arc<Extensions>,
              &'b Arc<Extensions>,
          ) -> Box<
              dyn for<'r, 'c, 'd> Fn(
                      &'r mut SqlCommandExecutor<'c, 'd>,
                  )
                      -> Box<dyn AuthenticationProvider + Send + Sync + 'r>
                  + Send
                  + Sync,
          > + Send
          + Sync,
  >;

代码在这

苦瓜小仔 2022-11-22 14:30

你使用了 &'d mut SqlCommandExecutor<'c, 'd>,这是典型的生命周期标注错误 —— 过度约束

改成 &'r mut SqlCommandExecutor<'c, 'd>,比如:

  pub type AuthenticationProviderFn = Box<
      dyn for<'a, 'b> Fn(
              &'a Arc<Extensions>,
              &'b Arc<Extensions>,
          ) -> Box<
              dyn for<'r, 'c, 'd> Fn(
                      &'r mut SqlCommandExecutor<'c, 'd>,
                  )
                      -> Box<dyn AuthenticationProvider + Send + Sync + 'r>
                  + Send
                  + Sync,
          > + Send
          + Sync,
  >;

代码在这

作者 xbitlabs 2022-11-22 11:47

我main入口方法试了下,可以多次可变引用,但是后面的声明了,前面的就没法再使用了,像这样:

let mut s = User{
    id: 1,
    username: None,
    password: None,
    phone_number: None,
    is_phone_number_verified: None,
    wx_open_id: None,
    enable: 0,
    created_time: Default::default()
};

let a = &mut s;
test(a);
let b = &mut s;
test(b);

但是在AuthenticationProcessingFilter这个结构体的实现里这样用,就报错了,应该有些细节问题我没有搞定。

👇
苦瓜小仔: 代码发一下

作者 xbitlabs 2022-11-22 11:34

代码链接https://files.cnblogs.com/files/xlabs/sqlx_tran.rar

--
👇
苦瓜小仔: 代码发一下

苦瓜小仔 2022-11-22 09:10

代码发一下

作者 xbitlabs 2022-11-21 23:54

同时只能有一个可变引用,那一个请求的业务逻辑只能写在一个方法里了,如果这样,很多代码无法复用。

--
👇
xbitlabs: 大佬,现在还有个问题,就是一个http请求里,同时实例化两个struct,这个两个struct全都依赖同一内存位置的 &'b mut SqlCommandExecutor<'a,'b>,这时候就编译报错了:cannot borrow *sql_command_executor as mutable more than once at a time,可变引用只能同时有一个,这样也就没有办法在一个http请求里复用Transaction了。有什么好的解决办法吗?

--
👇
xbitlabs: 完美

--
👇
苦瓜小仔: > 枚举也算是if else一个变种把,太多重复代码了,if else 中间的代码都是一样的

写一个宏 :)

macro_rules! sql_exe {
    ($self:tt, $method:ident, $query:expr) => {
        match *$self {
            Self::Transaction(ref mut exe) => exe.$method($query).await?,
            Self::Pool(exe) => exe.$method($query).await?,
        }
    }
}

impl<'db> SqlCommandExecutor<'db> {
    pub async fn execute(&mut self, query: &str) -> Result<MySqlQueryResult> {
        Ok(sql_exe!(self, execute, query))
    }
    pub async fn fetch_one(&mut self, query: &str) -> Result<MySqlRow> {
        Ok(sql_exe!(self, fetch_one, query))
    }
    pub async fn fetch_all(&mut self, query: &str) -> Result<Vec<MySqlRow>> {
        Ok(sql_exe!(self, fetch_all, query))
    }
}
作者 xbitlabs 2022-11-21 23:49

大佬,现在还有个问题,就是一个http请求里,同时实例化两个struct,这个两个struct全都依赖同一内存位置的 &'b mut SqlCommandExecutor<'a,'b>,这时候就编译报错了:cannot borrow *sql_command_executor as mutable more than once at a time,可变引用只能同时有一个,这样也就没有办法在一个http请求里复用Transaction了。有什么好的解决办法吗?

--
👇
xbitlabs: 完美

--
👇
苦瓜小仔: > 枚举也算是if else一个变种把,太多重复代码了,if else 中间的代码都是一样的

写一个宏 :)

macro_rules! sql_exe {
    ($self:tt, $method:ident, $query:expr) => {
        match *$self {
            Self::Transaction(ref mut exe) => exe.$method($query).await?,
            Self::Pool(exe) => exe.$method($query).await?,
        }
    }
}

impl<'db> SqlCommandExecutor<'db> {
    pub async fn execute(&mut self, query: &str) -> Result<MySqlQueryResult> {
        Ok(sql_exe!(self, execute, query))
    }
    pub async fn fetch_one(&mut self, query: &str) -> Result<MySqlRow> {
        Ok(sql_exe!(self, fetch_one, query))
    }
    pub async fn fetch_all(&mut self, query: &str) -> Result<Vec<MySqlRow>> {
        Ok(sql_exe!(self, fetch_all, query))
    }
}
作者 xbitlabs 2022-11-21 09:27

完美

--
👇
苦瓜小仔: > 枚举也算是if else一个变种把,太多重复代码了,if else 中间的代码都是一样的

写一个宏 :)

macro_rules! sql_exe {
    ($self:tt, $method:ident, $query:expr) => {
        match *$self {
            Self::Transaction(ref mut exe) => exe.$method($query).await?,
            Self::Pool(exe) => exe.$method($query).await?,
        }
    }
}

impl<'db> SqlCommandExecutor<'db> {
    pub async fn execute(&mut self, query: &str) -> Result<MySqlQueryResult> {
        Ok(sql_exe!(self, execute, query))
    }
    pub async fn fetch_one(&mut self, query: &str) -> Result<MySqlRow> {
        Ok(sql_exe!(self, fetch_one, query))
    }
    pub async fn fetch_all(&mut self, query: &str) -> Result<Vec<MySqlRow>> {
        Ok(sql_exe!(self, fetch_all, query))
    }
}
苦瓜小仔 2022-11-20 12:29

枚举也算是if else一个变种把,太多重复代码了,if else 中间的代码都是一样的

写一个宏 :)

macro_rules! sql_exe {
    ($self:tt, $method:ident, $query:expr) => {
        match *$self {
            Self::Transaction(ref mut exe) => exe.$method($query).await?,
            Self::Pool(exe) => exe.$method($query).await?,
        }
    }
}

impl<'db> SqlCommandExecutor<'db> {
    pub async fn execute(&mut self, query: &str) -> Result<MySqlQueryResult> {
        Ok(sql_exe!(self, execute, query))
    }
    pub async fn fetch_one(&mut self, query: &str) -> Result<MySqlRow> {
        Ok(sql_exe!(self, fetch_one, query))
    }
    pub async fn fetch_all(&mut self, query: &str) -> Result<Vec<MySqlRow>> {
        Ok(sql_exe!(self, fetch_all, query))
    }
}
作者 xbitlabs 2022-11-19 19:07

不过枚举还是比if else好,先暂时用枚举来搞吧,非常谢谢

--
👇
苦瓜小仔: 你有想过用枚举体吗?例子

impl<'a> TransactionManager<'a> {
    pub fn executor(&mut self, transaction: bool) -> SqlCommandExecutor {
        if transaction {
            SqlCommandExecutor::Transaction(&mut self.tran)
        } else {
            SqlCommandExecutor::Pool(self.pool)
        }
    }
}

pub enum SqlCommandExecutor<'db> {
    Transaction(&'db mut Transaction<'static, MySql>),
    Pool(&'db Pool<MySql>),
}

impl<'db> SqlCommandExecutor<'db> {
    pub async fn execute(&mut self, query: &str) -> Result<()> {
        match self {
            Self::Transaction(ref mut exe) => exe.execute(query).await?,
            Self::Pool(exe) => exe.execute(query).await?,
        };
        Ok(())
    }
}
作者 xbitlabs 2022-11-19 19:04

之前有用过if else来实现,枚举也算是if else一个变种把,但是太多重复代码了,if else 中间的代码都是一样的。

--
👇
苦瓜小仔: 你有想过用枚举体吗?例子

impl<'a> TransactionManager<'a> {
    pub fn executor(&mut self, transaction: bool) -> SqlCommandExecutor {
        if transaction {
            SqlCommandExecutor::Transaction(&mut self.tran)
        } else {
            SqlCommandExecutor::Pool(self.pool)
        }
    }
}

pub enum SqlCommandExecutor<'db> {
    Transaction(&'db mut Transaction<'static, MySql>),
    Pool(&'db Pool<MySql>),
}

impl<'db> SqlCommandExecutor<'db> {
    pub async fn execute(&mut self, query: &str) -> Result<()> {
        match self {
            Self::Transaction(ref mut exe) => exe.execute(query).await?,
            Self::Pool(exe) => exe.execute(query).await?,
        };
        Ok(())
    }
}
苦瓜小仔 2022-11-19 18:11

你有想过用枚举体吗?例子

impl<'a> TransactionManager<'a> {
    pub fn executor(&mut self, transaction: bool) -> SqlCommandExecutor {
        if transaction {
            SqlCommandExecutor::Transaction(&mut self.tran)
        } else {
            SqlCommandExecutor::Pool(self.pool)
        }
    }
}

pub enum SqlCommandExecutor<'db> {
    Transaction(&'db mut Transaction<'static, MySql>),
    Pool(&'db Pool<MySql>),
}

impl<'db> SqlCommandExecutor<'db> {
    pub async fn execute(&mut self, query: &str) -> Result<()> {
        match self {
            Self::Transaction(ref mut exe) => exe.execute(query).await?,
            Self::Pool(exe) => exe.execute(query).await?,
        };
        Ok(())
    }
}
作者 xbitlabs 2022-11-19 17:46

非常感谢,码了那么多字。我的想法是,在http接口的入口函数我实现一个#[transaction],实现自动事务管理,如果某些sql在某个http接口不需要事务,但是在别的接口需要事务,抽象出那个SqlCommandExecutor就有必要了,这样我就可以根据接口有没有#[transaction]自动生成SqlCommandExecutor,又因为刚好&Pool跟&mut Transaction都分别实现了Executor,如果这个接口需要事务,那我就往SqlCommandExecutor塞一个&mut Transaction,如果不需要事务那我就往SqlCommandExecutor塞一个&Pool,我这个想法,有没有更好的实现方式呢?

--
👇
苦瓜小仔: 不可能的。对于 impl<'c, 't> Executor<'t> for &'t mut Transaction<'c, MySql> 和所有 .execute 方法,如 Query 的 execute 方法被定义成

pub async fn execute<'e, 'c, E>(
    self,
    executor: E
) -> impl Future<Output = Result<<DB as Database>::QueryResult, Error>>
where
    'c: 'e,
    'q: 'e,
    A: 'e,
    E: Executor<'c, Database = DB>,

显然,这意味着执行 sqlx 库中任何 .execute 方法需要获取 E 的所有权,而 &mut 是唯一的,一旦调用这个方法,你不可能复用那个 &mut Transaction。这决定了每次调用 .execute 之前需要生成一个 &mut Transaction。而生成 &mut Transaction 的前提是你拥有 Transaction(或者拥有 &mut Transaction 来 reborrow)。而 reborrow 是 Rust 众多人体工程学设计中的具体规则,并不是任何语法和抽象。总而言之,你无法定义 SqlCommandExecutor 那种抽象。

pub struct SqlCommandExecutor<'a,E: Executor<'a>>
    where <<E as Executor<'a>>::Database as HasArguments<'a>>::Arguments: IntoArguments<'a, <E as Executor<'a>>::Database>,
{
    executor:E,
    _phantom_data: std::marker::PhantomData<&'a ()>,
}

impl <'a,E:  Executor<'a>> SqlCommandExecutor<'a,E> where <<E as Executor<'a>>::Database as HasArguments<'a>>::Arguments: IntoArguments<'a, <E as Executor<'a>>::Database>{
    pub async fn execute(
        self,
        sql: &'a str,
    ) -> anyhow::Result<<<E as Executor<'a>>::Database as Database>::QueryResult> where  <<E as Executor<'a>>::Database as HasArguments<'a>>::Arguments: IntoArguments<'a, <E as Executor<'a>>::Database>,
    {
        let result = sqlx::query(sql)
            .execute(self.executor).await?; // 不可能复用 &mut 
        Ok::<<<E as Executor<'_>>::Database as Database>::QueryResult, anyhow::Error>(result)
    }
}

你已经拥有了 TransactionManager

pub struct TransactionManager<'a, 'b> {
    pool:          &'a Pool<MySql>,
    tran:          Transaction<'b, MySql>,
    rollback_only: bool,
}

只要在这上面通过方法定义你的逻辑就行: rustexplorer

// 例子
impl<'a, 'b> TransactionManager<'a, 'b> {
    pub async fn execute_trans(&mut self, sql: &str) -> Result<MySqlQueryResult> {
        Ok(self.tran.execute(sql).await?)
    }

    pub async fn execute(&mut self, sql: &str) -> Result<MySqlQueryResult> {
        Ok(self.pool.execute(sql).await?)
    }

    pub async fn fetch_one(&mut self, sql: &str) -> Result<MySqlRow> {
        Ok(self.pool.fetch_one(sql).await?)
    }

    pub async fn fetch_all(&mut self, sql: &str) -> Result<Vec<MySqlRow>> {
        Ok(self.pool.fetch_all(sql).await?)
    }
}
苦瓜小仔 2022-11-19 16:57

对了,根据 Pool::begin,你只需要 TransactionManager<'a>

pub struct TransactionManager<'a> {
    pool:          &'a Pool<MySql>,
    tran:          Transaction<'static, MySql>,
    rollback_only: bool,
}
苦瓜小仔 2022-11-19 16:49

不可能的。对于 impl<'c, 't> Executor<'t> for &'t mut Transaction<'c, MySql> 和所有 .execute 方法,如 Query 的 execute 方法被定义成

pub async fn execute<'e, 'c, E>(
    self,
    executor: E
) -> impl Future<Output = Result<<DB as Database>::QueryResult, Error>>
where
    'c: 'e,
    'q: 'e,
    A: 'e,
    E: Executor<'c, Database = DB>,

显然,这意味着执行 sqlx 库中任何 .execute 方法需要获取 E 的所有权,而 &mut 是唯一的,一旦调用这个方法,你不可能复用那个 &mut Transaction。这决定了每次调用 .execute 之前需要生成一个 &mut Transaction。而生成 &mut Transaction 的前提是你拥有 Transaction(或者拥有 &mut Transaction 来 reborrow)。而 reborrow 是 Rust 众多人体工程学设计中的具体规则,并不是任何语法和抽象。总而言之,你无法定义 SqlCommandExecutor 那种抽象。

pub struct SqlCommandExecutor<'a,E: Executor<'a>>
    where <<E as Executor<'a>>::Database as HasArguments<'a>>::Arguments: IntoArguments<'a, <E as Executor<'a>>::Database>,
{
    executor:E,
    _phantom_data: std::marker::PhantomData<&'a ()>,
}

impl <'a,E:  Executor<'a>> SqlCommandExecutor<'a,E> where <<E as Executor<'a>>::Database as HasArguments<'a>>::Arguments: IntoArguments<'a, <E as Executor<'a>>::Database>{
    pub async fn execute(
        self,
        sql: &'a str,
    ) -> anyhow::Result<<<E as Executor<'a>>::Database as Database>::QueryResult> where  <<E as Executor<'a>>::Database as HasArguments<'a>>::Arguments: IntoArguments<'a, <E as Executor<'a>>::Database>,
    {
        let result = sqlx::query(sql)
            .execute(self.executor).await?; // 不可能复用 &mut 
        Ok::<<<E as Executor<'_>>::Database as Database>::QueryResult, anyhow::Error>(result)
    }
}

你已经拥有了 TransactionManager

pub struct TransactionManager<'a, 'b> {
    pool:          &'a Pool<MySql>,
    tran:          Transaction<'b, MySql>,
    rollback_only: bool,
}

只要在这上面通过方法定义你的逻辑就行: rustexplorer

// 例子
impl<'a, 'b> TransactionManager<'a, 'b> {
    pub async fn execute_trans(&mut self, sql: &str) -> Result<MySqlQueryResult> {
        Ok(self.tran.execute(sql).await?)
    }

    pub async fn execute(&mut self, sql: &str) -> Result<MySqlQueryResult> {
        Ok(self.pool.execute(sql).await?)
    }

    pub async fn fetch_one(&mut self, sql: &str) -> Result<MySqlRow> {
        Ok(self.pool.fetch_one(sql).await?)
    }

    pub async fn fetch_all(&mut self, sql: &str) -> Result<Vec<MySqlRow>> {
        Ok(self.pool.fetch_all(sql).await?)
    }
}
1 共 20 条评论, 1 页