Skip to content

Marten 9.30.0

Latest

Choose a tag to compare

@jeremydmiller jeremydmiller released this 26 Aug 18:11
· 1 commit to master since this release

Dependencies move to Weasel 9.27.0 and JasperFx 2.56.0.

Several fixes in this release share a failure mode worth calling out on its own: the write succeeded, the read disagreed, and nothing anywhere reported an error. Two of them ran undetected in production for weeks.

Patching

  • #5290 — the patching API built its JSON paths from raw C# member names, so a member carrying [JsonPropertyName] (or [JsonProperty] under Newtonsoft) was patched at a path the serializer never reads. The patch reported success, Load and Query kept returning the old value, and the phantom node was erased by the next full save — so there was no durable evidence anything had gone wrong. Patch paths now resolve through the same member machinery the LINQ provider uses, so Where(x => x.Name == v) and Patch(...).Set(x => x.Name, v) agree about where a member lives by construction. The predicate overloads had the same gap and are fixed with it.
  • #5295 — a patch that moves a value the schema also keeps in a .Duplicate() column now refreshes that column in more of the cases that need it: an aliased member, a patch on a parent of the duplicated member, and the destinations of the patching API's own Duplicate and Rename operations. Left unfixed, the document and the index that exists to search it disagreed — Load returned the new value while Query filtered on the same member returned nothing.

DCB

  • #5268Events.BuildHStoreTagIndexConcurrently builds the hstore tag index without holding ACCESS EXCLUSIVE on mt_events for the duration. On an ordinary event table that is CREATE INDEX CONCURRENTLY; under Events.UseTenantPartitionedEvents, where PostgreSQL refuses CONCURRENTLY on a partitioned parent outright, Marten emits the per-partition sequence it does accept. Opt-in, because a concurrent build cannot run inside a transaction and so changes what db-patch and db-dump write out. The out-of-band route from 9.29.0 (Events.IgnoreIndex(EventGraph.HStoreTagIndexName)) still works and is still supported.
  • #5276 (thanks @Noblix) — a session that fetched a DCB boundary, decided it had nothing to append, and saved anyway still bumped the mt_dcb_tag_version rows, invalidating a concurrent session that did have something to append. A boundary is now only enforced when the save actually appends. #5280 covers the repeated-fetch half: each row is asserted exactly once, oldest capture wins.
  • #5283 / #5286 — tag rules. Events.TagWith<T>(...) states once how an event is tagged and applies it wherever the event is built — ordinary appends, StartStream, aggregate handlers and bulk inserts alike — closing the gaps tag inference leaves open. Events.TagEventsBy(...) hands over a translator an application already owns instead of restating it one registration at a time.

LINQ

  • #5279 (thanks @svenclaesson) — two enum value-collection filters that silently returned zero rows. x.EnumArray.Contains(variable) resolved to the MemoryExtensions.Contains comparer overload rather than Enumerable.Contains, because enums do not implement IEquatable<T>; with ImplicitUsings on, most applications hit this by default.
  • #5287 — a HashSet<TEnum> in a Contains filter now projects to something Npgsql can bind.
  • #5289 (thanks @arnelirobles) — the parameterised MatchesJsonPath(sql, params object[]) overload threw for every call. It now works for any argument type, maps null onto DBNull, and reports a placeholder/parameter count mismatch with a message naming both counts instead of an IndexOutOfRangeException from inside the LINQ provider. Worth knowing: ^ is the placeholder character for that overload and is also a regex anchor, so it lands inside JSONPath literals by accident.

Async daemon

  • #5277 — the skip-ahead probe asked for min(seq_id) over a join, and PostgreSQL only rewrites MIN into an ordered index scan when the aggregate's input is a single relation. The probe therefore scanned every remaining row in the partition, on the one code path that exists precisely because the store is too large for the normal query to finish.
  • JasperFx 2.56.0 brings three high-water fixes reachable under load, including one from a deployment running 2,173 tenants across 512 shard databases (thanks @erdtsieck): a tenant agent seeded below its own committed position no longer pauses permanently, high water is primed exactly once so concurrent agent starts cannot read it at zero, and per-tenant catch-up no longer silently skips a tenant a concurrent reconciliation dropped. The middle one is not tenancy-specific — it is the shape behind an unexplained Paused shard after a rollout.