Skip to content

StreamOne<T> ETag: inline mt_version (eliminate 2nd round trip) + reverse where T:notnull + ETag test hardening (pre-9.18, follow-up to #5015) #5027

Description

@jeremydmiller

Follow-up to #5015 (ETag / If-None-Match support). To be done before the 9.18 release.

Problem

StreamOne<T> currently computes its ETag by, after the document JSON is already fetched and buffered:

  1. deserializing the buffered JSON back into a T (session.Serializer.FromJson<T>(stream)), then
  2. issuing a second DB round tripIQuerySession.MetadataForAsync<T>(entity) — purely to read mt_version.

So with EmitETag = true (the default), every StreamOne request does two DB round trips instead of one, and a 304 saves only bandwidth — the document was already fetched. (This is unlike StreamAggregate, where FetchStreamStateAsync reads the version cheaply before the expensive fold, so a 304 skips real work.)

This second call is also the reason StreamOne<T>/WriteSingle<T> had to tighten to where T : notnull (because MetadataForAsync<T> is constrained notnull).

Goal

Fetch mt_version inline with the document in the original single round trip — same idea as the count(*) OVER() as total_rows stats-column trick that #5014 uses — so:

  • StreamOne stays at one DB round trip with EmitETag = true on by default (which the maintainer has OK'd once this lands), and
  • the second MetadataForAsync call is removed entirely.

Sketch

  • Have the StreamOne streaming path select mt_version alongside the data column (extend the Marten core streaming query used by WriteSingle, analogous to how StatsSelectClause appends count(*) OVER()), and read the version off the same DbDataReader before/while streaming the body — no re-deserialization, no metadata query.
  • Once the version no longer comes through MetadataForAsync<T>, the where T : notnull constraint on the public StreamOne<T>/WriteSingle<T> signatures can be dropped cleanly (see the separate constraint-reversal that ships in feat: ETag / If-None-Match (304) support on StreamOne and StreamAggregate (fixes #5010) #5015 itself).
  • Guard the mt_version-disabled case: if the doc type has version metadata turned off, skip emitting an ETag rather than emitting a constant "00000000-…" (which would risk false 304s). Confirm what the inline read returns in that config.

Acceptance


Also in scope for this follow-up (folded in from #5015 review)

Reverse the breaking constraint tightening. #5015 shipped where T : notnull on the public StreamOne<T> and WriteSingle<T> signatures — a source-compatibility break — solely because IQuerySession.MetadataForAsync<T> is constrained where T : notnull. Once this issue removes the MetadataForAsync call in favor of the inline mt_version read, that constraint is no longer needed and must be dropped from both StreamOne<T> and WriteSingle<T> to restore the pre-9.18 signature. (Loosening a constraint is non-breaking, and this ships before the 9.18 release, so the tightened form never reaches a release.)

Add the ETag test cases identified in review (additive; can land with this work or separately, but before release):

  • ETagHelpers unit coverage for the logic branches: * wildcard, W/ weak validator stripping, and multi-value comma-separated If-None-Match lists.
  • The 404 path (missing document for StreamOne, missing stream for StreamAggregate).
  • The version-metadata-disabled / no-version-column case → assert no ETag header is emitted (guards against a constant "00000000-…" ETag causing false 304s).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions