Import Marten.PostGIS and Marten.PgVector from CritterWatch#4576
Merged
Conversation
Adds two optional companion packages — `Marten.PostGIS` and `Marten.PgVector` — under Marten's MIT license, importing the projects originally prototyped under `~/code/critterwatch/tools/`. CritterWatch itself is left untouched (per instruction, no removal yet). What lands here: - **`src/Marten.PostGIS`** + tests (3 source files + 1 test class). `UsePostGIS()` opt-in that registers the `postgis` extension on every database Marten manages (multi-tenant aware), wires NetTopologySuite + GeoJSON serialization, and exposes four query helpers — `NearestToAsync`, `WithinDistanceAsync`, `ContainingAsync`, `IntersectingAsync`. - **`src/Marten.PgVector`** + tests (7 source files + 4 test classes covering single-tenant, conjoined multi-tenancy, database-per-tenant, and the embedding-aware `VectorProjection`). `UsePgVector()` opt-in that registers the `vector` extension on every database — also addresses the long-standing #2515 (extensions not created in tenant databases). `VectorSearchAsync` + a `VectorProjection` base class with content-hash skipping so unchanged content is not re-embedded. Adapted to Marten conventions: - Both libraries multi-target via `Directory.Build.props` (net9.0;net10.0), use CPM, and follow the existing `Marten.NodaTime` csproj template (Description + GenerateAssembly* attrs). - Tests use `Marten.Testing.Harness.ConnectionSource` (port 5432, `marten_testing` DB) instead of the CritterWatch-local `5442`/`postgres`. The database-per-tenant test creates `pgvector_tenant{1,2,3}` on demand mirroring `DocumentStore_IMartenStorage_implementation`. - `Marten.PostGIS` references `Marten.Newtonsoft` — `JsonNetSerializer` moved there in Marten 9. - `TenancyStyle` reference updated to the JasperFx-lifted name (via the global alias in `src/Shared/DedupeAliases.cs`). Wiring: - All four projects added to `src/Marten.slnx` under new `PgVector` / `PostGIS` folders. - `Directory.Packages.props` pins `NetTopologySuite 2.5.0`, `NetTopologySuite.IO.GeoJSON 4.0.0`, `Npgsql.NetTopologySuite 9.0.4`, `Pgvector 0.3.2`. - `build/build.cs` gets `TestPostGIS` + `TestPgVector` targets, both hung off `TestExtensions` (the Nuke schema regenerates to match). Docker: - Replaces `ionx/postgres-plv8:12.8` (Postgres 12 — only PLv8) with a tiny custom Dockerfile at `docker/postgres/Dockerfile` that layers `postgresql-17-postgis-3` + `postgresql-17-pgvector` on the official multi-arch `postgres:17` image. Multi-arch means it works on Apple-silicon hosts. Marten core SQL no longer uses PLv8 (only vestigial `InternalsVisibleTo` declarations remain) so the drop is safe. CI: - New workflow `.github/workflows/on-push-do-ci-build-postgis-pgvector.yml` runs the two extension test suites side-by-side, each against the upstream `postgis/postgis:17-3.5` and `pgvector/pgvector:pg17` images respectively. Existing CI workflows (PG15/PGLatest matrix) are untouched. Docs: - Fresh `docs/postgres/postgis.md` and `docs/postgres/pgvector.md` (there were no existing docs in CritterWatch). Wired into the sidebar in `docs/.vitepress/config.mts`; both pass markdownlint + cspell. `pgvector` and `Ollama` added to `docs/cSpell.json`. Tests were not run locally — the existing dev Postgres container is the old PLv8 image and rebuilding it could disrupt other in-flight work in this environment. CI exercises the suites against the extension-enabled images above. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI failure on PR #4576: one of fourteen PgVector tests (`conjoined_vector_tests.vector_search_respects_tenant_isolation`) failed with `Npgsql.PostgresException 23505: duplicate key value violates unique constraint "pg_extension_name_index" (extname)=(vector)`. Root cause: each PgVector test class spins up its own DocumentStore that calls `UsePgVector()`, registering a `CREATE EXTENSION IF NOT EXISTS vector` schema object. PostgreSQL's `CREATE EXTENSION IF NOT EXISTS` is *not* race-safe — concurrent callers can both pass the pre-existence check before either has inserted into `pg_extension`, and the loser hits `23505` on `pg_extension_name_index`. xUnit's default puts each test class in its own auto-collection and runs collections in parallel, so the four classes raced against themselves on the shared `marten_testing` DB. Fix: add a single `[CollectionDefinition("Marten.PgVector", DisableParallelization = true)]` and apply `[Collection("Marten.PgVector")]` to all four classes — sequential within the suite, still parallel with other (non-PgVector) collections in other assemblies. Also replaces the prior `[Collection("multi-tenancy")]` on the database-per-tenant test, which was a different collection from the others and therefore did not prevent the race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Second CI failure on PR #4576 — `vector_search_respects_tenant_isolation` threw: System.InvalidCastException : Writing values of 'Pgvector.Vector' is not supported for parameters having no NpgsqlDbType or DataTypeName. ---- System.NotSupportedException : Cannot resolve 'vector' to a fully qualified datatype name. The datatype was not found in the current database info. Cause: `b.UseVector()` registers a `Pgvector.Vector → "vector"` mapping on the NpgsqlDataSource, but the data source caches pg_type the first time it opens a connection. When Marten's schema migration creates the `vector` extension *on the same data source* (the typical `UsePgVector()` flow), the cache stays stale and subsequent `Pgvector.Vector` parameter binds throw on OID lookup. Fresh data sources are race-fragile across test classes because of the cache-on-first-connection rule. Fix: bind the vector value as its text form (`[f1,f2,…]`, returned by `Pgvector.Vector.ToString()`) and cast to `vector(N)` server-side (`$1::vector(N)`). Text is a builtin Npgsql type — no extension-type lookup, no cache dependency, race-immune. Applied in all three call sites: `VectorSearchAsync`, `VectorProjectionSearchAsync`, and `VectorProjection`'s embedding upsert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
May 28, 2026
…ths) CI failure on PR #4578: every test that exercises an event append on Quick / QuickWithServerTimestamps modes — including the Daemon tests and the PgVector projection tests on the prior PR #4576 import — failed with: Npgsql.PostgresException : 42601: INSERT has more target columns than expressions Root cause: the previous commit added `bdata` to `EventsTable.SelectColumns` (SELECT position 3) and consequently to the SQL prefix built by `PostgresEventStoreDialect.BuildAppendEventFullColumnsAndPrefix` (used by *every* full-shape INSERT — Rich AND the per-event Quick path). I only wired the bind in `RichAppendEventOperation`. The siblings — `QuickAppendEventWithVersionOperation` (used by RichEventStorage, QuickEventStorage, and QuickWithServerTimestampsEventStorage for per-event INSERTs: tombstone batches, new-stream appends, optimistic- concurrency appends, side-effect replay through EventSlice.BuildOperations) — still emitted the old N parameters against the now-N+1 column list. Fix: thread a `Func<IEvent, byte[]?> SerializeEventBdata` closure through both Quick descriptors and `QuickAppendEventWithVersionOperation`, mirroring the Rich descriptor's existing field. The closure binds `bdata` immediately after `mt_dotnet_type` to match the column-list position. In Quick modes the dialect installs `_ => null` since binary events are rejected at descriptor-build time anyway. This is what was missing from PR #4578's initial commit — the Rich-mode-only constraint covers the *binary opt-in*, but `bdata` is still a column on every row (NULL for JSON events), so every full-shape INSERT has to bind it regardless of mode. Local repro now passes: - DaemonTests.Bug_3059_double_application: 1/1 ✅ (was the canary failure) - Marten.MemoryPack.Tests: 5/5 ✅ (unaffected — binary path was already correct) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
May 28, 2026
… only) (#4578) * Foundation for binary event serialization (#4515 — Phase 1, Rich-mode only) Adds per-event-type binary serialization on the event store side, with JSON-serialized and binary-serialized events coexisting in the same mt_events table. Designed so the feature can be turned on in an existing store with no migration of existing event data. ## Coexistence design Schema is purely additive — one new column on mt_events: data jsonb NOT NULL -- existing; for binary events, holds {} placeholder bdata bytea NULL -- new; the serialized bytes for binary events, -- NULL for JSON-serialized events Per-row discriminator: bdata IS NULL ⇒ JSON path, non-null ⇒ binary. The existing `data NOT NULL` constraint stays intact, so the migration is one nullable column — safe for in-place upgrade. Existing rows in an upgraded store have `bdata = NULL` and continue to read through the JSON path unchanged. ## Public API public interface IEventBinarySerializer { byte[] Serialize(Type type, object data); object Deserialize(Type type, byte[] data); } Two equivalent registration paths: // 1. Attribute-driven (uses opts.Events.DefaultBinarySerializer as resolver) [BinaryEvent] [MemoryPackable] public partial record TripStarted(...); // 2. Fluent (explicit per-type serializer) opts.Events.UseBinarySerializer<TripStarted>(new MemoryPackEventSerializer()); Resolution order on EventMapping construction: 1. Explicit UseBinarySerializer<T>(...) for that type 2. [BinaryEvent] + opts.Events.DefaultBinarySerializer 3. Otherwise, plain JSON (existing path). If `[BinaryEvent]` is set but neither a per-type nor a default serializer is wired, EventMapping throws at construction with a remediation message naming both entry points. ## Surface area touched - src/Marten/Events/{IEventBinarySerializer,BinaryEventAttribute}.cs (new) - src/Marten/Events/Schema/EventBdataColumn.cs (new — bytea nullable column) - src/Marten/Events/Schema/EventsTable.cs (add column; pin at SELECT position 3) - src/Marten/Events/IEventStoreOptions.cs (DefaultBinarySerializer + UseBinarySerializer<T>) - src/Marten/Events/EventGraph.cs (registry + ResolveBinarySerializerFor) - src/Marten/Events/EventMapping.cs (BinarySerializer + IsBinary) - src/Marten/Events/EventDocumentStorage.cs (per-row JSON-vs-binary dispatch in Resolve/ResolveAsync) - src/Marten/EventStorage/Rich/RichAppendEventOperation.cs (bind bdata at column slot 4) - src/Marten/EventStorage/Rich/RichEventStorageDescriptor.cs (SerializeEventBdata closure) - src/Marten/EventStorage/Dialects/PostgresEventStoreDialect.cs (bdata in IsCoreColumn; SerializeEventBdata wiring; Quick-mode guard) - src/Marten/EventStorage/ClosedShapeEventDocumentStorage.cs (Skip(3)→Skip(4); +4 ordinal offset) ## Marten.MemoryPack package - src/Marten.MemoryPack/ — IEventBinarySerializer impl over MemoryPack 1.21.4 - src/Marten.MemoryPack.Tests/ — 5 integration tests (round-trip, multi-event replay, mixed JSON+binary stream, on-disk shape verification, upgrade-backfill against a pre-existing JSON row) Added to Marten.slnx under a `/MemoryPack/` folder; TestMemoryPack target wired into build.cs and hung off TestExtensions; MemoryPack 1.21.4 pinned in Directory.Packages.props. ## Phase 1 scope — explicit limitations (documented in docs/events/binary-serialization.md) - **EventAppendMode.Rich only.** The default `QuickWithServerTimestamps` and `Quick` modes go through the `mt_quick_append_events` PostgreSQL function whose signature would need a parallel `bdata bytea[]` parameter. BuildQuickDescriptor / BuildQuickWithServerTimestampsDescriptor fail loud at store-build time if a binary event type is registered with a non-Rich AppendMode, with the remediation recipe in the exception message. Quick-mode support is the Phase 2 scope. - **No BulkEventAppender support.** Same root cause — the COPY column shape needs the bdata column adding. Follow-up. - **No binary upcaster support.** Marten's JSON upcasters (Marten.Services.Json.Transformations) operate on JSON payloads and don't generalize to byte[]. Binary upcasters need their own typed transform shape; tracked as a separate follow-up issue. ## Docs - docs/events/binary-serialization.md (new page — coexistence design, registration, on-disk shape, migration story, constraints) - docs/events/optimizing.md (link from the scalability/optimization page) - docs/.vitepress/config.mts (sidebar entry under Events) Both pass `markdownlint --disable MD009` and `cspell` locally. ## Test results - Marten.MemoryPack.Tests: 5/5 passing - EventSourcingTests.end_to_end_event_capture_and_fetching: 83/83 passing (regression check — JSON-only events through the dispatched read path) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Bind bdata in QuickAppendEventWithVersionOperation (all Quick/Rich paths) CI failure on PR #4578: every test that exercises an event append on Quick / QuickWithServerTimestamps modes — including the Daemon tests and the PgVector projection tests on the prior PR #4576 import — failed with: Npgsql.PostgresException : 42601: INSERT has more target columns than expressions Root cause: the previous commit added `bdata` to `EventsTable.SelectColumns` (SELECT position 3) and consequently to the SQL prefix built by `PostgresEventStoreDialect.BuildAppendEventFullColumnsAndPrefix` (used by *every* full-shape INSERT — Rich AND the per-event Quick path). I only wired the bind in `RichAppendEventOperation`. The siblings — `QuickAppendEventWithVersionOperation` (used by RichEventStorage, QuickEventStorage, and QuickWithServerTimestampsEventStorage for per-event INSERTs: tombstone batches, new-stream appends, optimistic- concurrency appends, side-effect replay through EventSlice.BuildOperations) — still emitted the old N parameters against the now-N+1 column list. Fix: thread a `Func<IEvent, byte[]?> SerializeEventBdata` closure through both Quick descriptors and `QuickAppendEventWithVersionOperation`, mirroring the Rich descriptor's existing field. The closure binds `bdata` immediately after `mt_dotnet_type` to match the column-list position. In Quick modes the dialect installs `_ => null` since binary events are rejected at descriptor-build time anyway. This is what was missing from PR #4578's initial commit — the Rich-mode-only constraint covers the *binary opt-in*, but `bdata` is still a column on every row (NULL for JSON events), so every full-shape INSERT has to bind it regardless of mode. Local repro now passes: - DaemonTests.Bug_3059_double_application: 1/1 ✅ (was the canary failure) - Marten.MemoryPack.Tests: 5/5 ✅ (unaffected — binary path was already correct) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
May 28, 2026
…arget (#4582) The Nuke `Pack` target in `build/build.cs` lists every project that gets packed and pushed by the `on-manual-do-nuget-publish.yml` workflow. The three new optional companion packages added in PR #4576 (PostGIS / PgVector) and PR #4578 (MemoryPack — binary event serialization) were never added to the list, so the next NuGet release would silently leave them off NuGet. Repacking locally now produces all 9 .nupkgs (was 6): Marten.9.2.1.nupkg Marten.AspNetCore.9.2.1.nupkg Marten.EntityFrameworkCore.9.2.1.nupkg Marten.MemoryPack.9.2.1.nupkg ← new Marten.Newtonsoft.9.2.1.nupkg Marten.NodaTime.9.2.1.nupkg Marten.PgVector.9.2.1.nupkg ← new Marten.PostGIS.9.2.1.nupkg ← new Marten.SourceGenerator.9.2.1.nupkg Gating fix before the 9.3.0 release. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
May 28, 2026
… fix Weasel 9.0.2 (JasperFx/weasel#299) fixes PostgresqlMigrator.executeWithConcurrencyRetryAsync so it reopens a Closed/Broken connection before the retry attempt. That eliminates the recurring "Connection is not open" failure on the conjoined `EventSourcingTests.end_to_end_event_capture_and_fetching_the_stream. query_before_saving` test that hit this PR + #4576 + #4578 + #4582. Bumps Weasel.Postgresql + Weasel.EntityFrameworkCore 9.0.1 → 9.0.2 in Directory.Packages.props (CPM). Marten.MemoryPack.Tests still 8/8 locally on top of the new Weasel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
May 28, 2026
#4584) * #4515 Phase 2: binary event serialization on Quick + BulkEventAppender #4578 shipped the foundation with `Rich` mode only; the Quick paths (QuickWithServerTimestamps default + Quick + bulk COPY) all guarded against binary events at store-build time. This lifts that constraint — binary serialization now works on every EventAppendMode and through the BulkEventAppender. ## Wire-format change: `mt_quick_append_events` grows a `bdatas bytea[]` param The PostgreSQL function used by both Quick variants now accepts a parallel `bdatas bytea[]` parameter right after `bodies jsonb[]`. The INSERT writes `bdatas[index]` into `mt_events.bdata`. For JSON events the array slot is NULL; for binary events `bodies[index]` is the `{}` placeholder and `bdatas[index]` carries the real payload. Same on-disk row shape as the Rich path: `bdata IS NULL` remains the discriminator that the existing read path keys off. Weasel's standard function-diff migration handles the signature change as DROP + CREATE on existing installations; existing JSON rows are untouched. ## Call-site dispatch — same shape as Rich `PostgresEventStoreDialect.BuildQuickDescriptor` and `BuildQuickWithServerTimestampsDescriptor` install the same `serializeEventData` / `serializeEventBdata` closures the Rich descriptor uses (look up `EventMapping`, branch on `IsBinary`). `QuickAppendEventsOperationBase.writeBasicParameters` now accepts an optional `Func<IEvent, byte[]?> serializeEventBdata` and binds the parallel `bdatas bytea[]` array. ## BulkEventAppender — bdata in the COPY column list `buildEventColumns()` adds `bdata` right after `data`; `writeEventRow` looks up the EventMapping per event and writes either the binary payload (for `[BinaryEvent]` types) or NULL (for JSON). The COPY format already supports NULL values per column, so no schema relaxation is needed. ## Removed: AssertNoBinaryEventsForQuickMode The Phase 1 guard in `PostgresEventStoreDialect` that threw at store-build time if a binary event type was registered with a non-Rich AppendMode is gone — no longer needed. ## Tests Three new tests in `QuickModeBinaryEventTests` (separate fixture so each test can dial in its own AppendMode): - `quick_with_server_timestamps_round_trips_binary_events` — mixed binary + JSON stream on the default mode, round-trip via the PG function. - `quick_mode_round_trips_binary_events` — explicit `Quick` mode. - `quick_mode_binary_events_land_in_bdata_column` — on-disk shape verification: binary rows have `data = '{}'` + `bdata != NULL`; JSON rows have `data = real JSON` + `bdata = NULL`. Regression checks: - Full Marten.MemoryPack.Tests suite: 8/8 ✅ - EventSourcingTests.end_to_end_event_capture_and_fetching: 83/83 ✅ - DaemonTests.Bug_3059_double_application: 1/1 ✅ (re-running the test that flushed out the column-count bug in PR #4578's first CI run) ## Docs `docs/events/binary-serialization.md` updated: - Removed the "EventAppendMode.Rich only" + "No bulk appender support" constraints from the Constraints section. - Added a new "Append modes" section explaining the feature works across all three modes + BulkEventAppender. - Quick-start example no longer forces `AppendMode = Rich`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Consume Weasel 9.0.2 — picks up the migration-retry connection-reopen fix Weasel 9.0.2 (JasperFx/weasel#299) fixes PostgresqlMigrator.executeWithConcurrencyRetryAsync so it reopens a Closed/Broken connection before the retry attempt. That eliminates the recurring "Connection is not open" failure on the conjoined `EventSourcingTests.end_to_end_event_capture_and_fetching_the_stream. query_before_saving` test that hit this PR + #4576 + #4578 + #4582. Bumps Weasel.Postgresql + Weasel.EntityFrameworkCore 9.0.1 → 9.0.2 in Directory.Packages.props (CPM). Marten.MemoryPack.Tests still 8/8 locally on top of the new Weasel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Document the versioned-event-types pattern for binary schema evolution Closes #4579 with a docs-only answer rather than building a binary-side upcaster framework. The JSON upcasters (Marten.Services.Json.Transformations) operate on the JSON wire form and don't generalize to a byte[] payload; designing a typed transform shape for binary events is non-trivial and the use case can be addressed end-to-end today by leaning on Marten's existing per-event-type registry. The recommendation: introduce a new event type for each schema change (e.g. TripStarted -> TripStartedV2), have the aggregate handle both versions, and let the coexistence design carry old rows + new rows on the same stream without migration. The only caveat is that MemoryPack's in-place backward-compatible field evolution works for additive-only changes too, but stops at the serializer's tolerance rules (renames, type changes, splits) — versioned event types work for every shape of change and stay explicit. Replaces the "No upcaster support" constraint section with a "Schema evolution — use versioned event types" section that gives the recommended pattern with code samples + a sub-section on the "why-not-in-place" tradeoff + a note on mixing binary + JSON. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 29, 2026
Open
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.
Imports the four PostGIS / pgvector projects originally prototyped under
~/code/critterwatch/tools/into Marten itself, to be shipped under Marten's MIT license. No removal from CritterWatch is included in this PR (per instruction — that'll be a follow-up after the NuGets ship).What lands
Two new library projects
src/Marten.PostGISUsePostGIS()opt-in that enables thepostgisextension on every database Marten manages (multi-tenant aware), wires NetTopologySuite + GeoJSON serialization, and exposes four query helpers —NearestToAsync,WithinDistanceAsync,ContainingAsync,IntersectingAsync.src/Marten.PgVectorUsePgVector()opt-in that enables thevectorextension on every database (also addresses #2515).VectorSearchAsyncfor similarity queries, plus an embedding-awareVectorProjectionbase class with content-hash skipping. AI-model-agnostic (IEmbeddingProvider).Both follow Marten's
Marten.NodaTimecsproj template — multi-target net9.0;net10.0, CPM,GenerateAssembly*attrs,RootNamespace, etc.Test projects
src/Marten.PostGIS.Tests— single class covering extension creation + the four query helpers.src/Marten.PgVector.Tests— four classes: single-tenancy column, conjoined multi-tenancy, database-per-tenant (verifies Installing an extension that defines extra types results in types not being found #2515 in each tenant DB), and the embeddingVectorProjection(including the content-hash skip path).Tests use
Marten.Testing.Harness.ConnectionSource(port 5432,marten_testingDB) rather than CritterWatch's local5442/postgres. The database-per-tenant test createspgvector_tenant{1,2,3}on demand following the same pattern asDocumentStore_IMartenStorage_implementation.Wiring
src/Marten.slnxunder newPgVector/PostGISsolution folders.Directory.Packages.propspins:NetTopologySuite 2.5.0,NetTopologySuite.IO.GeoJSON 4.0.0,Npgsql.NetTopologySuite 9.0.4,Pgvector 0.3.2.build/build.csaddsTestPostGIS+TestPgVectortargets, both hung offTestExtensions. Nuke's.nuke/build.schema.jsonregenerates to match.Docker
Replaces the legacy
ionx/postgres-plv8:12.8(Postgres 12 — PLv8 only) image with a tiny customdocker/postgres/Dockerfilethat layerspostgresql-17-postgis-3+postgresql-17-pgvectoron the official multi-archpostgres:17image. Works on Apple-silicon hosts. Marten core SQL no longer requires PLv8 — only vestigialInternalsVisibleTo("Marten.PLv8")declarations remain inAssemblyInfo.cs— so the drop is safe.CI
New workflow
.github/workflows/on-push-do-ci-build-postgis-pgvector.ymlruns the two extension test suites side-by-side, each against the upstream image that ships the corresponding extension:postgis/postgis:17-3.5for the PostGIS suitepgvector/pgvector:pg17for the PgVector suiteExisting CI workflows (PG15/PGLatest matrix) are untouched.
Docs
There were no existing PostGIS/PgVector docs in CritterWatch's
docs/, so this PR includes fresh VitePress pages authored from the imported code & tests:docs/postgres/postgis.mddocs/postgres/pgvector.mdBoth wired into the sidebar in
docs/.vitepress/config.mts. Both passmarkdownlint --disable MD009andcspell --config ./docs/cSpell.jsonlocally (matching thedocs-prs.ymlCI invocations).pgvectorandOllamaadded todocs/cSpell.json.Notes for the reviewer
TenancyStylereference inconjoined_vector_tests.csupdated fromMarten.Storage.TenancyStyleto the JasperFx-lifted name, picked up via the existing global alias insrc/Shared/DedupeAliases.cs.Marten.PostGISreferencesMarten.NewtonsoftbecauseJsonNetSerializermoved there in Marten 9. The PostGISUsePostGIS()registers the NTS GeoJSON converters on the Newtonsoft serializer.Directory.Build.propsstill says 9.2.1, matching master. Bump and release as a separate step once these compile and pass on CI.🤖 Generated with Claude Code