Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DateTime Comparison for Conditional Writes in DynamoDB

I'm working with DynamoDB at the moment. I want to use a conditional write to update a record if that record has a date that is older than the new record date field.

Is there a way to compare DateTime types for conditional writes? Or is it currently only for integers, strings and streams?

Thanks.

like image 895
Francis Gilbert Avatar asked Jun 05 '15 10:06

Francis Gilbert


People also ask

Does Amazon DynamoDB support conditional operations?

Yes, like all the other database management systems, DynamoDB also supports all the conditional operators, User can specify a condition that is satisfied for a put, update, or delete operation to work on an item.

Can I store datetime in DynamoDB?

There are multiple ways to represent a timestamp in DynamoDB. Probably the most common is to use a Number type to represent the timestamp with the value as a Unix timestamp (seconds or milliseconds). Additionally, you can store the timestamp as a String type with the value as an ISO 8601 formatted string.

Does DynamoDB have timestamp?

Amazon DynamoDB TTL allows you to define a per-item timestamp to determine when an item is no longer needed. After the expiration of the TTL timestamp, DynamoDB deletes the item from your table within 48 hours without consuming any write throughput.

What is key condition expression in DynamoDB?

For more information, see Using expressions in DynamoDB. KeyConditions are the selection criteria for a Query operation. For a query on a table, you can have conditions only on the table primary key attributes. You must provide the partition key name and value as an EQ condition.


2 Answers

Since you mentioned you are using ISO-8601 with the String datatype, it is easy to use the comparing operators (<, <=, etc.) in your conditional expression because of the lexicographical ordering described in this answer.

Here is a quick example where I used Java 8's time and ran it against DynamoDB Local:

import com.google.common.collect.ImmutableMap;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.UpdateItemSpec;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.ConditionalCheckFailedException;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
import com.amazonaws.services.dynamodbv2.util.Tables;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

public class DynamoDBStackoverflow {

    public static final String TABLE_NAME = "exampleTable";
    private static final String HASH_KEY = "hashAttributeName";

    public static void main(String[] args) {

        AWSCredentials awsCredentials = new BasicAWSCredentials("key", "secret");
        AmazonDynamoDB client = new AmazonDynamoDBClient(awsCredentials);
        client.setEndpoint("http://localhost:4000");
        DynamoDB dynamoDB = new DynamoDB(client);

        if (Tables.doesTableExist(client, TABLE_NAME)) {
            client.deleteTable(TABLE_NAME);
        }

        final CreateTableRequest createTableRequest = new CreateTableRequest()
            .withTableName(TABLE_NAME)
            .withKeySchema(new KeySchemaElement(HASH_KEY, KeyType.HASH))
            .withAttributeDefinitions(new AttributeDefinition(HASH_KEY, ScalarAttributeType.S))
            .withProvisionedThroughput(new ProvisionedThroughput(15L, 15L));
        final Table table = dynamoDB.createTable(createTableRequest);

        final Instant now = Instant.now();
        final Instant before = now.minus(10, ChronoUnit.MINUTES).truncatedTo(ChronoUnit.MINUTES);
        final Instant after = now.plus(10, ChronoUnit.MINUTES);
        System.out.println("Before: " + before.toString());
        System.out.println("Now: " + now.toString());
        System.out.println("After: " + after.toString());

        table.putItem(new Item().withPrimaryKey(HASH_KEY, "1")
                          .withString("dateField", before.toString()));
        table.putItem(new Item().withPrimaryKey(HASH_KEY, "2")
                          .withString("dateField", now.toString()));
        System.out.println("put items");
        table.scan().forEach(System.out::println);

        UpdateItemSpec updateItemSpec = new UpdateItemSpec().withPrimaryKey(HASH_KEY, "1")
            .withConditionExpression("dateField < :beforeDate")
            .withValueMap(ImmutableMap.of(":beforeDate", before.toString()))
            .withUpdateExpression("SET dateField = :beforeDate");

        try {
            table.updateItem(updateItemSpec);
            throw new RuntimeException();
        } catch (ConditionalCheckFailedException ccfe) {
            System.out.println("expected conditional write with < to fail when they are equal");
        }

        updateItemSpec = new UpdateItemSpec().withPrimaryKey(HASH_KEY, "2")
            .withConditionExpression("dateField < :beforeDate")
            .withValueMap(ImmutableMap.of(":beforeDate", before.toString()))
            .withUpdateExpression("SET dateField = :beforeDate");

        try {
            table.updateItem(updateItemSpec);
            throw new RuntimeException();
        } catch (ConditionalCheckFailedException ccfe) {
            System.out.println("expected conditional write with < to fail when new is before");
        }

        updateItemSpec = new UpdateItemSpec().withPrimaryKey(HASH_KEY, "1")
            .withConditionExpression("dateField <= :beforeDate")
            .withValueMap(ImmutableMap.of(":beforeDate", before.toString()))
            .withUpdateExpression("SET dateField = :beforeDate");

        try {
            table.updateItem(updateItemSpec);
        } catch (ConditionalCheckFailedException ccfe) {
            System.out.println("should not happen");
            throw new RuntimeException();
        }

        updateItemSpec = new UpdateItemSpec().withPrimaryKey(HASH_KEY, "2")
            .withConditionExpression("dateField <= :afterDate")
            .withValueMap(ImmutableMap.of(":afterDate", after.toString()))
            .withUpdateExpression("SET dateField = :afterDate");
        try {
            table.updateItem(updateItemSpec);
        } catch (ConditionalCheckFailedException ccfe) {
            System.out.println("should not happen");
            throw new RuntimeException();
        }

        System.out.println();
        System.out.println("after all updates");
        table.scan().forEach(System.out::println);
    }
}

And the output:

Before: 2015-06-08T15:57:00Z
Now: 2015-06-08T16:07:08.893Z
After: 2015-06-08T16:17:08.893Z
put items
{ Item: {hashAttributeName=1, dateField=2015-06-08T15:57:00Z} }
{ Item: {hashAttributeName=2, dateField=2015-06-08T16:07:08.893Z} }
expected conditional write with < to fail when they are equal
expected conditional write with < to fail when new is before

after all updates
{ Item: {hashAttributeName=1, dateField=2015-06-08T15:57:00Z} }
{ Item: {hashAttributeName=2, dateField=2015-06-08T16:17:08.893Z} }
like image 51
mkobit Avatar answered Oct 28 '22 14:10

mkobit


DynamoDB doesn't understand dates. If you save the date as long, ms/s since epoch, then you can use arithmetic <, >=, etc.

If you use a String presentation, then it all depends if you can find the right DynamoDB operator to query on two of them.

I personally use the former, thus doing it with calculus.

like image 38
Chen Harel Avatar answered Oct 28 '22 15:10

Chen Harel