Skip to content

dotnet+infra: scaffold HoldFast.Data.Postgres analytics backend (HOL-26)#107

Merged
BrewingCoder merged 1 commit into
mainfrom
issue-26-postgres-scaffold
May 9, 2026
Merged

dotnet+infra: scaffold HoldFast.Data.Postgres analytics backend (HOL-26)#107
BrewingCoder merged 1 commit into
mainfrom
issue-26-postgres-scaffold

Conversation

@BrewingCoder
Copy link
Copy Markdown
Owner

Summary

Foundation for the Postgres analytics backend EPIC (HOL-28). Adds the project, options, migration runner, and three foundational migrations. No backend behavior change — the existing ClickHouse stack keeps working untouched. The Postgres store implementations and the runtime Storage:Analytics switch land in HOL-29 through HOL-34.

What this PR adds

  • New project HoldFast.Data.Postgres — depends only on HoldFast.Analytics + Npgsql 10.0.2. No EF Core in the analytics path; HOL-29+ will use direct Npgsql for the high-volume insert path.
  • PostgresAnalyticsOptions — connection-string + schema-name config; defaults the connection string to ConnectionStrings:PostgreSQL so the hobby stack runs both relational and analytics schemas in the one PG container.
  • PostgresMigrationService (IHostedService) — mirrors ClickHouseMigrationService design. Same NNNN_description.up.sql file convention, same logging shape, same disable knob. Each migration runs in its own transaction; dirty/clean state recorded in analytics.schema_migrations for parity with golang-migrate.
  • Three foundational migrations:
    • 0001_create_analytics_schema.up.sql
    • 0002_create_schema_migrations.up.sql
    • 0003_install_extensions.up.sql (TimescaleDB + pg_partman, both conditional via DO blocks so vanilla PG images no-op gracefully — required for the hobby stack which uses ankane/pgvector)
  • DI registration in Program.cs alongside the existing CH services. Disabled by default via the PostgresAnalytics:Migrations:Disabled config flag — opt in to start applying migrations.
  • Migration files baked into the backend image at /app/postgres-analytics-migrations.
  • 30 new unit tests on the runner's pure helpers (ParseVersion, SanitizeIdentifier) including SQLi-attempt strings, empty/whitespace inputs, and edge cases per the project's OVER TEST rule.

Acceptance verified

  • dotnet build — 0 errors, 0 new warnings
  • dotnet test3,061 pass (was 3,031; +30 PG helper tests)
  • All 3 migrations apply cleanly against the running ankane/pgvector container via psql:
    • analytics schema created
    • analytics.schema_migrations(version, dirty, applied_at) table created with PK on version
    • Conditional extension blocks emit NOTICE: HOL-26: TimescaleDB extension not available... and no-op (expected behavior on vanilla PG)
  • Existing CH backend untouched

What this PR does NOT do

  • Add any IXxxStore implementation — those are HOL-29 (Logs), HOL-30 (Traces), HOL-31 (Sessions), HOL-32 (Errors), HOL-33 (Metrics+Events+AlertState). Each domain PR ports the CH migrations it needs as part of building its store.
  • Add the Storage:Analytics config switch — that's HOL-34, which arrives last.
  • Touch ClickHouse.

Stats

14 files changed, +550 / -0.

Closes HOL-26. First foundation PR of the Postgres-backend EPIC (HOL-28).

🤖 Generated with Claude Code

Foundation for the Postgres analytics backend EPIC (HOL-28). Adds the
project, options, migration runner, and three foundational migrations.
The .NET store implementations (HOL-29..33) and the Storage:Analytics
config switch (HOL-34) are deferred to follow-up PRs.

What this PR adds:

- New project HoldFast.Data.Postgres
  - depends only on HoldFast.Analytics + Npgsql 10.0.2
  - no EF Core in the analytics path (kept query-direct for high-volume
    insert performance later in HOL-29+)
- PostgresAnalyticsOptions / PostgresAnalyticsMigrationOptions
  - ConnectionString defaults to ConnectionStrings:PostgreSQL (the
    relational DB), so the hobby stack hosts both schemas in one PG
  - Schema name configurable but defaults to `analytics`
- PostgresMigrationService (IHostedService)
  - mirrors ClickHouseMigrationService design — same file naming,
    same logging shape, same disable knob
  - applies *.up.sql idempotently in a per-file transaction
  - records dirty/clean state in analytics.schema_migrations for
    parity with golang-migrate
  - bootstraps the analytics schema + tracking table before running
    any migration (so the runner can record into its own tracking
    table on a virgin DB)
- Three foundational migrations:
  - 0001_create_analytics_schema.up.sql
  - 0002_create_schema_migrations.up.sql
  - 0003_install_extensions.up.sql (TimescaleDB + pg_partman, both
    conditional via DO blocks so vanilla PG images no-op gracefully)
- DI registration in Program.cs (alongside the existing CH services,
  no Storage:Analytics switch yet — that's HOL-34)
- Migration files baked into the backend image at
  /app/postgres-analytics-migrations
- 30 unit tests on the runner's pure helpers (ParseVersion +
  SanitizeIdentifier), including SQLi-attempt strings, empty inputs,
  and edge cases per the project's OVER TEST rule
- Smoke verified: applied all three migrations against the running
  ankane/pgvector hobby container; schema + table created cleanly,
  conditional extension blocks emit informational NOTICEs and
  no-op as designed

What this PR does NOT do:

- Add any IXxxStore implementation (HOL-29..33 each port their
  domain's CH migrations as part of building their store)
- Add the Storage:Analytics config switch (HOL-34)
- Touch ClickHouse — the existing CH backend keeps working unchanged

Test coverage: 3,061 tests pass (was 3,031; +30 new PG migration
helper tests).

Refs HOL-26 / HOL-28.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@BrewingCoder BrewingCoder merged commit b941df2 into main May 9, 2026
4 checks passed
@BrewingCoder BrewingCoder deleted the issue-26-postgres-scaffold branch May 9, 2026 16:10
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