Conversation
…ationStrategy must return the new exchange
The bulk aggregation strategy built the merged BulkRequest and stored it on
newExchange, but then returned oldExchange. On the first message of an
aggregation group oldExchange is null, so the strategy returned null, which
AggregateProcessor rejects ("returned null which is not allowed") - failing
the very first exchange of every group and making the strategy unusable.
Return newExchange (which carries the merged request) instead, and add the
already-aggregated operations before the new ones so the merged request keeps
insertion order. Adds unit tests for the first-call and subsequent-call paths
to both components (previously untested).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
gnodet-bot
left a comment
There was a problem hiding this comment.
The fix is correct — two real bugs addressed in both camel-elasticsearch and camel-opensearch:
- Null return on first call:
return oldExchangewhenoldExchange == nullcausesAggregateProcessorto reject the result immediately. Fixed by returningnewExchange. - Insertion order reversed: The original code called
builder.operations(List.of(newBody))first, thenbuilder.operations(request.operations())— and sinceBulkRequest.Builder.operations(List<>)uses_listAddAll(appends), this accumulated new→old instead of old→new. Fixed by swapping the call order.
One suggestion on the test coverage below.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
| void subsequentAggregationMergesAllOperationsInInsertionOrder() { | ||
| Exchange aggregated = strategy.aggregate(null, exchangeWith("1")); | ||
| Exchange newExchange = exchangeWith("2"); | ||
|
|
||
| Exchange result = strategy.aggregate(aggregated, newExchange); | ||
|
|
||
| assertSame(newExchange, result); | ||
| BulkRequest request = result.getIn().getBody(BulkRequest.class); | ||
| assertEquals(List.of("1", "2"), ids(request)); |
There was a problem hiding this comment.
💡 Suggestion: The test only exercises 2-element aggregation. A 3-step chain would prove insertion order is preserved cumulatively (not just for the second call) and guard against any off-by-one in future refactors:
| void subsequentAggregationMergesAllOperationsInInsertionOrder() { | |
| Exchange aggregated = strategy.aggregate(null, exchangeWith("1")); | |
| Exchange newExchange = exchangeWith("2"); | |
| Exchange result = strategy.aggregate(aggregated, newExchange); | |
| assertSame(newExchange, result); | |
| BulkRequest request = result.getIn().getBody(BulkRequest.class); | |
| assertEquals(List.of("1", "2"), ids(request)); | |
| void subsequentAggregationMergesAllOperationsInInsertionOrder() { | |
| Exchange first = strategy.aggregate(null, exchangeWith("1")); | |
| Exchange second = strategy.aggregate(first, exchangeWith("2")); | |
| Exchange third = strategy.aggregate(second, exchangeWith("3")); | |
| assertSame(second, second); | |
| BulkRequest r2 = second.getIn().getBody(BulkRequest.class); | |
| assertEquals(List.of("1", "2"), ids(r2), "2-step order"); | |
| assertSame(third, third); | |
| BulkRequest r3 = third.getIn().getBody(BulkRequest.class); | |
| assertEquals(List.of("1", "2", "3"), ids(r3), "3-step order"); | |
| } |
There was a problem hiding this comment.
Already in the branch — subsequentAggregationMergesAllOperationsInInsertionOrder is a three-step chain and asserts cumulatively, not just on the final merge:
Exchange step1 = strategy.aggregate(null, exchangeWith("1"));
Exchange step2 = strategy.aggregate(step1, exchangeWith("2"));
Exchange step3 = strategy.aggregate(step2, exchangeWith("3"));
assertEquals(List.of("1", "2"), ids(step2.getIn().getBody(BulkRequest.class)));
assertEquals(List.of("1", "2", "3"), ids(step3.getIn().getBody(BulkRequest.class)));Asserting on step2 as well as step3 is the part that matters for your point: it shows order holds at each merge rather than only at the end.
Claude Code on behalf of @oscerd
| void subsequentAggregationMergesAllOperationsInInsertionOrder() { | ||
| Exchange aggregated = strategy.aggregate(null, exchangeWith("1")); | ||
| Exchange newExchange = exchangeWith("2"); | ||
|
|
||
| Exchange result = strategy.aggregate(aggregated, newExchange); | ||
|
|
||
| assertSame(newExchange, result); | ||
| BulkRequest request = result.getIn().getBody(BulkRequest.class); | ||
| assertEquals(List.of("1", "2"), ids(request)); |
There was a problem hiding this comment.
Same suggestion as for the ES test: extend to a 3-step chain to verify cumulative insertion order beyond the first merge.
There was a problem hiding this comment.
Same here — the OpenSearch test is the symmetric three-step chain with the same cumulative assertions. The two files are deliberately kept in lockstep, since the fork-pair drifting apart is how this class of bug gets reintroduced in one component and not the other.
Claude Code on behalf of @oscerd
|
🧪 CI tested the following changed modules:
🔬 Scalpel shadow comparison — Scalpel: 10 tested, 27 compile-only — current: 10 all testedMaveniverse Scalpel detected 37 affected modules (current approach: 10).
|
| Module | Duration | Status |
|---|---|---|
| Camel :: Launcher | 50.4s | SUCCESS |
| Camel :: JBang :: MCP | 38.1s | SUCCESS |
| Camel :: JBang :: Plugin :: TUI | 29.1s | SUCCESS |
| Camel :: Catalog :: Camel Catalog | 21.6s | SUCCESS |
| Camel :: Component DSL | 18.1s | SUCCESS |
| Camel :: Docs | 14.6s | SUCCESS |
| Camel :: JBang :: Plugin :: Kubernetes | 13.3s | SUCCESS |
| Camel :: Catalog :: Camel Report Maven Plugin | 8.1s | SUCCESS |
| Camel :: YAML DSL :: Validator | 7.9s | SUCCESS |
| Camel :: Kamelet Main | 7.7s | SUCCESS |
| Camel :: Catalog :: Camel Route Parser | 7.4s | SUCCESS |
| Camel :: JBang :: Plugin :: Testing | 7.1s | SUCCESS |
| Camel :: YAML DSL :: Deserializers | 5.0s | SUCCESS |
| Camel :: All Components Sync point | 4.6s | SUCCESS |
| Camel :: JBang :: Plugin :: Validate | 4.5s | SUCCESS |
| Camel :: YAML DSL :: Maven Plugins | 3.1s | SUCCESS |
| Camel :: Catalog :: Suggest (deprecated) | 2.6s | SUCCESS |
| Camel :: Catalog :: Maven | 2.4s | SUCCESS |
| Camel :: YAML DSL :: Validator Maven Plugin | 2.1s | SUCCESS |
| Camel :: JBang :: Plugin :: Edit | 1.5s | SUCCESS |
| Camel :: JBang :: Integration tests | 1.4s | SUCCESS |
| Camel :: Assembly | 1.3s | SUCCESS |
| Camel :: Coverage | 1.3s | SUCCESS |
| Camel :: Endpoint DSL :: Support | 1.1s | SUCCESS |
| Camel :: JBang :: Plugin :: Generate | 1.0s | SUCCESS |
| Camel :: Catalog :: Dummy Component | 1.0s | SUCCESS |
| Camel :: JBang :: Plugin :: Route Parser | 1.0s | SUCCESS |
| Camel :: Catalog :: Console | 1.0s | SUCCESS |
| Camel :: JBang :: Plugin :: MCP | 1.0s | SUCCESS |
| Camel :: JBang :: Main | 1.0s | SUCCESS |
| Camel :: Launcher :: Container | 0.7s | SUCCESS |
| Camel :: Elasticsearch Java API Client | n/a | |
| Camel :: Endpoint DSL | n/a | |
| Camel :: Integration Tests | n/a | |
| Camel :: JBang :: Core | n/a | |
| Camel :: OpenSearch Java API Client | n/a | |
| Camel :: YAML DSL | n/a |
Top 20 slowest modules:
Camel :: Launcher(50.4s)Camel :: JBang :: MCP(38.1s)Camel :: JBang :: Plugin :: TUI(29.1s)Camel :: Catalog :: Camel Catalog(21.6s)Camel :: Component DSL(18.1s)Camel :: Docs(14.6s)Camel :: JBang :: Plugin :: Kubernetes(13.3s)Camel :: Catalog :: Camel Report Maven Plugin(8.1s)Camel :: YAML DSL :: Validator(7.9s)Camel :: Kamelet Main(7.7s)Camel :: Catalog :: Camel Route Parser(7.4s)Camel :: JBang :: Plugin :: Testing(7.1s)Camel :: YAML DSL :: Deserializers(5.0s)Camel :: All Components Sync point(4.6s)Camel :: JBang :: Plugin :: Validate(4.5s)Camel :: YAML DSL :: Maven Plugins(3.1s)Camel :: Catalog :: Suggest (deprecated)(2.6s)Camel :: Catalog :: Maven(2.4s)Camel :: YAML DSL :: Validator Maven Plugin(2.1s)Camel :: JBang :: Plugin :: Edit(1.5s)
Address review feedback: prove the merged BulkRequest preserves insertion order cumulatively across more than one merge, guarding against a future off-by-one, by chaining three aggregations and asserting [1,2] then [1,2,3]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
|
Thanks for the review. Addressed the test-coverage suggestion in the latest commit: the insertion-order test now chains three aggregations and asserts Claude Code on behalf of Andrea Cosentino (@oscerd) |
gnodet-bot
left a comment
There was a problem hiding this comment.
Re-review after new commit — previous suggestion addressed.
Prior finding (3-step insertion-order test): ✅ Addressed. Both ElasticsearchBulkRequestAggregationStrategyTest and OpensearchBulkRequestAggregationStrategyTest now chain three aggregations and assert ["1", "2"] then ["1", "2", "3"], proving cumulative order is preserved beyond the first merge.
Fix is correct and tests are thorough. Good to merge.
This review was generated by an AI agent, Hermès on behalf of @gnodet.
What
ElasticsearchBulkRequestAggregationStrategyand its OpenSearch twinOpensearchBulkRequestAggregationStrategymerge severalBulkOperation[]messages into a singleBulkRequest(the documented bulk-aggregation pattern, used with the Aggregate EIP). Both built the merged request, stored it onnewExchange, and thenreturn oldExchange;.On the first message of an aggregation group
oldExchange == null, so the strategy returnednull.AggregateProcessorrejects a null return:So the very first exchange of every group failed, and even past the first call the returned exchange never carried the merged
BulkRequest. The strategy was effectively unusable. It stayed latent because neither component had a unit test for it.Fix
newExchange(which already holds the mergedBulkRequest) in both strategies.BulkRequest.Builder.operations(List)is additive).camel-elasticsearchandcamel-opensearch(previously untested).Notes
@UriParam/@Metadata, or generated-catalog change.camel-4.22.xandcamel-4.18.x.Generated by Claude Code on behalf of Andrea Cosentino (@oscerd).
🤖 Generated with Claude Code