Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(dynamodb): resolve nested document paths in ADD and DELETE update actions - #1908

Merged
hectorvent merged 1 commit into
floci-io:mainfrom
hampsterx:fix/dynamodb-add-delete-nested-paths
Jul 30, 2026
Merged

fix(dynamodb): resolve nested document paths in ADD and DELETE update actions#1908
hectorvent merged 1 commit into
floci-io:mainfrom
hampsterx:fix/dynamodb-add-delete-nested-paths

Conversation

@hampsterx

@hampsterx hampsterx commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Route DynamoDB UpdateItem ADD and DELETE actions through the existing document-path helpers so nested paths update the intended attribute instead of creating phantom top-level keys.
  • Resolve each ExpressionAttributeNames segment while traversing DynamoDB M and L wrappers.
  • Preserve top-level behavior, nested DELETE empty-set removal, and DELETE on a missing path as a no-op.
  • Add focused unit coverage and AWS CLI compatibility cases for nested ADD and DELETE.

Review

Pre-push Codex and GLM-5.2 reviews found no regressions. One compatibility-coverage suggestion was applied before publication. Greptile also passed with no review threads.

Changes

  • Update ADD and DELETE clause reads and writes in DynamoDbService.
  • Add nested placeholder, literal path, set, missing-leaf, missing-path, and top-level regression tests.
  • Add AWS CLI wire-level tests for nested ADD and DELETE.

Test plan

  • ./mvnw test -Dtest=DynamoDbServiceTest (99 tests passed)
  • Filtered dynamodb.bats nested ADD and DELETE cases against the packaged server
  • ./mvnw package -DskipTests
  • Upstream Build and Test workflow plus the complete native AWS CLI, Java, Go, Node, Python, CDK, Terraform, and OpenTofu compatibility matrix

Local full-suite attempts hit a Quarkus test classpath/resource rewrite problem in this checkout. The clean upstream Build and Test workflow passed the full suite.

Prior work

This builds on the approaches in #1757 and #1873. It consolidates ADD and DELETE path handling, including the DELETE defect that #1757 did not cover.

Closes #1756

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a long-standing DynamoDB emulator defect where UpdateItem ADD and DELETE actions used a single resolveAttributeName call on the full path string, causing multi-segment paths (e.g. #a.#b.#c) to be treated as a literal top-level attribute name instead of being traversed. The fix routes both actions through the existing getValueAtPath / setValueAtPath / removeValueAtPath helpers, which already handle nested M/L unwrapping and per-segment placeholder resolution via parsePath.

  • DynamoDbService.java: applyAddClause and applyDeleteClause replace direct item.get/item.set/item.remove calls with the three path helpers, preserving all top-level semantics while correctly resolving nested paths.
  • DynamoDbServiceTest.java: Six new focused unit tests cover nested placeholder ADD (number and set), literal dotted-path ADD, missing-leaf seeding, nested SET delete (partial removal, full removal, and missing-path no-op), and a top-level regression guard. The mapAttributeValue helper is also refactored into an overload to eliminate duplication.
  • dynamodb.bats: Two new wire-level compatibility tests verify the fix against the packaged server using the AWS CLI, including phantom-key absence assertions.

Confidence Score: 5/5

Safe to merge — the change is a targeted two-site swap of direct map access with already-proven path helpers, and every affected code branch is covered by new unit and wire-level tests.

The diff is minimal: four lines changed in the service, all replacing item.get/item.set/item.remove with getValueAtPath/setValueAtPath/removeValueAtPath — helpers already in production use for SET and REMOVE operations. Top-level attribute behavior is provably identical to before, and six new unit tests plus two new bats tests exercise every meaningful branch introduced by the fix, including the phantom-key regression guard.

No files require special attention.

Important Files Changed

Filename Overview
src/main/java/io/github/hectorvent/floci/services/dynamodb/DynamoDbService.java Minimal fix: replaced direct item.get/set/remove calls in applyAddClause and applyDeleteClause with the established getValueAtPath/setValueAtPath/removeValueAtPath path helpers. Top-level and nested behavior are both correct; no regressions in existing logic.
src/test/java/io/github/hectorvent/floci/services/dynamodb/DynamoDbServiceTest.java Six new unit tests comprehensively cover all paths introduced by the fix (placeholder and literal ADD, missing leaf, SS ADD union, nested DELETE with empty-set removal, no-op DELETE on missing path, top-level regression). mapAttributeValue refactored into a clean overload.
compatibility-tests/sdk-test-awscli/test/dynamodb.bats Two new end-to-end bats tests verify nested ADD and DELETE against the packaged server via the AWS CLI, including phantom top-level key absence checks and the full delete-until-empty-then-no-op sequence.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["applyAddClause / applyDeleteClause\nattrPath = parts[0] (raw, e.g. '#a.#b.#c')"] --> B["getValueAtPath(item, attrPath, exprAttrNames)"]
    B --> C["parsePath(path, exprAttrNames)\n→ resolveAttributeName per segment\n→ tokens: [String|Integer]"]
    C --> D{"single token?"}
    D -- yes --> E["item.get(attrName)\n→ DynamoDB-typed leaf value"]
    D -- no --> F["Traverse item:\nString tok → unwrap M wrapper\nInteger tok → unwrap L wrapper"]
    F --> G["Return DynamoDB-typed leaf\n(or null if path missing)"]
    E --> H["applyAddOperation / applyDeleteOperation\nOperates on DynamoDB-typed value"]
    G --> H
    H --> I{"ADD: newValue computed\nDELETE: null = remove, non-null = update"}
    I -- "setValueAtPath" --> J["Write DynamoDB-typed value\nback at full path,\ncreating intermediate M nodes if needed"]
    I -- "removeValueAtPath" --> K["Remove leaf at full path\nvia M-unwrapping traversal"]
    J --> L["Item correctly mutated\nNo phantom top-level key"]
    K --> L
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["applyAddClause / applyDeleteClause\nattrPath = parts[0] (raw, e.g. '#a.#b.#c')"] --> B["getValueAtPath(item, attrPath, exprAttrNames)"]
    B --> C["parsePath(path, exprAttrNames)\n→ resolveAttributeName per segment\n→ tokens: [String|Integer]"]
    C --> D{"single token?"}
    D -- yes --> E["item.get(attrName)\n→ DynamoDB-typed leaf value"]
    D -- no --> F["Traverse item:\nString tok → unwrap M wrapper\nInteger tok → unwrap L wrapper"]
    F --> G["Return DynamoDB-typed leaf\n(or null if path missing)"]
    E --> H["applyAddOperation / applyDeleteOperation\nOperates on DynamoDB-typed value"]
    G --> H
    H --> I{"ADD: newValue computed\nDELETE: null = remove, non-null = update"}
    I -- "setValueAtPath" --> J["Write DynamoDB-typed value\nback at full path,\ncreating intermediate M nodes if needed"]
    I -- "removeValueAtPath" --> K["Remove leaf at full path\nvia M-unwrapping traversal"]
    J --> L["Item correctly mutated\nNo phantom top-level key"]
    K --> L
Loading

Reviews (1): Last reviewed commit: "fix(dynamodb): resolve nested document p..." | Re-trigger Greptile

@hectorvent hectorvent added bug Something isn't working dynamodb Amazon DynamoDB labels Jul 22, 2026

@pgermosen pgermosen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

@hectorvent hectorvent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @hampsterx

@hectorvent
hectorvent merged commit c052259 into floci-io:main Jul 30, 2026
14 checks passed
@hectorvent

Copy link
Copy Markdown
Collaborator

🎉 This PR is included in version 1.6.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

abanna pushed a commit to abanna/floci that referenced this pull request Aug 6, 2026
# [1.6.0](floci-io/floci@1.5.34...1.6.0) (2026-08-06)

### Bug Fixes

* **1790:** better populate describe alarm responses ([floci-io#1792](floci-io#1792)) ([fcb22be](floci-io@fcb22be))
* **apigateway:** fall through to less specific resources on method mi… ([floci-io#1630](floci-io#1630)) ([96c7aa6](floci-io@96c7aa6)), closes [floci-io#1547](floci-io#1547)
* **apigateway:** import authorizers and security requirements from OpenAPI ([floci-io#1798](floci-io#1798)) ([91e06eb](floci-io@91e06eb))
* **apigateway:** use last duplicate header value ([floci-io#1806](floci-io#1806)) ([6a214e8](floci-io@6a214e8))
* **athena:** report the result CSV object key as OutputLocation ([floci-io#1895](floci-io#1895)) ([418ec1b](floci-io@418ec1b)), closes [floci-io#1752](floci-io#1752)
* **cloudformation:** AWS::IAM::ManagedPolicy exposes PolicyArn for Fn::GetAtt ([floci-io#2056](floci-io#2056)) ([f719033](floci-io@f719033))
* **cloudformation:** keep uniqueness suffix when truncating generated resource names ([floci-io#1802](floci-io#1802)) ([48b2139](floci-io@48b2139)), closes [floci-io#1825](floci-io#1825)
* **cloudformation:** preserve subnet public IP setting ([floci-io#2031](floci-io#2031)) ([b62d1e7](floci-io@b62d1e7))
* **cloudformation:** provision AWS::ApiGatewayV2::Authorizer and wire Route.AuthorizerId ([floci-io#1760](floci-io#1760)) ([7f51c74](floci-io@7f51c74)), closes [floci-io#1773](floci-io#1773)
* **cloudformation:** resolve Fn::GetAtt [Vpc, DefaultSecurityGroup] ([floci-io#1977](floci-io#1977)) ([bdad066](floci-io@bdad066)), closes [floci-io#1976](floci-io#1976)
* **cloudformation:** treat delete of an already-removed DynamoDB table or Lambda function as idempotent ([floci-io#1803](floci-io#1803)) ([5cb26ff](floci-io@5cb26ff))
* **codebuild:** stabilize start and retry responses ([floci-io#2052](floci-io#2052)) ([fa96b79](floci-io@fa96b79))
* **cognito:** reject unconfirmed users in SRP auth ([floci-io#2027](floci-io#2027)) ([733c72d](floci-io@733c72d))
* **dockerfile:** ensure docker-java compatibility ([floci-io#2096](floci-io#2096)) ([7ade290](floci-io@7ade290))
* **dynamodb:** resolve nested document paths in ADD and DELETE update actions ([floci-io#1908](floci-io#1908)) ([c052259](floci-io@c052259))
* **ec2:** DescribeKeyPairs returns InvalidKeyPair.NotFound for missing names/ids ([floci-io#1932](floci-io#1932)) ([7d96106](floci-io@7d96106)), closes [floci-io#1911](floci-io#1911)
* **ec2:** return AWS-parity MissingParameter for CreateSubnet without VpcId ([floci-io#2094](floci-io#2094)) ([88408d8](floci-io@88408d8)), closes [floci-io#2089](floci-io#2089)
* **ecs:** resolve Secrets Manager JSON-key selectors in task definition secrets ([floci-io#2133](floci-io#2133)) ([e3c180b](floci-io@e3c180b)), closes [floci-io#1912](floci-io#1912)
* **lambda:** carry the owning account into published version snapshots ([floci-io#2041](floci-io#2041)) ([f4e8bd8](floci-io@f4e8bd8)), closes [floci-io#2040](floci-io#2040)
* **lambda:** report resolved executed version ([floci-io#2026](floci-io#2026)) ([2889fbd](floci-io@2889fbd))
* **lambda:** reset volatile DynamoDB Streams ESM checkpoints on restart ([floci-io#2077](floci-io#2077)) ([847c30a](floci-io@847c30a))
* **pipes:** register kafka-clients reflection metadata for native image ([floci-io#2068](floci-io#2068)) ([ab73379](floci-io@ab73379))
* **s3:** accept a percent-encoded bucket/key separator in copy sources ([floci-io#2060](floci-io#2060)) ([a8ed218](floci-io@a8ed218)), closes [floci-io#2038](floci-io#2038)
* **s3:** apply CreateBucketConfiguration tags on bucket creation ([floci-io#2115](floci-io#2115)) ([79031a2](floci-io@79031a2))
* **s3:** include EventBridgeConfiguration in GetBucketNotification response ([floci-io#2072](floci-io#2072)) ([b8edd37](floci-io@b8edd37))
* **s3:** recognize virtual-hosted-style requests over HTTP/2 ([floci-io#1954](floci-io#1954)) ([5f7d4d2](floci-io@5f7d4d2)), closes [floci-io#1866](floci-io#1866)
* **ssm:** report invalid batch parameter names ([floci-io#2075](floci-io#2075)) ([04873bf](floci-io@04873bf))
* **tls:** reuse the persisted self-signed certificate across restarts ([floci-io#1799](floci-io#1799)) ([a4356f2](floci-io@a4356f2))

### Features

* Add AWSCloudFormationReadOnlyAccess as a managed policy. ([floci-io#2057](floci-io#2057)) ([0142dc4](floci-io@0142dc4))
* **apigateway:** support floci:override-id for v1 and v2 ([floci-io#2045](floci-io#2045)) ([02385da](floci-io@02385da)), closes [floci-io#1593](floci-io#1593)
* **bedrock:** add proxy backend for real LLM responses via OpenAI-compatible API ([floci-io#1789](floci-io#1789)) ([875e0f2](floci-io@875e0f2))
* **cloudcontrol:** include EC2 resource tags ([floci-io#1933](floci-io#1933)) ([205a8f1](floci-io@205a8f1))
* **cloudformation:** provision AWS::EC2::LaunchTemplate ([floci-io#1973](floci-io#1973)) ([4e0a815](floci-io@4e0a815)), closes [floci-io#1971](floci-io#1971)
* **cloudformation:** provision AWS::EC2::VPCGatewayAttachment ([floci-io#1972](floci-io#1972)) ([1a99721](floci-io@1a99721)), closes [floci-io#1970](floci-io#1970)
* **cloudformation:** provision AWS::SecretsManager::SecretTargetAttachment ([floci-io#1804](floci-io#1804)) ([62a576c](floci-io@62a576c))
* **cloudformation:** support AWS::Events::EventBus and EventBusPolicy ([floci-io#1794](floci-io#1794)) ([76f602f](floci-io@76f602f))
* **cognito:** implement ResendConfirmationCode ([floci-io#2071](floci-io#2071)) ([bb7c45c](floci-io@bb7c45c)), closes [floci-io#2067](floci-io#2067)
* **ec2:** added attach and detach volume support ([floci-io#1787](floci-io#1787)) ([2c74329](floci-io@2c74329))
* **ec2:** return an empty set for DescribeVpnGateways ([floci-io#1975](floci-io#1975)) ([3baf4f4](floci-io@3baf4f4)), closes [floci-io#1974](floci-io#1974)
* **iam:** list entities attached to managed policies ([floci-io#1808](floci-io#1808)) ([78ba5b3](floci-io@78ba5b3))
* **iam:** seed Amazon Bedrock managed policies ([floci-io#2034](floci-io#2034)) ([5874348](floci-io@5874348)), closes [floci-io#1559](floci-io#1559) [floci-io#1216](floci-io#1216)
* **iam:** seed the managed policies CDK bootstrap and the scenario stacks attach ([floci-io#2064](floci-io#2064)) ([43aea09](floci-io@43aea09)), closes [floci-io#2059](floci-io#2059) [floci-io#2057](floci-io#2057) [floci-io#2057](floci-io#2057)
* **kms:** implement ListKeyPolicies ([floci-io#2046](floci-io#2046)) ([760115d](floci-io@760115d)), closes [floci-io#1528](floci-io#1528)
* **lambda:** implement the Lambda Extensions API ([floci-io#1773](floci-io#1773)) ([9f0dec9](floci-io@9f0dec9))
* **lambda:** support extra /etc/hosts entries on Lambda launch ([floci-io#2073](floci-io#2073)) ([b543aa4](floci-io@b543aa4))
* **mwaa:** add Amazon MWAA emulation backed by real Airflow (LocalExecutor) ([floci-io#2086](floci-io#2086)) ([7e4e3e8](floci-io@7e4e3e8))
* **release:** one-button release cut from main and ECR Public versioned publishing ([floci-io#2127](floci-io#2127)) ([61011c0](floci-io@61011c0))
* **rum:** add CloudWatch RUM app-monitor service ([floci-io#1797](floci-io#1797)) ([aadc93e](floci-io@aadc93e))
* **sqs:** retain MessageGroupId on standard queue messages ([floci-io#1891](floci-io#1891)) ([a01b707](floci-io@a01b707)), closes [floci-io#1496](floci-io#1496)

### Performance Improvements

* **docker:** stop duplicating the native binary into a second image layer ([floci-io#2069](floci-io#2069)) ([9e90e5c](floci-io@9e90e5c))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working dynamodb Amazon DynamoDB released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] DynamoDB UpdateItem: ADD on a nested map path with ExpressionAttributeNames creates a phantom top-level attribute instead of incrementing

3 participants