Sqlx, issue with trait bounds for transactions

Im trying to extend sqlx Query generically to provide a "finisher" (like execute(), fetch_one(), etc) that will also write a message to an outbox table. The method should take a Transaction, and should reject any other thing that derefs to Executor, since I wan't to be absolutley sure that the original query and the INSERT to my outbox table happens within the same transaction - but I'm running into issues setting up the trait bounds for it.

I've got this:

// ------------------------------------------------------------
// Trait definition
// ------------------------------------------------------------
pub trait Publishable<'q, DB>
where
    DB: Database,
{
    const OUTBOX_INSERT: &'static str = r#"
        INSERT INTO outbox (id, channel, message)
        VALUES ($1, $2, $3, $4)
    "#;

    ///
    async fn outbox_transact<'a>(
        self,
        tx: &'a mut Transaction<'a, DB>,
    ) -> Result<<DB as Database>::QueryResult, sqlx::Error>;
}

// ------------------------------------------------------------
// Geneirc implmentation for Query
// ------------------------------------------------------------
impl<'q, DB, A> Publishable<'q, DB> for Query<'q, DB, A>
where
    DB: Database,
    A: IntoArguments<'q, DB> + Send + 'q,
    for<'c> &'c mut Transaction<'c, DB>: Executor<'c, Database = DB>,
{
    async fn outbox_transact<'a>(
        self,
        tx: &'a mut Transaction<'a, DB>,
    ) -> Result<<DB as Database>::QueryResult, sqlx::Error> {
        self.execute(tx).await
    }
}

Now the above seems to check out at first, but when I try to invoke it on a query like this:

let mut tx = pool.begin().await?;

let query = sqlx::query(
    r#"
        INSERT INTO example (data)
        VALUES ($1)
    "#,
)
.bind(1);

query.outbox_transact(&mut *tx).await?; // <<< compile error here

tx.commit().await?;

Then I run into this compiler error:

error[E0599]: the method `outbox_transact` exists for struct `Query<'_, _, _>`, but its trait bounds were not satisfied
   --> src/outbox/driven.rs:199:15
    |
199 |         query.outbox_transact(&mut *tx).await?;
    |               ^^^^^^^^^^^^^^^ method cannot be called on `Query<'_, _, _>` due to unsatisfied trait bounds
    |
   ::: /home/jakob/.cargo/registry/src/index.crates.io-6f17d22bba15001f/sqlx-core-0.8.2/src/query.rs:17:1
    |
17  | pub struct Query<'q, DB: Database, A> {
    | ------------------------------------- doesn't satisfy `_: Publishable<'_, _>`
    |
note: the following trait bounds were not satisfied:
      `&'c mut Transaction<'c, _>: sqlx::Executor<'c>`
      `<&'c mut Transaction<'c, _> as sqlx::Executor<'c>>::Database = _`
   --> src/outbox/driven.rs:154:42
    |
150 | impl<'q, DB, A> Publishable<'q, DB> for Query<'q, DB, A>
    |                 -------------------     ----------------
...
154 |     for<'c> &'c mut Transaction<'c, DB>: Executor<'c, Database = DB>,
    |                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |                                          |            |
    |                                          |            unsatisfied trait bound introduced here
    |                                          unsatisfied trait bound introduced here
    = help: items from traits can only be used if the trait is implemented and in scope
note: `outbox::driven::Publishable` defines an item `outbox_transact`, perhaps you need to implement it
   --> src/outbox/driven.rs:131:1
    |
131 | pub trait Publishable<'q, DB>
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Then, in trying to fix it I loosen the bounds on the impl to:

for<'c> &'c mut <DB as Database>::Connection: Executor<'c, Database = DB>

...but then I instead get into errors on the call to execute() within my trait impl

error[E0277]: the trait bound `&mut Transaction<'a, DB>: sqlx::Executor<'_>` is not satisfied
   --> src/outbox/driven.rs:160:26
    |
160 |         self.execute(tx).await
    |                          ^^^^^ the trait `sqlx::Executor<'_>` is not implemented for `&mut Transaction<'a, DB>`
    |
    = help: the following other types implement trait `sqlx::Executor<'c>`:
              `&'c mut AnyConnection` implements `sqlx::Executor<'c>`
              `&'c mut MySqlConnection` implements `sqlx::Executor<'c>`
              `&'c mut PgConnection` implements `sqlx::Executor<'c>`
              `&'c mut PgListener` implements `sqlx::Executor<'c>`
              `&'c mut SqliteConnection` implements `sqlx::Executor<'c>`
              `&Pool<DB>` implements `sqlx::Executor<'p>`
note: required by a bound in `sqlx::query::Query::<'q, DB, A>::execute`
   --> /home/jakob/.cargo/registry/src/index.crates.io-6f17d22bba15001f/sqlx-core-0.8.2/src/query.rs:188:12
    |
184 |     pub async fn execute<'e, 'c: 'e, E>(self, executor: E) -> Result<DB::QueryResult, Error>
    |                  ------- required by a bound in this associated function
...
188 |         E: Executor<'c, Database = DB>,
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Query::<'q, DB, A>::execute`

Would be very grateful for any insight on the above, I'm pretty stuck with this.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.