Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed EnhancedClient UpdateItem operation to make it work on nested attributes as well #5593

Merged
merged 61 commits into from
Sep 19, 2024

Conversation

anirudh9391
Copy link
Contributor

@anirudh9391 anirudh9391 commented Sep 12, 2024

Modified Enhanced Client to facilitate nested update operations.

Motivation and Context

Customer reported an issue where UpdateItem does not work for nested attributes.

Modifications

Modified UpdateItem query path to form the right updateQuery for nested update attributes.
Previously nested update queries looked like

SET #AMZN_MAPPED_mainAddress = :AMZN_MAPPED_mainAddress
:AMZN_MAPPED_mainAddress -> "AttributeValue(M={state=AttributeValue(S=WA)})"

This operates like a putItem and updates the map with a new state.

Intead modified the update query to

aws dynamodb update-item \
    --table-name TEST_TABLE3 \
    --key '{"id":{"N":"111"}}' \
    --region us-west-2 \
    --update-expression "SET #AMZN_MAPPED_mainAddress.#AMZN_MAPPED_city = :AMZN_MAPPED_mainAddress_city" \
    --expression-attribute-values '{":AMZN_MAPPED_mainAddress_city": {"S": "Salem"}}' \  
    --expression-attribute-names '{"#AMZN_MAPPED_mainAddress": "mainAddress","#AMZN_MAPPED_city":"city"}'

Public APIs

  1. ignoreNullsMode(IgnoreNullsMode ignoreNullsMode) - Sets the ignoreNullsMode, defining the configuration in which the update is to be carried out.

Usage :

dynamoDBTable.updateItem(r -> r.item(update_record).ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY));
  1. public IgnoreNullsMode ignoreNullsMode() - Retrieves the mode of usage

Usage :

updateItemEnhancedRequest.ignoreNullsMode() == IgnoreNullsMode.SCALAR_ONLY ? : clause1 : clause2

Testing

Unit Tests pass

Screenshots (if appropriate)

Types of changes

  • [ x] Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

…e operations on nested DynamoDB object attributes.
@anirudh9391 anirudh9391 requested a review from a team as a code owner September 12, 2024 22:26
// If ignoreNulls is set to true, check for nested params to be updated
// If needed, Transform itemMap for it to be able to handle them.

Map<String, AttributeValue> itemMap = ignoreNullsMode == IgnoreNullsMode.SCALAR_ONLY ?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This line is different from the approved PR

Copy link
Contributor

Choose a reason for hiding this comment

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

So just to confirm, MAPS_ONLY mode is what we originally supported with ignoreNulls == true, and SCALAR_ONLY is new support we added?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes that is correct. with MAPS_ONLY, essentially a new map is created as part of a every item update regardless of whether its scalar or non-scalar. This does not work for scalar updates, and hence the SCALAR_ONLY

@@ -0,0 +1,32 @@
/*
Copy link
Contributor Author

@anirudh9391 anirudh9391 Sep 12, 2024

Choose a reason for hiding this comment

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

This enum is new

@@ -145,6 +162,355 @@ public void updateBehaviors_transactWriteItems_secondUpdate() {
assertThat(persistedRecord.getCreatedAutoUpdateOn()).isEqualTo(firstUpdatedRecord.getCreatedAutoUpdateOn());
}

@Test
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have modified and added more tests

@joviegas
Copy link
Contributor

Since we are adding Public APIS for this could you please mention the Public APIS in PR description and user experience in PR description

verifySingleLevelNestingDefaultUpdateBehavior(persistedRecord.getNestedRecord(), updatedNestedCounter);
}

@Test
Copy link
Contributor

@joviegas joviegas Sep 13, 2024

Choose a reason for hiding this comment

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

Can we have parameterized test so that,its easy to find expected behaviors

public static List<Arguments> getDifferentCombinationOfignoreNullsModeAndExpectedResult() {
        return Arrays.asList(
            Arguments.of(simpleNestedAttrib, enum1, expectedMap1),
  Arguments.of(simpleNestedAttrib, enum2, expectedMap1),
  Arguments.of(nestedAttribWithEmpty, enum2, expectedMap1),
  
  etc etc


@ParameterizedTest
@MethodSource("getDifferentCombinationOfignoreNullsModeAndExpectedResult")
void testUpdateWithDifferentModes(NestedAttibute attrib,  IgnoreNullsMode ignoreNullsMode, Map<String, AttributeVale> expectedUpdate)
{
//existing common code
        mappedTable.updateItem(r -> r.item(attrib).ignoreNullsMode(ignoreNullsMode));

Map<String, AttributeVale> valueInDDB = nativeClient.get(item);

assertThat(expectedUpdate).isEqualTo(valueInDDB)

}

Copy link
Contributor Author

@anirudh9391 anirudh9391 Sep 16, 2024

Choose a reason for hiding this comment

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

This is not going to make it neater. For each case, i.e. simple nested attribute, empty attribute or a multi nested attribute, the validation functions are different. If at all we group some methods together, it will not make it neater.

For eg. updating non-scalar object updates in scalar_only mode throws exception, whereas for maps_only mode has an expected output. Much more like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To make it more readable, I have grouped some of the validation methods together in the next revision into :

  • verifyMultipleLevelNestingTargetedUpdateBehavior
  • verifySingleLevelNestingTargetedUpdateBehavior

Comment on lines 20 to 29
/**
* This enum offers users different modes of performing DDB item updates
* <p>
* In the SCALAR_ONLY mode, updates to nested scalar attributes are supported
* <p>
* In the MAPS_ONLY mode, updates to nested map structures are supported
* <p>
* The DEFAULT mode operates by setting ignoreNulls to false, and requires the user to
* fetch existing DDB item, make modifications to it and then update the item
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

This probably needs more detail, especially regarding tradeoffs

import software.amazon.awssdk.annotations.SdkPublicApi;

/**
* This enum offers users different modes of performing DDB item updates
Copy link
Contributor

Choose a reason for hiding this comment

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

different modes of performing DDB item updates

This statement seems too generic

Comment on lines +33 to +34
MAPS_ONLY,
DEFAULT
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the difference between MAPS_ONLY and DEFAULT? In the code, seems like we behave the same unless its SCALAR_ONLY

Copy link
Contributor Author

@anirudh9391 anirudh9391 Sep 16, 2024

Choose a reason for hiding this comment

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

Good point. I have changed this and added a unit test.

  1. MAPS_ONLY - allows users to set non-null values to null valued / nonexistent maps in ignoreNulls = true mode.
  2. DEFAULT - ignoreNulls = false mode, expecting users to retrieve and modify the item

* <p>
* In the SCALAR_ONLY mode, updates to nested scalar attributes are supported in the ignoreNulls = true mode, i.e. when the user
* wants to update nested scalar attributes by providing only the delta of changes to be updated. This mode does not support
* updates to maps and is expected to throw a 400x DynamoDB exception if done so.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: 400x looks like "400 times" to me. Should this be 4xx?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah i will change it

// If ignoreNulls is set to true, check for nested params to be updated
// If needed, Transform itemMap for it to be able to handle them.

Map<String, AttributeValue> itemMap = ignoreNullsMode == IgnoreNullsMode.SCALAR_ONLY ?
Copy link
Contributor

Choose a reason for hiding this comment

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

So just to confirm, MAPS_ONLY mode is what we originally supported with ignoreNulls == true, and SCALAR_ONLY is new support we added?

@anirudh9391 anirudh9391 added this pull request to the merge queue Sep 18, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Sep 18, 2024
Copy link

sonarcloud bot commented Sep 19, 2024

@anirudh9391 anirudh9391 added this pull request to the merge queue Sep 19, 2024
Merged via the queue into master with commit 7ae6ed3 Sep 19, 2024
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants