Add Postgres backend support
Add Postgres backend support
#24 in Riparion/riparion-cms — merged 2026-05-31
Summary
cargo build --no-default-features --features server,postgresnow produces a working server end-to-end. SQLite remains the default; existingdata/blog.dbfiles are preserved bit-for-bit on upgrade (sqlx-sqlite's chrono decoder accepts the legacy'YYYY-MM-DD HH:MM:SS'timestamp format unchanged, so no backfill).- Migrations split into per-backend dirs (
migrations/{sqlite,postgres}/) and tracked bysqlx::migrate!. Riparion uses timestamp-versioned filenames (20260601000000_blog_init.sql) so it shares_sqlx_migrationswith arium's1..9rows without colliding. - New
src/db/dialect.rscovers the few SQL fragments that diverge between backends (NOW,RANDOM_HEX_16,now_offset(n)). Placeholders unified on$N(sqlx-sqlite accepts it — verified bysqlx_sqlite_accepts_dollar_placeholders).INSERT OR IGNORErewritten toINSERT ... ON CONFLICT DO NOTHING,last_insert_rowid()toRETURNING id— both work in both backends. - Row-struct timestamp fields converted from
StringtoOption<DateTime<Utc>>. Helpersto_rfc3339/fmt_datetake&DateTime<Utc>. - FTS gets a
cfg-gatedsearch_sqlbuilder indb/posts.rs(FTS5MATCH/rankfor SQLite,tsvector @@/ts_rankfor Postgres). - CI:
clippyjob matrixed on[sqlite, postgres]; newpg-migratejob with aservices: postgres:16-alpineblock runspg_migrations_apply(new postgres-only test) against a live DB to catch dialect SQL regressions. - Docker:
Dockerfiletakes aBACKEND={sqlite|postgres}build arg; newdocker-compose.postgres.ymloverlay adds apostgres:16-alpinesidecar with healthcheck.docker-compose.ymlgainsDX_RATE_LIMITenv passthrough.
Required upstream: Riparion/arium#22 (users.id/roles.id as BIGSERIAL, User.id widened to i64). Already merged; picked up by the Cargo.lock bump in this PR.
End-to-end verification steps live in TODO_VERIFICATION.md.
Test plan
-
cargo fmt --check -
cargo clippy --no-default-features --features server,sqlite --all-targets -- -D warnings -A clippy::too_many_arguments -
cargo clippy --no-default-features --features server,postgres --all-targets -- -D warnings -A clippy::too_many_arguments -
cargo clippy --no-default-features --features web --target wasm32-unknown-unknown -- -D warnings -A clippy::too_many_arguments -A dead_code -
cargo test --no-default-features --features server,sqlite— 26 passed -
cargo test --no-default-features --features server,postgres pg_migrations_applyagainstpostgres:16-alpine— passed - SQLite upgrade preservation: existing
data/blog.db(25 posts / 33 comments / 5 subscribers, pre-branch timestamps) renders the home page, post detail, comments, and Atom feed without errors._sqlx_migrationsgains exactly one new row (20260601000000 | blog init). - SQLite fresh seed: create / edit / publish a post → appears on
/; updatesupdated_atvia the{NOW}dialect helper; cascades on delete. - Postgres docker stack (
docker-compose.yml+docker-compose.postgres.yml): seed planted 3 users, 4 categories, 10 tags, 25 posts, 30 comments, 5 subscribers. Atom feed renders with RFC3339-Z timestamps decoded fromTIMESTAMPTZ. Login succeeded after the upstream arium fix landed. - PR's own CI run goes green (clippy matrix + sqlite test + pg-migrate + wasm + audit + machete + gitleaks)
🤖 Generated with Claude Code
Last updated 2026-06-01
Links to this note
Credits
Merged pull requests, newest first.