Skip to content

Migrate database from SQL Server to PostgreSQL#5

Merged
chrismuench merged 4 commits into
mainfrom
feat/postgres-migration
Jul 10, 2026
Merged

Migrate database from SQL Server to PostgreSQL#5
chrismuench merged 4 commits into
mainfrom
feat/postgres-migration

Conversation

@chrismuench

Copy link
Copy Markdown
Collaborator

Replaces SQL Server / SQL Server Express with PostgreSQL (Npgsql) across the product, deployment, and docs. Fits the open-source relaunch: no proprietary DB dependency, no EULA, cross-platform.

What changed

  • Data layer: Microsoft.Data.SqlClientNpgsql 10. Connection factory, DatabaseInitializer (postgres maintenance DB, pg_database, transactional DDL), and health probe rewritten.
  • 7 migrations ported to PostgreSQL DDL (identity columns, boolean, timestamptz, "key" quoting, partial indexes; dynamic-SQL FK-drop collapsed).
  • All repository queries ported: MERGEINSERT … ON CONFLICT, OUTPUTRETURNING, TOPLIMIT, DELETE TOPctid subquery, ISNULLCOALESCE, IN @list= ANY(@list), SUM(bigint)::bigint casts, pg_database_size/pg_total_relation_size.
  • Size-pressure redesigned: opt-in, configurable DB-size cap (default off), overage-bounded purge + VACUUM FULL.
  • Deployment: docker-compose (postgres:17), Dockerfile, CI DB service, Windows installer (silent PostgreSQL bundle replacing SQL Express), Linux install.sh, and the virtual appliance.
  • CI: all jobs moved to self-hosted runners.
  • Docs: README, SPEC.md, RELEASING, installer README updated to PostgreSQL/Npgsql.

Validation

  • 312 tests pass in Release against a real postgres:17 (32 DB integration tests included).
  • End-to-end smoke test of the compose stack: app boots, creates the DB, applies all 7 migrations, /health reports connected:true via pg_database_size, and SMTP intake writes counters + audit rows through the ON CONFLICT upsert path.

Notes for reviewers

  • Behavior change: Message Log subject/tag search and SMTP-credential usernames are now case-sensitive (PostgreSQL default). Can switch subject/tag search to ILIKE if desired.
  • Installers untested on the build host (macOS): Windows MSI/WiX bundle and Linux install.sh are written and syntax-checked but need a real Windows/Linux box. InstallPostgres.ps1 has a REPLACE_WITH_PINNED_SHA256 placeholder to fill at release time.
  • Pre-existing quirk (not a regression): on a fresh install with zero relays configured, failure rows can't be written to relay_log (FK to relays) — identical on SQL Server.

🤖 Generated with Claude Code

Chris Muench and others added 4 commits July 9, 2026 18:33
Replace Microsoft.Data.SqlClient with Npgsql across Dispatch.Data:
- Connection factory, DatabaseInitializer (postgres maintenance DB, pg_database,
  CREATE TABLE IF NOT EXISTS, transactional DDL) and DatabaseHealth.
- Rewrite all 7 schema migrations to PostgreSQL DDL (identity columns, boolean,
  timestamptz, "key" quoting, partial indexes; collapse the dynamic-SQL FK drop).
- Port every repository query: MERGE→INSERT..ON CONFLICT, OUTPUT→RETURNING,
  TOP→LIMIT, DELETE TOP→ctid-subquery, ISNULL→COALESCE, SYSUTCDATETIME→now(),
  DATEADD→interval, IN @list→= ANY(@list), SUM(bigint)::bigint casts.
- Storage/maintenance use pg_database_size / pg_total_relation_size.
- Redesign size-pressure: opt-in configurable DB-size cap (default off) driven by
  pg_database_size, bounded overage purge + VACUUM FULL to reclaim.
- Port the integration-test fixture to Npgsql; case-sensitive log search (accepted).

All 312 tests pass against a real postgres:17 (32 DB integration tests included).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docker-compose.yml: postgres:17 service with pg_isready healthcheck, Npgsql
  connection string; drop Azure SQL Edge.
- Dockerfile / appsettings.json: reword SQL -> PostgreSQL/config table.
- build.yml: postgres:17 service container, Npgsql DISPATCH_TEST_SQL, health-cmd.
- All CI workflows (build/docker/appliance/installers/release/scripts) now run on
  self-hosted runners: ubuntu-latest -> [self-hosted, linux, x64],
  windows-latest -> [self-hosted, windows, x64].

Smoke-tested the full compose stack (Dispatch + postgres:17): schema init + all 7
migrations, /health connected via pg_database_size, live audit_log + relay_counters
writes (ON CONFLICT upsert) through SMTP intake.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Windows installer: replace the SQL Express Burn bootstrap with a silent PostgreSQL
  install (installer/windows/postgres/InstallPostgres.ps1 + PostgresLauncher);
  WiX bundle chains it, MSI SQLCONN defaults to a loopback-trusted 'dispatch' role,
  functional-smoke size-pressure test reframed as the opt-in DB-size cap.
- Linux installer (install.sh): --install-postgres/--db-password (apt/dnf postgresql,
  create dispatch role + DispatchLog db); dispatch-update.sh backup via pg_dump.
- Appliance: bake in postgresql (build/provision/firstboot), per-VM role password,
  systemd postgresql; drop the Microsoft EULA/license-terms.
- Docs: README, SPEC.md (schema DDL, bootstrap sections, size-pressure), RELEASING,
  installer README all moved to PostgreSQL/Npgsql.
- CI workflow wording + docker.yml service (sql -> db); comment cleanups in
  WebEndpoints/PurgeWorker/IStorageReport/tests.

Full suite (312 tests) green in Release against postgres:17.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The installers.yml full-install smoke assembled a tarball with only install.sh +
dispatch.service, but install.sh installs dispatch-updater.service and
dispatch-update.path too, so `install.sh --prebuilt` aborted at the unit step.
Ship the same file set as the release tarball (units + updater script + public
key) so the full-install smoke actually completes. Pre-existing gap, surfaced
while running the Linux full-install smoke end-to-end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chrismuench
chrismuench merged commit 04d289b into main Jul 10, 2026
3 of 7 checks passed
@chrismuench
chrismuench deleted the feat/postgres-migration branch July 10, 2026 03:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant