跳到主要内容
本页尚未翻译

下面显示的是英文原文。

AnyPool

With the any feature, an AnyPool is served by the built-in dialect of the database it reaches at connect; its rows hold integers, text and bytes only.

An AnyPool names its database by the scheme of its URL, so the service picks the database in its configuration and builds one binary for all of them. sqlx::any::install_default_drivers() registers the drivers before the pool connects. The broker picks the dialect when it connects, among the dialects whose features are on; another backend stops connect with SqlxBrokerError::Backend.

A row holds the types sqlx::Any carries: integers, text and bytes, with no time and no JSON. A table on an AnyPool therefore has no locked_until, retry_after, processed_at or headers column, and the lease form is out of its reach. It serves the row lock form on its Postgres and MySQL backends and the advisory lock form on all three. A row lock table on a SQLite backend stops its subscription at startup, because the database is known only then. For the same reason a table's isolation level compiles at every level, and the backend's dialect refuses one its database does not open when the subscription starts, with SqlxBrokerError::Dialect.

One Publish serves every backend when it writes the row through the derive's Insert on the connection the pool hands out. The AsyncAPI document describes the server by the URL's scheme, never its credentials. A test addresses the broker as tb.broker::<SqlxBroker<Any>>().

Example​

An email queue in the advisory lock form, which every backend serves; the test runs it on SQLite in memory.

use ruststream::OutgoingMessage;
use ruststream_sqlx::QueueDatabase;
use sqlx::any::AnyPoolOptions;
use sqlx::{Any, AnyConnection, Connection};

/// One row of `email_jobs` in the advisory lock form: integers, text and bytes, no time.
#[derive(Inbox, sqlx::FromRow)]
#[inbox(table = "email_jobs", advisory_lock = "email_jobs-{id}")]
struct SendEmail {
#[field(id, generated)]
id: i64,
#[field(group)]
name: String,
#[field(attempt, generated)]
attempt: i32,
#[field(payload)]
payload: Vec<u8>,
}

/// One insert for every database: the derive's own, on the connection the pool hands out.
impl<DB> Publish<DB> for SendEmail
where
DB: QueueDatabase,
Self: Insert<DB::Connection>,
{
async fn publish(
conn: &mut DB::Connection,
message: &OutgoingMessage<'_>,
) -> Result<(), sqlx::Error> {
let job = Self {
id: 0,
name: message.name().to_owned(),
attempt: 1,
payload: message.payload().to_vec(),
};
job.insert(conn).await
}
}

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

/// The service on the database `url` names.
fn service(url: &str) -> RustStream {
let schema_url = url.to_owned();
// Nothing connects here: the broker builds its pool when the service starts.
let broker = SqlxBroker::<Any>::from_url(url, AnyPoolOptions::new());
RustStream::new(AppInfo::new("mailer", "0.1.0"))
// The table, created before the subscription checks it.
.on_startup(async move |()| {
let mut conn = AnyConnection::connect(&schema_url).await?;
sqlx::raw_sql(SCHEMA).execute(&mut conn).await?;
conn.close().await
})
.with_broker(broker.route::<SendEmail>("emails"), |b| {
b.include(send);
})
}

#[ruststream::app]
fn app() -> impl App {
// An `AnyPool` reaches its driver by the URL's scheme.
install_default_drivers();
let url = env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite:mail.db?mode=rwc".to_owned());
service(&url)
}