Codegen endpoint operationContextParams JMESPath expressions - #7383
Conversation
…etters at codegen time
alextwoods
left a comment
There was a problem hiding this comment.
The stringarray service I think was orig mean to cover all of the supported operationContextParams (and string array cases) from when we added that - I think we have it in test/codegen-generated-classes-test/src/main/resources/codegen-resources/stringarray/service-2.json - I think its worth adding tests (like what you did in ddb/s3) to verify the generated behavior
…ntextParams-jmesPath-lowering
Good call. StringArrayBindingsTest in that module already covers the generated behavior end-to-end and passes against the lowered code (all three bindings lower, including the multiselect-list). Added an OperationContextParamsBindingEquivalenceTest there matching the DDB/S3 ones, comparing each lowered binding against the reflective evaluation, plus a keys()-behind-a-nullable-struct case that no real service exercises. All three equivalence tests now also assert the generated resolver has no JmesPathRuntime reference, so a regression back to the reflective path can't pass silently. |
|
This pull request has been closed and the conversation has been locked. Comments on closed PRs are hard for our team to see. If you need more assistance, please open a new issue that references this one. |
Motivation and Context
Endpoint
operationContextParamsare currently bound by evaluating JMESPath expressions through the reflectiveJmesPathRuntimeon every request. It wraps request values, scansSdkPojo.sdkFields()for each field access, and copies collections and maps, so cost grows with batch size.These expressions are known at build time. This change generates direct typed getter calls for the supported patterns, removing reflection and most per-request allocation from the binding path.
Modifications
Generated
<Service>EndpointResolverUtils.setOperationContextParams(...)methods now bind supported expressions with plain getters and loops instead of the reflective runtime. For S3DeleteObjects(Delete.Objects[*].Key):Before:
After:
How it works:
JmesPathTypedGetterGenerator, converts a validated subset of expressions into typed Java: scalar string or boolean field chains,keys()over string-keyed maps, list projections ending in a string field, and a projected multiselect of string paths followed by one flatten.NameAllocator, so generated locals cannot collide with the method parameters or with locals from earlier bindings.keys()results as unordered, so generated code iterates the SDK request map directly instead of reproducing the reflective runtime's intermediateHashMaporder.This preserves defined JMESPath behavior and changes no public APIs. For
keys(), the concrete first entry and initially selected account endpoint can change from the reflective runtime's hash order to request-map iteration. For the current DynamoDB bindings, mixed-account requests are ultimately handled through the general endpoint regardless of which account ID is first, so request behavior converges to the same result. Expressions outside the subset keep the exact code emitted today.Scope
Six operations across DynamoDB and S3 use
operationContextParamstoday. All six expressions fall within the supported subset, so each one generates typed getters and none uses the reflective fallback:DynamoDB
BatchGetItemandBatchWriteItem:keys(RequestItems)ImportTable:TableCreationParameters.TableNameTransactGetItems:TransactItems[*].Get.TableNameTransactWriteItems:TransactItems[*].[ConditionCheck.TableName, Put.TableName, Delete.TableName, Update.TableName][]S3
DeleteObjects:Delete.Objects[*].KeyTesting
Added codegen unit tests covering accepted and rejected expression shapes, runtime-default rejection, name collisions, and whole-operation fallback. New S3 and DynamoDB tests feed the same request to both the lowered binding and the reflective runtime (which still ships as the fallback), comparing projections positionally and
keys()results as unordered content while covering null containers, null elements, null leaves, and empty-list mutability. A third equivalence test against the synthetic stringarray service intest/codegen-generated-classes-testcovers variants the real services don't reach, such askeys()behind a nullable struct and single-field multiselect branches. Each equivalence test also asserts the generated resolver's class file contains noJmesPathRuntimereference, so a regression that sends an operation back to the reflective path fails the test instead of comparing the fallback with itself.Benchmark
Benchmarked endpoint resolution for S3
DeleteObjectsand DynamoDBTransactWriteItemsat the request sizes shown below. The benchmark measures endpoint parameter construction plus endpoint resolution, not end-to-end API latency. JMH ran one thread, five 2-second warmups, ten 5-second measurements, and three forks, with allocation profiling enabled.BatchGetItemwas measured before the final review removed an unnecessaryHashMapcopy from its binding path; those stale rows are omitted rather than mixed with results for the final code.Both sides of the A/B are current master (which includes the recent BDD endpoint provider adoption for S3 and DynamoDB) and differ only by this change, so the ns-saved and allocation columns isolate this change's effect; ratios express the improvement of the combined operation on the current engine.
S3
DeleteObjects:DynamoDB
TransactWriteItems:All seven reported cases were faster and allocated less memory, with no overlapping latency error ranges.
The unaffected S3
GetObjectcontrol stayed stable: 199.4 ± 2.4 ns/op base, 197.3 ± 2.4 ns/op new. Its error ranges overlap, and allocation was identical (248 B/op).