My DynamoDB table looks like this:
ID -> String
status -> (READY, DONE, IN_PROGRESS, FAILED)
other attributes..
My save data into DDB looks like this:
DDBClass class = new DDBClass();
class.setId("ID");
class.setStatus("READY");
DynamoDBSaveExpression saveExpression = new DynamoDBSaveExpression();
Map expectedExpression = new HashMap();
List<AttributeValue> attributeValues = new ArrayList<AttributeValue>();
attributeValues.add(new AttributeValue(WorkStatus.READY.toString()));
attributeValues.add(new AttributeValue(WorkStatus.RUNNING.toString()));
expectedExpression.put(WorkLogDdbItem.WORK_STATUS_ATTR, new ExpectedAttributeValue().withAttributeValueList(attributeValues).withComparisonOperator(ComparisonOperator.IN));
saveExpression.setExpected(expectedExpression);
ddbMapper.save(class, expression, DynamoDBMapperConfig.DEFAULT);
I want DynamoDBSaveExpression to evaluate such that:
If the entry does not exist, insert into DDB
If the entry does exist, compare the status values and then insert if required. I added withExists=true as part of the ExpectedAttributeValue but that gives me an exception:
Caused by: com.amazonaws.services.dynamodbv2.model.AmazonDynamoDBException: One or more parameter values were invalid: Exists and ComparisonOperator cannot be used together for Attribute: workStatus (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: 3N44TOJ4CFAJQ3A0OSKUCI22ARVV4KQNSO5AEMVJF66Q9ASUAAJG)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1640)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1304)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1058)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:743)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:717)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:699)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:667)
at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:649)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:513)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.doInvoke(AmazonDynamoDBClient.java:3443)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.invoke(AmazonDynamoDBClient.java:3419)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.executeUpdateItem(AmazonDynamoDBClient.java:3153)
at com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient.updateItem(AmazonDynamoDBClient.java:3128)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper$SaveObjectHandler.doUpdateItem(DynamoDBMapper.java:853)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper$2.executeLowLevelRequest(DynamoDBMapper.java:593)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper$SaveObjectHandler.execute(DynamoDBMapper.java:732)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.save(DynamoDBMapper.java:622)
How do I make this condition with DynamoDBSaveExpression?
By exists, did you mean the record exists already in the table or check for not-null? If its the later, use
ExpectedAttributeValue expectedNotNull = new ExpectedAttributeValue()
.withComparisonOperator(ComparisonOperator.NOT_NULL);
If it is to check if a record already exist in the table, that could be done only on primary key.
AFAIK, DynamoDBSaveExpression on a non-key/non-index attribute could be applied only for updates and not inserts.
Related
I'm new to DynamoDB and was following tutorial to implement basic CRUD operations.
I have the below code
public static void main(String ... args) throws Exception {
//1. create client
client = AmazonDynamoDBClientBuilder.standard()
.withRegion(Regions.US_EAST_1)
.build();
//2. Create table
String jobID="8aee43e5c44212040529fe11000117cdd0cb77eb";
PutItemSpec putItemSpec = new PutItemSpec();
putItemSpec.withItem(new Item().withPrimaryKey("jobid", jobID ).
withString("type", "first").withBoolean("cancel", false));
PutItemOutcome putItemOutcome = table.putItem(putItemSpec);
System.out.println(putItemOutcome);
//3. Read
GetItemSpec spec = new GetItemSpec().withPrimaryKey("jobid", jobID);
Item item = table.getItem(spec);
System.out.println(item);
//4.Update boolean
UpdateItemSpec updateItemSpec = new UpdateItemSpec().withPrimaryKey("jobid", jobID)
.withUpdateExpression("set cancel=:s")
.withValueMap(new ValueMap().withBoolean(":s",true));
UpdateItemOutcome updateItemOutcome = table.updateItem(updateItemSpec);
//4. read updated bool
item = table.getItem(spec);
System.out.println(item);
//5. update String breaks
UpdateItemSpec updateItemSpec2 = new UpdateItemSpec().withPrimaryKey("jobid", jobID)
.withUpdateExpression("set type=:s")
.withValueMap(new ValueMap().withString(":s","updated"));
UpdateItemOutcome updateItemOutcome2 = table.updateItem(updateItemSpec2);
//4. read updated bool
item = table.getItem(spec);
System.out.println(item);
}}
In this code, Create works fine and getItem fetches the data. When I updated the cancel boolean column it works fine and returns the updated Item. However, when I try to update the String column type to different value it throws the following exception.
Exception in thread "main" com.amazonaws.AmazonServiceException: Unable to unmarshall exception response with the unmarshallers provided (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: 4RHDOGACM1MOADU4N8RUPFMUJBVV4KQNSO5AEMVJF66Q9ASUAAJG; Proxy: null)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1862)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleServiceErrorResponse(AmazonHttpClient.java:1415)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1384)
The AWS-SDK-Version is 1.12.153 and JDK Version is 17.0.1. Also, when I add new string column type2 as part of the update and update it with another call it works. But, String column added as part of create is not getting updated.
Please let me know what I'm missing?
Thanks!
The word type is one of many DynamoDB reserved words.
You cannot use a reserved word in an expression. Instead you need to use an alias and then provide DynamoDB with a map from the alias to the real name. For example you might indicate an alias by set #t=:s and then provide a map from #t to type.
Your code will look something like this:
UpdateItemSpec updateItemSpec2 =
new UpdateItemSpec()
.withPrimaryKey("jobid", jobID)
.withUpdateExpression("set #t=:s")
.withValueMap(new ValueMap().withString(":s","updated"))
.withNameMap(new NameMap().with("#t","type"));
I have a requirement to create a DynamoDB table with four Attribute. I am using the following Java program:
String tableName = "devtest";
Table table = dynamoDB.createTable(tableName,
Arrays.asList(new KeySchemaElement("BIC", KeyType.HASH)), // Sort key
Arrays.asList(new AttributeDefinition("BIC", ScalarAttributeType.S),
new AttributeDefinition("Tenant", ScalarAttributeType.S),
new AttributeDefinition("TenantID", ScalarAttributeType.S),
new AttributeDefinition("Destination", ScalarAttributeType.S)),
new ProvisionedThroughput(10L, 10L));
table.waitForActive();
System.out.println("Success. Table status: " + table.getDescription().getTableStatus());
I am always getting the following error:
Unable to create table:
Unable to unmarshall exception response with the unmarshallers provided (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: bc0565ac-9d44-4876-934a-b39fbe8ca3f1)
How to fix this error?
You can have only one attribute BIC, because this is your HASH. Since you don't have any sort key, local or global secondary indices, you can't define any other attributes.
DynamoDB is schema-less, which means that it does not have any per-define structure.
Similar to: I cannot query my dynamodb table from aws lambda due to wrong filterexpression? and DynamoDB update error Invalid UpdateExpression: An expression attribute value used in expression is not defined
I am trying to code a way to query DynamoDB tables using partial matches on Partition Key / Sort Key in Java.
The DynamoDB table I am trying to access has a Partition key of "Type" (A restricted key word in DynamoDB, I know, but not my choice) and a Sort key of "Id". I know the "Type" but not the full Id, so I have researched the Query method using AWS SDK 2.x source code and have implemented as shown below:
DynamoDBClient dynamoDbClient = DynamoDbClient.builder()
.region(Region.EU_WEST_1)
.credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
.build();
String idKey = "wholeIdKey";
String idValue = "partialIdValue";
String typeValue = "typeValue";
Map<String, String> expressionNames = new HashMap<>();
expressionNames.put("#t", "Type");
QueryRequest request = QueryRequest.builder()
.tableName(tableName)
.keyConditionExpression("begins_with ( " + idKey + ", :" + idValue + " )
AND #t = :" + typeValue)
.expressionAttributeNames(expressionNames)
.build();
QueryResponse response = dynamoDbClient.query(request);
However, when I run this code, I get the following error message:
Exception in thread "main" software.amazon.awssdk.services.dynamodb.model.DynamoDbException:
Invalid KeyConditionExpression: An expression attribute value used in expression is not defined; attribute value: :typeValue
It's as if it's not recognizing the fact that I have told the code use the Expression Attribute Names feature to replace the "#t" with "Type" (Which is a reserved keyword in DynamoDB)
Can anyone help?
EDIT: References for code:
https://docs.aws.amazon.com/code-samples/latest/catalog/javav2-dynamodb-src-main-java-com-example-dynamodb-Query.java.html
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html
https://www.javadoc.io/static/software.amazon.awssdk/dynamodb/2.7.14/software/amazon/awssdk/services/dynamodb/model/QueryRequest.html#expressionAttributeNames--
The name is fine, but you're prefixing both values with ':'. That causes a lookup in ExpressionAttributeValues, which you did not provide.
Never try to write dynamic values directly into the query string.
Your expressionAttributeName looks fine, but you forgot to provide a value for :typeValue so dynamoDB cannot know what to look for.
In addition to what you did, you need to add an expressionAttributeValue where you can provide proper values. See documentation here
Fixed Code for whoever wants it in the future (Thanks to #aherve and #MattTimmermans)
DynamoDBClient dynamoDbClient = DynamoDbClient.builder()
.region(Region.EU_WEST_1)
.credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
.build();
String idKey = "wholeIdKey";
String idValue = "partialIdValue";
String typeValue = "typeValue";
String typeKey = "typeKey";
Map<String, String> expressionNames = new HashMap<>();
expressionNames.put("#t", "Type");
expressionNames.put("#i", "Id");
Map<String, AttributeValue> expressionValues = new HashMap<>();
expressionValues.put(":typeName", AttributeValue.builder().s(typeValue).build());
expressionValues.put(":idName", AttributeValue.builder().s(idValue).build());
QueryRequest request = QueryRequest.builder()
.tableName(tableName)
.keyConditionExpression("#t = :typeName AND begins_with ( #i, :idName )")
.expressionAttributeNames(expressionNames)
.expressionAttributeValues(expressionValues)
.build();
response = dynamoDbClient.query(request);
I am trying to query a local secondary index with the hash key.
My table: Table1
Username(hash_key), Id(range_key), timestamp....
My local secondary index(because I want to query all usernames and have it ordered by time)
My LSI: Table1_TimestampLSI
Username(hash_key), timestamp(range_key)
e.g
user1 | 123 | 12/12/2015
user1 | 456 | 11/01/2015
user2 | 789 | 12/01/2015
NOTE: I could not just make the timestamp the range key in the table itself because (Username+Timestamp) is not unique. So I had to create a ID field to ensure uniqueness.
Since I want the async client I am using the low level API(model NOT document) java API.
Function to query index
HashMap<String, Condition> queryFilter = new HashMap<String, Condition>();
Condition condition = new Condition()
.withComparisonOperator(ComparisonOperator.EQ.toString())
.withAttributeValueList(new AttributeValue().withS(username));
queryFilter.put("Username", condition);
QueryRequest queryRequest = new QueryRequest(tableName + "_TimestampLSI").withQueryFilter(queryFilter);
queryRequest.setScanIndexForward(false);
Future<QueryResult> fQueryResult = dynamoDB.queryAsync(queryRequest,
new AsyncHandler<QueryRequest,QueryResult>() {
public void onSuccess(QueryRequest request, QueryResult result) {
System.out.println("Table: " + result);
}
public void onError(Exception exception) {
System.out.println("Error describing table: " + exception.getMessage());
// Callers can also test if exception is an instance of
// AmazonServiceException or AmazonClientException and cast
// it to get additional information
}
});
System.out.println("Result: " + fQueryResult);
I get the following error
Error describing table: Either the KeyConditions or KeyConditionExpression parameter must be specified in the request. (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException;
Am I missing something? Thought I should be able to query an index just on the hash value.
You've misunderstood Local Indices.
A Query always takes a hash key. So your QueryRequest should have has a setHashKeyValue setup.
You can't cross-hash a query, not with hash+range primary key and not with LSI. You can do that with GSI but I'm not sure how this will help you here.
It is unclear what exactly you are trying to achieve. If you want all usernames then you need a Scan and not a Query - since you want all hash keys to be present.
I have a table of users with a primary hash key of userId. each user may/may not have a string attribute called "environment".
I would like to get all the users which have "environment"="xyz" or which do not have the "environment" attribute.
The following code will filter those users with environment=xyz, but how do I filter those items with no environment at all? Dynamo API will not allow to filter on an empty String.
AmazonDynamoDBClient client = DbClientManager.getDynamoDbClient();
ArrayList<AttributeValue> avList = new ArrayList<AttributeValue>();
avList.add(new AttributeValue().withS("xyz"));
Condition scanFilterCondition = new Condition()
.withComparisonOperator(ComparisonOperator.EQ.toString())
.withAttributeValueList(avList);
Map<String, Condition> conditions = new HashMap<>();
conditions.put("environment", scanFilterCondition);
ScanRequest scanRequest = new ScanRequest()
.withTableName("users")
.withAttributesToGet(
"userId",
"environment");
.withScanFilter(conditions);
ScanResult result = client.scan(scanRequest);
For now I just dropped the scan filter, and I do the filtering client-side. Bit is there any way to do it server side?
Thanks,
Aliza
Hope I'm not too late.
I've found useful function which you could use in the query. I did not check with ScanRequest but with QueryRequest works as charm.
QueryRequest queryRequest = new QueryRequest()
.withTableName("YouTableName")
queryRequest.setFilterExpression(" attribute_not_exists(yourAttributeName) ")
queryRequest.setExpressionAttributeValues(expressionAttributeValues)
queryRequest.setExclusiveStartKey(ifYouHave)
queryRequest.setSelect('ALL_ATTRIBUTES')
queryRequest.setExpressionAttributeNames(youNames)
attribute_not_exists(yourAttributeName) works with ":aws-sdk:1.11.11"
also you could use attribute_exists(yourAttributeName)
You need to use the NULL ComparisonOperator.
Check out this link: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html
NOT_NULL : The attribute exists.
NULL : The attribute does not exist.
Does this work for you?
I my case also, attribute_not_exists(attribute) worked. You can refer to this question:- How to check whether an attribute is not present in dynamoDB filter expression
for more details.