navidrome/db/pglite
Deluan 98555ca499 feat(db): spike an embedded PostgreSQL via PGlite under wazero
Adds db/pglite, a bridge that runs the PGlite WASI build of PostgreSQL 17.5
inside the Navidrome process with wazero and exposes it to database/sql
through pgx over the real wire protocol. Selected with DbPath = "pglite://<dir>".

The bridge handles what the WASI build cannot do on its own: it emulates dup2
onto stdin (wazero refuses fd_renumber on preopens), replays ParameterStatus
for connections after the first, synthesizes a missing ReadyForQuery after an
error, recovers from the trap every PG ERROR raises, and passes wire bytes
through shared wasm memory instead of files (round-trip floor 377 µs to 39 µs).
Several connections are accepted and serialized onto the single backend,
holding the session across a whole transaction and a whole handshake; a client
that disconnects mid-transaction gets a ROLLBACK.

db.go opens the pglite:// scheme, skips the SQLite-only migrations, and can
apply a translated schema from ND_PGLITE_SCHEMA. The shared count() helper now
adds its ORDER BY only for SQLite, since Postgres rejects it next to
count(distinct ...).

The wasm archive is not committed. The README explains how to build it and
lists the limits found: one session, one process (DevExternalScanner must be
off), shared session state, simple protocol only.
2026-09-04 18:29:58 -04:00
..
concurrency_test.go feat(db): spike an embedded PostgreSQL via PGlite under wazero 2026-09-04 18:29:58 -04:00
pglite.go feat(db): spike an embedded PostgreSQL via PGlite under wazero 2026-09-04 18:29:58 -04:00
pglite_suite_test.go feat(db): spike an embedded PostgreSQL via PGlite under wazero 2026-09-04 18:29:58 -04:00
pglite_test.go feat(db): spike an embedded PostgreSQL via PGlite under wazero 2026-09-04 18:29:58 -04:00
proto_test.go feat(db): spike an embedded PostgreSQL via PGlite under wazero 2026-09-04 18:29:58 -04:00
README.md feat(db): spike an embedded PostgreSQL via PGlite under wazero 2026-09-04 18:29:58 -04:00
setup.go feat(db): spike an embedded PostgreSQL via PGlite under wazero 2026-09-04 18:29:58 -04:00
stdin.go feat(db): spike an embedded PostgreSQL via PGlite under wazero 2026-09-04 18:29:58 -04:00

db/pglite — spike: PostgreSQL embedded in the Navidrome binary

Runs the PGlite WASI build of PostgreSQL 17.5 inside the process with wazero (pure Go, no CGO), and exposes it to database/sql through the real PostgreSQL wire protocol. Enabled with DbPath = "pglite://<dir>".

This is exploratory. It is not a supported way to run Navidrome. The 131 SQLite migrations are not ported, so the schema has to be supplied by hand (ND_PGLITE_SCHEMA), and several queries Navidrome generates are SQLite-only.

How it fits together

Navidrome → database/sql → pgx → unix socket → bridge → wasm memory → PostgreSQL (wasm)

The bridge speaks no SQL. It copies wire-protocol bytes, and only inspects message framing, the handshake, and the ReadyForQuery status byte. PGlite.OpenDB() swaps the unix socket for an in-process pipe; both perform the same, so the socket is the default because external tools can use it.

Getting the wasm binary

Not in the repository: it is a 6.7 MB archive containing a 17 MB wasm module. Build it, then point the tests at it with ND_PGLITE_TARBALL, or drop it at tmp/pglite-wasi-O2-fix.tar.gz (the default the tests look for).

The published WASI builds are all compiled -O0; rebuilding with -O2 is worth 2 to 3x on every query and cuts startup from 16 s to 1.8 s. The build recipe and the patches, including one C fix of our own, are in the spike notes under tmp/pglite-build/ on the pglite-spike branch.

Connecting with psql

PGPASSWORD=x PGSSLMODE=disable psql -h /absolute/path/to/DataFolder -U postgres postgres

The exact command is logged at startup. A password is required even though PGlite skips authentication. Plain SQL works; \dt and \d do not, because the build's session schema is pg_catalog and its system views are missing.

Known limits

  • One session. PGlite is a single PostgreSQL backend. The bridge accepts several connections and serializes them, holding the session across a whole transaction and a whole handshake. Four connections perform exactly like one.
  • One process. Set DevExternalScanner = false. Navidrome otherwise scans in a child process, which would open a second PGlite on the same data directory and corrupt it.
  • Session state is shared. SET, temp tables and the like leak between clients.
  • Simple protocol only. The WASI build mishandles extended-protocol portals, so the DSN pins default_query_exec_mode=simple_protocol.
  • No COPY FROM STDIN. The tick loop cannot suspend to wait for the data rows.

Environment switches

variable effect
ND_PGLITE_TARBALL path to the wasm archive (tests)
ND_PGLITE_SCHEMA apply a schema file after connecting, tolerating failures
ND_PGLITE_TRACE log wire traffic and per-tick timings
ND_PGLITE_NOCMA force the file transport instead of shared memory