ruststream-sqlx 0.7.0

SQL databases for the RustStream messaging framework through sqlx: a transactional outbox over any RustStream broker and task queues in Postgres, MySQL/MariaDB and SQLite tables.
Documentation
# Startup checks and rows that do not decode

```no_run
# #[cfg(all(feature = "postgres", feature = "chrono"))]
# mod demo {
use std::time::Duration;

use chrono::{DateTime, Utc};
use ruststream_sqlx::prelude::*;
use serde::Deserialize;
use sqlx::PgPool;

#[derive(Inbox, sqlx::FromRow)]
#[inbox(table = "email_jobs")]
pub struct SendEmail {
    #[field(id, generated)]
    job_id: i64,
    #[field(group)]
    name: String,
    #[field(retry_after, generated)]
    retry_after: DateTime<Utc>,
    #[field(payload)]
    payload: Vec<u8>,
}

#[derive(Deserialize)]
pub struct Email {
    to: String,
}

#[subscriber(InboxQueue::<SendEmail>::new("emails"))]
async fn send(email: &Email) -> HandlerOutcome {
    tracing::info!(to = %email.to, "sending");
    HandlerOutcome::ack()
}

pub fn app(pool: PgPool) -> RustStream {
    // A row that does not decode stays in the table and comes back every ten minutes, until a
    // release that reads it ships.
    let keep = FailurePolicy::RetryAfter(Duration::from_secs(600));
    RustStream::new(AppInfo::new("mailer", "0.1.0")).with_broker(SqlxBroker::new(pool), |b| {
        b.include(send.on_failure(FailurePolicies::default().with_decode(keep)));
    })
}
# }
# fn main() {}
```

A mistake stops the service as early as it can be seen. A struct that cannot drive a queue does not
compile, and [`Inbox`] lists the errors; [macro or manual](#macro-or-manual) shows where each rule
of a table described by hand is checked. A subscription builds its statements when it opens and
prepares each one on the server: a table or a column the description names and the database lacks
stops it, and [`SqlxBrokerError::Schema`] names the table and the statement. Preparing checks the
names, not the column types: the types are the service's to get right. A retry declaration the table
cannot carry stops it too ([`SqlxBrokerError::Declaration`]): `max_attempts(..)` and
`dead_letter(..)` come together, and the cap needs an `attempt` column. On MySQL and MariaDB the
subscription also reads the server's version.

A claimed row whose columns do not decode into the struct reaches the subscription's
`on_failure(decode = ..)` policy, which settles it, and the subscription goes on with the next
row. The default policy drops the row: it is deleted, or marked where the table has
`processed_at`. Such a row still reports its attempt: the claim reads the `attempt` column alone,
as the struct reads it. So `max_attempts(..)` spends it like any other row, and a policy that
retries it keeps it only up to the cap. When the `attempt` column itself does not decode, the row
reports no attempt, and a policy that retries it keeps it in the queue until it decodes. The
policy settles such a row before the handler runs, whatever the handler takes, a `Deserialized`
type included. It settles the same way a claimed id that the service's own fetch returned no row
for. A row whose id does not decode fails the claim, and the error names the subscription and the
table; every claim that reaches the row fails the same way until the row is fixed.