Skip to content

feat(hd): add sqlx-cloudflare-hd, sqlx's Postgres driver over Hyperdrive - #16

Draft
iamralch wants to merge 2 commits into
mainfrom
feat/sqlx-cloudflare-hd
Draft

iamralch wants to merge 2 commits into
mainfrom
feat/sqlx-cloudflare-hd

Conversation

@iamralch

@iamralch iamralch commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Rust Workers can talk to D1 and Durable Object storage through sqlx, but not to Postgres through Hyperdrive. Hyperdrive speaks the Postgres wire protocol, so sqlx-postgres should just work, except stock sqlx 0.9 panics twice on wasm32-unknown-unknown: connecting ends in rt::missing_rt, since sqlx can only open sockets through tokio or async-io, and every query calls std::time::Instant::now(), which isn't implemented on that target.

Change

  • Upstream fix. Postgres: connect over a caller-provided socket, and a wasm-safe Instant, for hosts like Cloudflare Workers transact-rs/sqlx#4426 proposes the two changes: PgConnection::connect_with_socket(&options, socket), and sqlx_core::rt::Instant (web-time on that target). The branch is iamralch/sqlx wasm-custom-socket.
  • Workspace patch. Until that ships, the workspace [patch.crates-io] takes sqlx, sqlx-core and sqlx-postgres from that branch, pinned to 4313dae. The patch applies to the whole graph, so D1 and DO build against it too; their test Workers keep their own locks on crates.io sqlx.
  • New crate sqlx-cloudflare-hd, with no driver of its own. Laid out like sqlx: one feature per database, none by default, so MySQL (which Hyperdrive also fronts) can come later. postgres::connect(&env, binding), postgres::connect_with(&hyperdrive, &options) and postgres::options(&hyperdrive) return sqlx's own PgConnection/PgConnectOptions, so consumers get sqlx::Postgres: its types, query_as, FromRow, transactions and sqlx::migrate!. The socket adapter and binding lookup are shared.
  • TLS goes through the Workers runtime. The socket opens in StartTls mode. The crate sends the SSLRequest itself, upgrades with startTls() when the server accepts, and then hands the socket to sqlx with sslmode=disable so sqlx doesn't negotiate again. prefer/require behave as in libpq. It refuses bytes that arrive after S but before the upgrade (CVE-2021-23222).
  • Socket adapter. HdSocket turns worker::Socket's AsyncRead/AsyncWrite into sqlx's readiness-based Socket.
  • Consumer setup. getrandom's wasm_js feature is enabled by the crate, so consumers don't have to.
  • Test Worker. 10 scenarios: types, 2,000 rows over many reads, unique-violation mapping, rollback and commit, statement reuse, FromRow, migrations, sslmode disable and require, and custom options. run.sh starts a scratch Postgres with SCRAM auth and no TLS and points the binding at it through CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HD. The dev shell gains postgresql, and make test-worker gets a third runner.

Not mergeable yet

  • Depends on an unreleased sqlx. A git dependency can't go to crates.io. The crate is publish = false and not in the release-please config. When sqlx releases #4426: drop the patch, bump sqlx, and add the crate to release-please.
  • Never run against a real Hyperdrive. wrangler dev --local connects straight to Postgres, so the accepted-TLS path (S, then startTls()) and Hyperdrive's own behaviour are unexercised. That needs a real Hyperdrive config on a Cloudflare account.
  • No Pool or PgListener. Both need a runtime to spawn tasks. One connection per request is the intended model; Hyperdrive does the pooling.

Verification

  • make check lint test doc-check pass, host and wasm32.
  • make test-worker passes for all three: D1 29 scenarios, DO 36, Hyperdrive 10.
  • On the fork: its tests/postgres/postgres.rs (58 passed, 2 ignored, plus the new it_connects_with_socket) and tests/postgres/migrate.rs (4) pass. With only the Instant change reverted, the Worker panics with time not implemented on this platform.

Hyperdrive speaks the Postgres wire protocol, so this crate writes no
driver: it opens the Worker's socket to a Hyperdrive binding, settles TLS
through the Workers runtime's startTls(), and hands the socket to
sqlx-postgres. `connect`, `connect_with` and `options` return sqlx's own
`PgConnection` and `PgConnectOptions`, so consumers get `sqlx::Postgres`
with all of its types, transactions and `sqlx::migrate!`.

It needs two sqlx changes that are not released yet, proposed in
transact-rs/sqlx#4426: `PgConnection::connect_with_socket`, and an
`Instant` that does not panic on wasm32-unknown-unknown. Until they ship,
the workspace patches every sqlx crate to that branch, pinned to a commit,
and the crate is `publish = false` and out of the release-please config.

The test Worker runs ten scenarios under `wrangler dev --local` against a
scratch Postgres that run.sh starts with SCRAM auth and no TLS, so the dev
shell gains postgresql and `make test-worker` a third runner.
Hyperdrive also fronts MySQL, so the crate is laid out as sqlx is: one
feature per database, none by default, each adding a module that returns
that sqlx driver's connection. Postgres moves to `postgres::connect`,
`postgres::connect_with` and `postgres::options`; the socket adapter and
the binding lookup stay shared, and `HdSocket::connect` opens the socket
for any driver.

The sqlx dev-dependency gains `macros`, as D1's has: it is what turns on
`sqlx-postgres/offline`, and without it a build with no features of this
crate's own gets sqlx-postgres without `describe`.
@iamralch iamralch self-assigned this Sep 28, 2026
@iamralch iamralch added the enhancement New feature or request label Sep 28, 2026
Themezv added a commit to MemeBattle/monorepo that referenced this pull request Oct 18, 2026
…e-hd (#794)

* build: pin sqlx to the MemeBattle fork (connect_with_socket, wasm Instant)

sqlx 0.9.0 cannot open a Postgres connection on wasm32-unknown-unknown:
PgStream::connect only knows TCP and unix sockets, and
std::time::Instant::now() traps there. MemeBattle/sqlx tag
v0.9.0-memebattle.1 is v0.9.0 plus the two commits upstream refused in
transact-rs/sqlx#4444 (PgConnection::connect_with_socket and a wasm-safe
Instant). The only lockfile changes are the sqlx crates' source and
web-time, which the fork's sqlx-core uses on wasm32.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* build: vendor sqlx-cloudflare-hd from sqlx-contrib/sqlx-cloudflare#16

Copied from branch feat/sqlx-cloudflare-hd at 9499455d (unpublished, MIT).
It opens a Worker socket to a Hyperdrive binding and hands it to sqlx's own
Postgres driver through PgConnection::connect_with_socket. Sources, README
and LICENSE are verbatim; only Cargo.toml is rewritten to stand alone outside
the upstream workspace. VENDOR.md records the source and every change.

worker 0.8.6+ needs wasm-bindgen 0.2.128+, so the lockfile moves the
wasm-bindgen family (wasm-bindgen 0.2.129, js-sys/web-sys 0.3.106,
wasm-bindgen-futures 0.4.79); those only build on wasm targets. oxfmt now
skips vendor/ so the copied Markdown stays byte for byte.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci(cas): run the CAS workflow on vendor changes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* ci(cas): compile the vendored Hyperdrive adapter for wasm32

Adding vendor/** to the path filter only decided when the workflow runs;
every job still built apps/cas alone, and CAS does not depend on
sqlx-cloudflare-hd, so a regression in the adapter or in the wasm-only code
of the sqlx fork could merge with every check green. A dedicated job now
checks and lints the crate with its postgres feature for
wasm32-unknown-unknown, the target it exists for.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant