dotnet+infra: scaffold HoldFast.Data.Postgres analytics backend (HOL-26)#107
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:Analyticsswitch land in HOL-29 through HOL-34.What this PR adds
HoldFast.Data.Postgres— depends only onHoldFast.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 toConnectionStrings:PostgreSQLso the hobby stack runs both relational and analytics schemas in the one PG container.PostgresMigrationService(IHostedService) — mirrorsClickHouseMigrationServicedesign. SameNNNN_description.up.sqlfile convention, same logging shape, same disable knob. Each migration runs in its own transaction; dirty/clean state recorded inanalytics.schema_migrationsfor parity withgolang-migrate.0001_create_analytics_schema.up.sql0002_create_schema_migrations.up.sql0003_install_extensions.up.sql(TimescaleDB + pg_partman, both conditional viaDOblocks so vanilla PG images no-op gracefully — required for the hobby stack which usesankane/pgvector)Program.csalongside the existing CH services. Disabled by default via thePostgresAnalytics:Migrations:Disabledconfig flag — opt in to start applying migrations./app/postgres-analytics-migrations.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 warningsdotnet test— 3,061 pass (was 3,031; +30 PG helper tests)ankane/pgvectorcontainer viapsql:analyticsschema createdanalytics.schema_migrations(version, dirty, applied_at)table created with PK onversionNOTICE: HOL-26: TimescaleDB extension not available...and no-op (expected behavior on vanilla PG)What this PR does NOT do
IXxxStoreimplementation — 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.Storage:Analyticsconfig switch — that's HOL-34, which arrives last.Stats
14 files changed, +550 / -0.
Closes HOL-26. First foundation PR of the Postgres-backend EPIC (HOL-28).
🤖 Generated with Claude Code