Skip to content

Fix #4526/#4528: revert IRevisioned to int + add ILongVersioned (long)#4533

Merged
jeremydmiller merged 1 commit into
masterfrom
feat/4526-ilongversioned
May 20, 2026
Merged

Fix #4526/#4528: revert IRevisioned to int + add ILongVersioned (long)#4533
jeremydmiller merged 1 commit into
masterfrom
feat/4526-ilongversioned

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Summary

Marten 9.0-alpha had forked IRevisioned to a long Version. Upstream #348 keeps the canonical JasperFx.IRevisioned at int (matching Marten 8) and introduces a new JasperFx.ILongVersioned (long Version) for documents projected from a MultiStreamProjection whose Version is the global event sequence number and can therefore exceed Int32.MaxValue. This PR consumes both and keeps the bigint mt_version column for everyone.

This is the focused follow-up that was split out of the dedupe-consumption work (foundation landed in #4529; coordinator/hilo refactors in #4532).

Changes

  • Aliases — delete the forked Marten.Metadata.IRevisioned (long); alias IRevisioned -> JasperFx.IRevisioned (int) and ILongVersioned -> JasperFx.ILongVersioned (long) in the shared dedupe file.
  • VersionedPolicy recognizes both IRevisioned and ILongVersioned -> UseNumericRevisions + maps Metadata.Revision.Member.
  • MetadataColumn<T> gains a virtual IsAcceptableMemberType hook; RevisionColumn overrides it to accept int (IRevisioned) or long (ILongVersioned). The column stays bigint.
  • DocumentRevisionBinder builds a member-type-aware setter: an int member downcasts the loaded long; a long member is assigned directly.
  • DocumentSessionBase.storeEntity gains an ILongVersioned case beside IRevisioned; revision flows as long internally (int upcasts).
  • Tests/examples — the single-stream revision-counter docs that had adopted the alpha long signature revert to int Version. [Version]-attribute docs stay long.
  • New regression test long_versioned_revisioning — an ILongVersioned doc whose Version > Int32.MaxValue round-trips through the bigint column without truncation.
  • Docsconcurrency.md documents IRevisioned (int) vs ILongVersioned (long) and the int-overflow caveat for MultiStreamProjection-derived documents.

Test plan

  • Full solution builds clean (net9/net10), 0 errors.
  • DocumentDbTests revisioning suite (long_versioned_revisioning + numeric_revisioning): 24 passed / 0 failed.
  • markdownlint + cspell clean on docs/documents/concurrency.md.

Closes #4526
Closes #4528
Refs #348, #4529, #4532

🤖 Generated with Claude Code

…d (long) support

Marten 9.0-alpha forked IRevisioned to long Version. #348 keeps the
canonical JasperFx.IRevisioned at int and adds JasperFx.ILongVersioned (long) for
documents projected from a MultiStreamProjection whose Version is the global
event sequence number. This consumes both and keeps the bigint mt_version column.

Production:
- Delete Marten.Metadata.IRevisioned (long); alias IRevisioned -> JasperFx.IRevisioned
  (int) and ILongVersioned -> JasperFx.ILongVersioned (long) in the shared dedupe file.
- VersionedPolicy recognizes both IRevisioned and ILongVersioned -> UseNumericRevisions,
  mapping Metadata.Revision.Member.
- MetadataColumn<T>.Member gains a virtual IsAcceptableMemberType hook; RevisionColumn
  overrides it to accept int (IRevisioned) or long (ILongVersioned). Column stays bigint.
- DocumentRevisionBinder builds a member-type-aware setter: for an int member it
  downcasts the loaded long (documented overflow caveat for huge event sequences);
  for a long member it assigns directly.
- DocumentSessionBase.storeEntity gains an ILongVersioned case beside IRevisioned;
  revision flows as long internally (int upcasts).

Tests/examples: the revision-counter docs that had adopted the alpha long signature
revert to int Version (all are single-stream revision docs). Added
long_versioned_revisioning with an ILongVersioned doc whose Version > Int32.MaxValue
round-trips through the bigint column without truncation.

Docs: concurrency.md documents IRevisioned (int) vs ILongVersioned (long) and the
int-overflow caveat for MultiStreamProjection-derived documents.

Full solution builds clean (net9/net10). Revision/concurrency tests: DocumentDbTests
numeric + long revisioning 24/0; EventSourcing FetchForWriting/compacting/last-good 227/0.

Closes #4526 and #4528.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 86d9d18 into master May 20, 2026
6 checks passed
@jeremydmiller jeremydmiller deleted the feat/4526-ilongversioned branch May 20, 2026 18:12
jeremydmiller added a commit that referenced this pull request May 20, 2026
… (#4534)

* Marten docs: rewrite stale IRevisioned int->long migration-guide section for the #4533 revert

An early 9.0 alpha widened IRevisioned.Version to long; #4533 reverted that
before rc.1 (IRevisioned stays int, V8-compatible) and added ILongVersioned
(long) for MultiStreamProjection documents whose Version is the global event
sequence number. The migration guide still told users to widen Version to long.

Rewrite the section to lead with the reversal, introduce ILongVersioned with the
Int32-overflow rationale, and replace the widen-to-long example with a correct
IRevisioned(int)/ILongVersioned(long) pair. Every Before|After type in the table
was verified against origin/master source: IRevisioned.Version is int (unchanged);
DocumentMetadata.CurrentRevision, UpdateRevision/TryUpdateRevision parameters,
IRevisionedOperation.Revision and the m.Revision metadata column are long (and
source-compatible via int->long). Dropped the stale claim that MapTo to an int
property throws (RevisionColumn now accepts int or long). References updated to
#4533 / #4526 / #4528 / #348; #3733 / #4377 are historical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Marten docs: regenerate concurrency.md sample_versioned_reservation snippet (int Version)

The embedded sample_versioned_reservation snippet still showed long Version even
though its source (src/Marten.Testing/Examples/RevisionedDocuments.cs) was already
reverted to int by #4533. Regenerated via mdsnippets so the embed matches source.

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 Jun 3, 2026
Marten 9 widened the mt_version column from `integer` to `bigint` for every
projected document (#3733), then partially reverted in #4533 — the .NET
IRevisioned.Version went back to int, but the column itself stayed bigint.
Result: V8 → V9 upgrades silently migrate SingleStreamProjection aggregate
tables from `integer` to `bigint`, even though their version (the per-stream
event count) comfortably fits Int32 and was the V8 default.

This restores the V8 column width for IRevisioned-backed documents while
preserving the bigint column for the legitimate ILongVersioned shape that
MultiStreamProjection-projected documents use (Version is the global event
sequence number; can exceed Int32 under high event volume).

Discriminator: the document type's interface choice, not the projection kind.
IRevisioned → integer column; ILongVersioned → bigint column. The Marten
projection registration paths already steer users toward the right interface
(SingleStream-projected aggregates typically implement IRevisioned;
MultiStream-projected docs implement ILongVersioned), so no public knob is
introduced — the variant is policy-driven via VersionedPolicy.

Migration semantics are non-destructive in both directions:
  - V8 integer + IRevisioned (desired integer)   → no-op  (the headline fix)
  - V8 integer + ILongVersioned (desired bigint) → widen  (existing path preserved)
  - 9.x bigint + IRevisioned (desired integer)   → no-op  (tolerate; no force-narrow)
  - 9.x bigint + ILongVersioned (desired bigint) → no-op
  - uuid + numeric revisions                     → drop + recreate as desired width

Refusing to narrow bigint → integer is the safety call: Postgres won't verify
without scanning the table, and a `USING mt_version::integer` cast would
silently truncate any out-of-range value. Documents whose interface is
IRevisioned are practically bounded by Int32, but a wider on-disk column is
harmless — leave it alone.

Implementation:
  - RevisionColumnInt32 sibling of RevisionColumn — `integer` column, integer
    parameter type, integer-aware migration policy.
  - RevisionArgumentInt32 sibling of RevisionArgument for the codegen path.
  - DocumentMetadataCollection.Revision gets an internal setter so
    VersionedPolicy can swap to the Int32 variant when IRevisioned is detected.
  - DocumentRevisionBinder accepts a column NpgsqlDbType so its BindParameter
    and WriteToBulkAsync emit `integer`/`bigint` to match the column. Read
    side stays as long — Npgsql widens int→long via the field-value handler.
  - ClosedShape{Insert,Update,Upsert}Operation: revision parameter slots now
    bind with the binder's ColumnDbType (integer for IRevisioned, bigint for
    ILongVersioned) so the CASE-in-VALUES expression's branches type-align
    with the target column. Postgres won't auto-narrow on the VALUES strict
    path, so this match has to be exact.
  - DocumentTable.SelectColumns also matches RevisionColumnInt32 explicitly
    (it's a sibling, not a subclass of RevisionColumn) so the version slot
    stays canonical in the SELECT projection.

Tests (8 in CoreTests/Bugs/Bug_4614_revision_column_int_for_IRevisioned.cs):
  - Fresh IRevisioned table → integer column.
  - Fresh ILongVersioned table → bigint column.
  - V8 integer + IRevisioned → no migration (headline regression).
  - V8 integer + ILongVersioned → widens to bigint (legitimate path preserved).
  - 9.x bigint + IRevisioned → tolerated, no force-narrow.
  - IRevisioned round-trip insert + update + read on integer column.
  - ILongVersioned round-trip insert + update + read on bigint column.
  - SingleStreamProjection<TIRevisionedDoc> end-to-end through the inline
    projection path on the integer column.

Regression sweeps clean: CoreTests (424 passed), DocumentDbTests (999 passed),
EventSourcingTests (1358 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request Jun 3, 2026
Marten 9 widened the mt_version column from `integer` to `bigint` for every
projected document (#3733), then partially reverted in #4533 — the .NET
IRevisioned.Version went back to int, but the column itself stayed bigint.
Result: V8 → V9 upgrades silently migrate SingleStreamProjection aggregate
tables from `integer` to `bigint`, even though their version (the per-stream
event count) comfortably fits Int32 and was the V8 default.

This restores the V8 column width for IRevisioned-backed documents while
preserving the bigint column for the legitimate ILongVersioned shape that
MultiStreamProjection-projected documents use (Version is the global event
sequence number; can exceed Int32 under high event volume).

Discriminator: the document type's interface choice, not the projection kind.
IRevisioned → integer column; ILongVersioned → bigint column. The Marten
projection registration paths already steer users toward the right interface
(SingleStream-projected aggregates typically implement IRevisioned;
MultiStream-projected docs implement ILongVersioned), so no public knob is
introduced — the variant is policy-driven via VersionedPolicy.

Migration semantics are non-destructive in both directions:
  - V8 integer + IRevisioned (desired integer)   → no-op  (the headline fix)
  - V8 integer + ILongVersioned (desired bigint) → widen  (existing path preserved)
  - 9.x bigint + IRevisioned (desired integer)   → no-op  (tolerate; no force-narrow)
  - 9.x bigint + ILongVersioned (desired bigint) → no-op
  - uuid + numeric revisions                     → drop + recreate as desired width

Refusing to narrow bigint → integer is the safety call: Postgres won't verify
without scanning the table, and a `USING mt_version::integer` cast would
silently truncate any out-of-range value. Documents whose interface is
IRevisioned are practically bounded by Int32, but a wider on-disk column is
harmless — leave it alone.

Implementation:
  - RevisionColumnInt32 sibling of RevisionColumn — `integer` column, integer
    parameter type, integer-aware migration policy.
  - RevisionArgumentInt32 sibling of RevisionArgument for the codegen path.
  - DocumentMetadataCollection.Revision gets an internal setter so
    VersionedPolicy can swap to the Int32 variant when IRevisioned is detected.
  - DocumentRevisionBinder accepts a column NpgsqlDbType so its BindParameter
    and WriteToBulkAsync emit `integer`/`bigint` to match the column. Read
    side stays as long — Npgsql widens int→long via the field-value handler.
  - ClosedShape{Insert,Update,Upsert}Operation: revision parameter slots now
    bind with the binder's ColumnDbType (integer for IRevisioned, bigint for
    ILongVersioned) so the CASE-in-VALUES expression's branches type-align
    with the target column. Postgres won't auto-narrow on the VALUES strict
    path, so this match has to be exact.
  - DocumentTable.SelectColumns also matches RevisionColumnInt32 explicitly
    (it's a sibling, not a subclass of RevisionColumn) so the version slot
    stays canonical in the SELECT projection.

Tests (8 in CoreTests/Bugs/Bug_4614_revision_column_int_for_IRevisioned.cs):
  - Fresh IRevisioned table → integer column.
  - Fresh ILongVersioned table → bigint column.
  - V8 integer + IRevisioned → no migration (headline regression).
  - V8 integer + ILongVersioned → widens to bigint (legitimate path preserved).
  - 9.x bigint + IRevisioned → tolerated, no force-narrow.
  - IRevisioned round-trip insert + update + read on integer column.
  - ILongVersioned round-trip insert + update + read on bigint column.
  - SingleStreamProjection<TIRevisionedDoc> end-to-end through the inline
    projection path on the integer column.

Regression sweeps clean: CoreTests (424 passed), DocumentDbTests (999 passed),
EventSourcingTests (1358 passed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant