Capture Put/Delete/Increment into StorageOpCollector#254
Merged
Conversation
0218576 to
ea7fea9
Compare
em3s
added a commit
that referenced
this pull request
Apr 22, 2026
Flip the newly-added `context` field on mutation responses from non-null `emptyMap()` to nullable + `@JsonInclude(NON_NULL)` so the wire format is byte-identical for existing callers. The key only appears once a caller actually populates the map, see #254. - `core.MutationResult.context: Map<String, Any?>? = null` - V3 `EdgeMutationResponse.Item` / `MultiEdgeMutationResponse.Item`: nullable + `@field:JsonInclude(Include.NON_NULL)` - V2 `engine.edge.MutationResultItem`: same - Revert the `"context":{}` additions to `MutationServiceAsyncSpec`, `MutationServiceSystemAsyncSpec`, `MultiEdgeSpec` — existing JSON expectations no longer need to change Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
em3s
added a commit
that referenced
this pull request
Apr 22, 2026
Add opt-in control over the `context` slot via an `AB-Include-Mutation-Context: true` request header. When set, the server attaches an empty `context` map to each mutation response item; without the header nothing changes on the wire. #254 can later populate the map with storage-op details. - `ServerWebExchangeContextFilter` writes the current `ServerWebExchange` into the Reactor Context (Spring Boot 3.5 no longer does this automatically) so downstream operators can read request headers without threading `exchange` through controller signatures - `mapToResponseEntity()` reads the header via `Mono.deferContextual`; when the flag is set it enriches V2 `MutationResult`, V3 `EdgeMutationResponse`, and V3 `MultiEdgeMutationResponse` items with `context = emptyMap()` - V3 `EdgeMutationController` / `MultiEdgeMutationController` routed through `mapToResponseEntity()` for consistency with V2 (previously they wrapped `ResponseEntity.ok(...)` directly and bypassed the helper) - New tests: - `MutationContextOptInE2ETest` — V3 edge + multi-edge sync endpoints, header on/off via `WebTestClient`, case-insensitive header values - `ResponseEntityExtensionsTest` — unit coverage for V2 and V3 response bodies, pass-through for non-mutation bodies Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
e2a8caf to
95ccab3
Compare
em3s
added a commit
that referenced
this pull request
Apr 23, 2026
Query payloads (`EdgePayload`, `EdgeCountPayload`, `EdgeAggPayload`) already carry a `context: Map<String, Any?>` slot for per-item metadata; add the same slot to mutation payloads. Nullable with `@JsonInclude(NON_NULL)` — existing callers see byte-identical responses. The key is only emitted once a caller populates it (see #259, #254). - `core.MutationResult.context: Map<String, Any?>? = null` - V3 `EdgeMutationResponse.Item` and `MultiEdgeMutationResponse.Item`: nullable + `@field:JsonInclude(Include.NON_NULL)` - V2 `engine.edge.MutationResultItem`: same - `from(...)` factories thread `MutationResult.context` through Tests (ObjectSource-driven, one class per Item type): - `EdgeMutationResponseItemContextTest` (core) — V3 edge item - `MultiEdgeMutationResponseItemContextTest` (core) — V3 multi-edge item - `MutationResultItemContextTest` (engine) — V2 mutation result item Each class has two parameterized methods (`Item serializes`, `Item deserializes`) whose YAML cases list input and expected JSON/context — makes intent readable from the test data alone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95ccab3 to
f3389bc
Compare
em3s
added a commit
that referenced
this pull request
Apr 23, 2026
Add opt-in control over the `context` slot via an `AB-Include-Mutation-Context: true` request header. When set, the server attaches an empty `context` map to each mutation response item; without the header nothing changes on the wire. #254 can later populate the map with storage-op details. - `ServerWebExchangeContextFilter` writes the current `ServerWebExchange` into the Reactor Context (Spring Boot 3.5 no longer does this automatically) so downstream operators can read request headers without threading `exchange` through controller signatures - `mapToResponseEntity()` reads the header via `Mono.deferContextual`; when the flag is set it enriches V2 `MutationResult`, V3 `EdgeMutationResponse`, and V3 `MultiEdgeMutationResponse` items with `context = emptyMap()` - V3 `EdgeMutationController` / `MultiEdgeMutationController` routed through `mapToResponseEntity()` for consistency with V2 (previously they wrapped `ResponseEntity.ok(...)` directly and bypassed the helper) - New tests: - `MutationContextOptInE2ETest` — V3 edge + multi-edge sync endpoints, header on/off via `WebTestClient`, case-insensitive header values - `ResponseEntityExtensionsTest` — unit coverage for V2 and V3 response bodies, pass-through for non-mutation bodies Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
5a50dd7 to
e0bcd22
Compare
914f2b8 to
00e89fa
Compare
Put/Delete/Increment into StorageOpCollector
c0a533c to
b69f3e5
Compare
c511f2e to
fbbb51f
Compare
73d76ca to
fa01dec
Compare
bac87c2 to
00c5a22
Compare
Contributor
Author
|
This PR went through several iterations — split across
— before landing here. |
2c2994a to
628e549
Compare
Closes #253. Follows #258, #259, #261. Implements `StorageOpCollector.collect` so it decodes raw HBase requests into the `StorageOp` sealed type. The scaffolded marker is extended with the backend-facing shape: `Put` / `Delete` / `Increment` data classes with `Cell` / `Delta` nested types, base64-encoded row / family / qualifier / value. No call-site changes — the plumbing was done in #261. When the `AB-Include-Mutation-Context` header is set, mutation responses include the captured ops under `context["storageOps"]`. When more than `DEFAULT_MAX_OPS` (1024) ops would be collected, the collector sets `context["storageOpsTruncated"] = true` so clients can tell a complete snapshot from a silently truncated one. `StorageOp.Cell.type` captures the HBase `Cell.Type` name (`Put`, `DeleteColumn`, `DeleteFamily`, `DeleteFamilyVersion`, `Delete`) so shadow tests can distinguish delete subtypes. Unknown request types become `StorageOp.Unknown(type = qualified class name)` with a one-shot `warn`-level log per type, so new backends can join without silent drops. Tests: - `StorageOpCollectorTest` — data-driven via `@ObjectSource` (Put / Delete-full-row / Delete-columns / Delete-family / Increment / multi-byte) + `@Test` for multi-CF Put, non-Mutation fallthrough, `Append` → `Unknown`, truncation cap, `toContextMap` flag - `CdcContextSerializationTest` — `storageOps` and `storageOpsTruncated` excluded from CDC - `V2EdgeStorageOpsSpec` — V2 `/graph/v2/edge` e2e with header on - `EdgeMutationStorageOpsSpec` — V3 edge + multi-edge e2e with header on, asserts `context.storageOps[0].row` / cells shape Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
628e549 to
2031cd6
Compare
Contributor
Author
|
Optimistic Merge. |
em3s
added a commit
that referenced
this pull request
Apr 23, 2026
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
Closes #253.
StorageOpCollector.collect(...)now decodes HBasePut/Delete/Incrementinto base64-encodedStorageOp. No call-site changes.Set the
AB-Include-Mutation-Context: trueheader on a mutation request to receive the captured ops undercontext["storageOps"]in the response. When the captured set exceeds theDEFAULT_MAX_OPScap,context["storageOpsTruncated"] = trueis added so clients can tell a truncated snapshot from a complete one. Without the header the wire format is unchanged.Changes
StorageOp— addsPut/Delete/Increment/Unknowndata classes;Cellcarriestype(Put,DeleteColumn,DeleteFamily,DeleteFamilyVersion,Delete) so delete subtypes are observableStorageOpCollector.collect—whenonPut/Delete/Increment; base64-encode row/family/qualifier/value viaByteBuffer.wrapto avoid an extra cell clone; unknown request types becomeStorageOp.Unknownwith a one-shot per-typewarnlogStorageOpCollector.toContextMap()— single source of thestorageOps/storageOpsTruncatedshape, consumed by V3MutationServiceand V2GraphCdcContext.storageOpsTruncated— carries the flag across the V2 pipeline;@JsonIgnorekeeps it out of CDCHow to Test
./gradlew buildStorageOpCollectorTest— round-trip per op type, multi-CF Put, multi-byte value, truncation,toContextMapflag,Append→Unknown, non-MutationfallthroughCdcContextSerializationTest—storageOpsandstorageOpsTruncatedexcluded from CDCV2EdgeStorageOpsSpec— V2/graph/v2/edgewith headerEdgeMutationStorageOpsSpec— V3 edge + multi-edge,context.storageOps[0]assertions