sqlx-cloudflare-do
A Cloudflare Durable Objects driver for sqlx: a real
sqlx::Databaseover a Durable Object's SQL storage, so code written against sqlx's runtime API runs inside a Rust Durable Object.
[!NOTE] Pre-1.0. The API may still change between minor versions.
use ;
use ;
The class must be SQLite-backed -- new_sqlite_classes in wrangler.toml:
[[]]
= "USERS"
= "Users"
[[]]
= "v1"
= ["Users"]
Install
[]
= "0.2"
= { = "0.9", = false, = ["derive"] }
= "0.8"
# `#[durable_object]` expands to code that names it directly.
= "0.2"
| sqlx-cloudflare-do | sqlx | worker | Rust |
|---|---|---|---|
| 0.2 | 0.9 | 0.8 | 1.94+ |
| 0.1 | 0.9 | 0.8 | 1.94+ |
The crate runs on wasm32-unknown-unknown, inside a Durable Object.
How it differs from D1
The database lives in the Durable Object, so a query never leaves the
process: sql.exec runs it before returning, and the driver reads every row
out on the spot. Each query's future finishes on its first poll. Types map
exactly as in
sqlx-cloudflare-d1 -- the
two share one definition.
Transactions
sql.exec rejects BEGIN, so a transaction is a callback. It commits when
the callback returns Ok and rolls back every write when it returns Err,
and inside it you read your own writes:
let id = conn
.transaction
.await?;
- The callback owns what it uses. It is
'static-- move data in, and run queries through thetxit is handed, not the connection. - It holds the whole object. No other request reaches the object until
the transaction ends, even while the callback awaits something that is not
storage. That keeps other requests' writes out of it -- and is why it should
be short: no
fetch()inside, and never a request to the object itself.
Batches
execute_batch runs statements atomically without a callback, and
fetch_batch streams each statement's rows and result as well -- the shape of
sqlc's :batchexec, :batchone and :batchmany queries:
use TryStreamExt;
use Row;
let names: = conn
.fetch_batch
.map_ok
.try_collect
.await?;
Limitations
- No
query!/query_as!macros. sqlx's macros only know its built-in drivers. The runtime API (sqlx::query,query_as,FromRow) works. - No
begin().sql.execrejectsBEGINandSAVEPOINT, so sqlx'sbegin()fails. Use a transaction callback. - No
sqlx::Pool. Nothing to pool: the connection is a handle to storage the object owns. Keep one in the object's struct. - Integers are limited to ±(2^53 − 1). Values cross as JavaScript
numbers, so a wider
i64is refused rather than silently rounded. Note that sqlx'sQueryBuilder::push_bindpanics on a refused value instead of returning the error. rows_affectedcomes fromchanges(). Like SQLite's own count, it covers the rows the statement itself inserted, updated or deleted, not those its triggers touched. A statement that wrote nothing reports 0 for bothrows_affectedandlast_insert_rowid.- No
sqlx::migrate!. Create the schema from the object's constructor. - No derived TEXT enums.
#[derive(sqlx::Type)]works for#[sqlx(transparent)]newtypes and#[repr(i32)]enums, but sqlx only generates string-backed enums for its built-in drivers. - No statement logging.
log_statementsis accepted and ignored.