AWS amazondynamodb documentation change
Summary
Enhanced TTL update example with conditional expressions, added validation, and improved error handling. Introduced constants for attribute names and expiration logic.
Security assessment
While the conditional check (attribute_exists) improves data integrity, this is a standard idempotency pattern rather than a security feature. No evidence of addressing vulnerabilities or documenting security controls.
Diff
diff --git a/amazondynamodb/latest/developerguide/ttl-expired-items.md b/amazondynamodb/latest/developerguide/ttl-expired-items.md index f8c5bba87..403597d9d 100644 --- a//amazondynamodb/latest/developerguide/ttl-expired-items.md +++ b//amazondynamodb/latest/developerguide/ttl-expired-items.md @@ -39,2 +38,0 @@ Query Filtered Expression to gather TTL items in a DynamoDB table using AWS SDK - import software.amazon.awssdk.utils.ImmutableMap; - @@ -44,16 +41,0 @@ Query Filtered Expression to gather TTL items in a DynamoDB table using AWS SDK - // Get current time in epoch second format (comparing against expiry attribute) - final long currentTime = System.currentTimeMillis() / 1000; - - // A string that contains conditions that DynamoDB applies after the Query operation, but before the data is returned to you. - final String keyConditionExpression = "#pk = :pk"; - - // The condition that specifies the key values for items to be retrieved by the Query action. - final String filterExpression = "#ea > :ea"; - final Map<String, String> expressionAttributeNames = ImmutableMap.of( - "#pk", "primaryKey", - "#ea", "expireAt"); - final Map<String, AttributeValue> expressionAttributeValues = ImmutableMap.of( - ":pk", AttributeValue.builder().s(primaryKey).build(), - ":ea", AttributeValue.builder().s(String.valueOf(currentTime)).build() - ); - @@ -62,2 +44,2 @@ Query Filtered Expression to gather TTL items in a DynamoDB table using AWS SDK - .keyConditionExpression(keyConditionExpression) - .filterExpression(filterExpression) + .keyConditionExpression(KEY_CONDITION_EXPRESSION) + .filterExpression(FILTER_EXPRESSION) @@ -67,3 +49,3 @@ Query Filtered Expression to gather TTL items in a DynamoDB table using AWS SDK - try (DynamoDbClient ddb = DynamoDbClient.builder() - .region(region) - .build()) { + + try (DynamoDbClient ddb = dynamoDbClient != null ? dynamoDbClient : + DynamoDbClient.builder().region(region).build()) { @@ -71,6 +53,8 @@ Query Filtered Expression to gather TTL items in a DynamoDB table using AWS SDK - System.out.println(tableName + " Query operation with TTL successful. Request id is " - + response.responseMetadata().requestId()); - // Print the items that are not expired - for (Map<String, AttributeValue> item : response.items()) { - System.out.println(item.toString()); - } + System.out.println("Query successful. Found " + response.count() + " items that have not expired yet."); + + // Print each item + response.items().forEach(item -> { + System.out.println("Item: " + item); + }); + + return 0; @@ -78,2 +62,2 @@ Query Filtered Expression to gather TTL items in a DynamoDB table using AWS SDK - System.err.format("Error: The Amazon DynamoDB table \"%s\" can't be found.\n", tableName); - System.exit(1); + System.err.format(TABLE_NOT_FOUND_ERROR, tableName); + throw e; @@ -82 +66 @@ Query Filtered Expression to gather TTL items in a DynamoDB table using AWS SDK - System.exit(1); + throw e; @@ -84 +67,0 @@ Query Filtered Expression to gather TTL items in a DynamoDB table using AWS SDK - System.exit(0); @@ -224,0 +208,5 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. + import java.util.Map; + import java.util.Optional; + + import com.amazon.samplelib.CodeSampleUtils; + @@ -227,0 +216 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. + import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; @@ -232,4 +220,0 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - import software.amazon.awssdk.utils.ImmutableMap; - - import java.util.Map; - import java.util.Optional; @@ -236,0 +222,4 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. + /** + * Updates an item in a DynamoDB table with TTL attributes using a conditional expression. + * This class demonstrates how to conditionally update TTL expiration timestamps. + */ @@ -238,2 +227,2 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - public static void main(String[] args) { - final String usage = """ + + private static final String USAGE = """ @@ -241 +230 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - <tableName> <primaryKey> <sortKey> <newTtlAttribute> <region> + <tableName> <primaryKey> <sortKey> <region> @@ -246 +234,0 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - newTtlAttribute - New attribute name (as part of the update command) @@ -249,3 +237,41 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - // Optional "region" parameter - if args list length is NOT 3 or 4, short-circuit exit. - if (!(args.length == 4 || args.length == 5)) { - System.out.println(usage); + private static final int DAYS_TO_EXPIRE = 90; + private static final int SECONDS_PER_DAY = 24 * 60 * 60; + private static final String PRIMARY_KEY_ATTR = "primaryKey"; + private static final String SORT_KEY_ATTR = "sortKey"; + private static final String UPDATED_AT_ATTR = "updatedAt"; + private static final String EXPIRE_AT_ATTR = "expireAt"; + private static final String UPDATE_EXPRESSION = "SET " + UPDATED_AT_ATTR + "=:c, " + EXPIRE_AT_ATTR + "=:e"; + private static final String CONDITION_EXPRESSION = "attribute_exists(" + PRIMARY_KEY_ATTR + ")"; + private static final String SUCCESS_MESSAGE = "%s UpdateItem operation with TTL successful."; + private static final String CONDITION_FAILED_MESSAGE = "Condition check failed. Item does not exist."; + private static final String TABLE_NOT_FOUND_ERROR = "Error: The Amazon DynamoDB table \"%s\" can't be found."; + + private final DynamoDbClient dynamoDbClient; + + /** + * Constructs an UpdateTTLConditional with a default DynamoDB client. + */ + public UpdateTTLConditional() { + this.dynamoDbClient = null; + } + + /** + * Constructs an UpdateTTLConditional with the specified DynamoDB client. + * + * @param dynamoDbClient The DynamoDB client to use + */ + public UpdateTTLConditional(final DynamoDbClient dynamoDbClient) { + this.dynamoDbClient = dynamoDbClient; + } + + /** + * Main method to demonstrate conditionally updating an item with TTL. + * + * @param args Command line arguments + */ + public static void main(final String[] args) { + try { + int result = new UpdateTTLConditional().processArgs(args); + System.exit(result); + } catch (Exception e) { + System.err.println(e.getMessage()); @@ -253,0 +280,15 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. + } + + /** + * Process command line arguments and conditionally update an item with TTL. + * + * @param args Command line arguments + * @return 0 if successful, non-zero otherwise + * @throws ResourceNotFoundException If the table doesn't exist + * @throws DynamoDbException If an error occurs during the operation + * @throws IllegalArgumentException If arguments are invalid + */ + public int processArgs(final String[] args) { + // Argument validation (remove or replace this line when reusing this code) + CodeSampleUtils.validateArgs(args, new int[]{3, 4}, USAGE); + @@ -257,2 +298,3 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - final String newTtlAttribute = args[3]; - Region region = Optional.ofNullable(args[4]).isEmpty() ? Region.US_EAST_1 : Region.of(args[4]); + final Region region = Optional.ofNullable(args.length > 3 ? args[3] : null) + .map(Region::of) + .orElse(Region.US_EAST_1); @@ -263,12 +306,12 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - final long expireDate = currentTime + (90 * 24 * 60 * 60); - // An expression that defines one or more attributes to be updated, the action to be performed on them, and new values for them. - final String updateExpression = "SET newTtlAttribute = :val1"; - // A condition that must be satisfied in order for a conditional update to succeed. - final String conditionExpression = "expireAt > :val2"; - - final ImmutableMap<String, AttributeValue> keyMap = - ImmutableMap.of("primaryKey", AttributeValue.fromS(primaryKey), - "sortKey", AttributeValue.fromS(sortKey)); - final Map<String, AttributeValue> expressionAttributeValues = ImmutableMap.of( - ":val1", AttributeValue.builder().s(newTtlAttribute).build(), - ":val2", AttributeValue.builder().s(String.valueOf(expireDate)).build() + final long expireDate = currentTime + (DAYS_TO_EXPIRE * SECONDS_PER_DAY); + + // Create the key map for the item to update + final Map<String, AttributeValue> keyMap = Map.of( + PRIMARY_KEY_ATTR, AttributeValue.builder().s(primaryKey).build(), + SORT_KEY_ATTR, AttributeValue.builder().s(sortKey).build() + ); + + // Create the expression attribute values + final Map<String, AttributeValue> expressionAttributeValues = Map.of( + ":c", AttributeValue.builder().n(String.valueOf(currentTime)).build(), + ":e", AttributeValue.builder().n(String.valueOf(expireDate)).build() @@ -280,2 +323,2 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - .updateExpression(updateExpression) - .conditionExpression(conditionExpression) + .updateExpression(UPDATE_EXPRESSION) + .conditionExpression(CONDITION_EXPRESSION) @@ -284,3 +327,3 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - try (DynamoDbClient ddb = DynamoDbClient.builder() - .region(region) - .build()) { + + try (DynamoDbClient ddb = dynamoDbClient != null ? dynamoDbClient : + DynamoDbClient.builder().region(region).build()) { @@ -288,2 +331,5 @@ Update TTL on on an existing DynamoDB Item in a table, with a condition. - System.out.println(tableName + " UpdateItem operation with conditional TTL successful. Request id is " - + response.responseMetadata().requestId()); + System.out.println(String.format(SUCCESS_MESSAGE, tableName)); + return 0; + } catch (ConditionalCheckFailedException e) { + System.err.println(CONDITION_FAILED_MESSAGE);