blob: 75de539dc33d4c221ef4f0689dde0642ecdc418f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.phoenix.ddb;
import java.sql.DriverManager;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeAction;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.AttributeValueUpdate;
import software.amazon.awssdk.services.dynamodb.model.ComparisonOperator;
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
import software.amazon.awssdk.services.dynamodb.model.ConditionalOperator;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ExpectedAttributeValue;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.phoenix.ddb.rest.RESTServer;
import org.apache.phoenix.end2end.ServerMetadataCacheTestImpl;
import org.apache.phoenix.jdbc.PhoenixDriver;
import org.apache.phoenix.util.PhoenixRuntime;
import org.apache.phoenix.util.ServerUtil;
import static org.apache.phoenix.query.BaseTest.setUpConfigForMiniCluster;
import static software.amazon.awssdk.services.dynamodb.model.ReturnValue.ALL_NEW;
/**
* Tests for UpdateItem API without conditional updates.
* Every test does 3 things:
* 1. Puts a row into the phoenix,ddb tables
* 2. Updates this row
* 3. Gets row from both phoenix and ddb to validate the update
* <p>
* {@link UpdateItemIT} has more tests for UpdateItem API.
*/
@RunWith(Parameterized.class)
public class UpdateItemBaseTests {
private static final Logger LOGGER = LoggerFactory.getLogger(UpdateItemBaseTests.class);
protected final DynamoDbClient dynamoDbClient =
LocalDynamoDbTestBase.localDynamoDb().createV2Client();
protected static DynamoDbClient phoenixDBClientV2;
private static String url;
private static HBaseTestingUtility utility = null;
private static String tmpDir;
private static RESTServer restServer = null;
@Rule
public final TestName testName = new TestName();
protected boolean isSortKeyPresent;
@BeforeClass
public static void initialize() throws Exception {
tmpDir = System.getProperty("java.io.tmpdir");
LocalDynamoDbTestBase.localDynamoDb().start();
Configuration conf = HBaseConfiguration.create();
utility = new HBaseTestingUtility(conf);
setUpConfigForMiniCluster(conf);
utility.startMiniCluster();
String zkQuorum = "localhost:" + utility.getZkCluster().getClientPort();
url = PhoenixRuntime.JDBC_PROTOCOL + PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR + zkQuorum;
restServer = new RESTServer(utility.getConfiguration());
restServer.run();
LOGGER.info("started {} on port {}", restServer.getClass().getName(), restServer.getPort());
phoenixDBClientV2 = LocalDynamoDB.createV2Client("http://" + restServer.getServerAddress());
}
@AfterClass
public static void stopLocalDynamoDb() throws Exception {
LocalDynamoDbTestBase.localDynamoDb().stop();
if (restServer != null) {
restServer.stop();
}
ServerUtil.ConnectionFactory.shutdown();
try {
DriverManager.deregisterDriver(PhoenixDriver.INSTANCE);
} finally {
if (utility != null) {
utility.shutdownMiniCluster();
}
ServerMetadataCacheTestImpl.resetCache();
}
System.setProperty("java.io.tmpdir", tmpDir);
}
@Parameters(name = "SortKey_{0}")
public static synchronized Collection<Boolean> data() {
return Arrays.asList(false, true);
}
public UpdateItemBaseTests(boolean isSortKeyPresent) {
this.isSortKeyPresent = isSortKeyPresent;
}
/**
* SET: Adds one or more attributes and values to an item. If any of these attributes already
* exist, they are replaced by the new values. You can also use SET to add or subtract from an
* attribute that is of type Number.
*/
@Test(timeout = 120000)
public void testSet1() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// update item
Map<String, AttributeValue> key = getKey();
UpdateItemRequest.Builder uir = UpdateItemRequest.builder().tableName(tableName).key(key);
uir.updateExpression(
"SET #1 = :v1, #2 = #2 + :v2, #3 = #3 - :v3, #4 = if_not_exists (#4, :v4)");
Map<String, String> exprAttrNames = new HashMap<>();
exprAttrNames.put("#1", "COL2");
exprAttrNames.put("#2", "COL1");
exprAttrNames.put("#3", "COL4");
exprAttrNames.put("#4", "NEWCOL");
uir.expressionAttributeNames(exprAttrNames);
Map<String, AttributeValue> exprAttrVal = new HashMap<>();
exprAttrVal.put(":v1", AttributeValue.builder().s("TiTlE2").build());
exprAttrVal.put(":v2", AttributeValue.builder().n("3.2").build());
exprAttrVal.put(":v3", AttributeValue.builder().n("89.34").build());
exprAttrVal.put(":v4",
AttributeValue.builder().b(SdkBytes.fromByteArray(new byte[] {1, -1})).build());
uir.expressionAttributeValues(exprAttrVal);
dynamoDbClient.updateItem(uir.build());
phoenixDBClientV2.updateItem(uir.build());
validateItem(tableName, key);
}
/**
* SET: test arithmetic operations with if_not_exists function.
*/
@Test(timeout = 120000)
public void testSet2() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// update item
Map<String, AttributeValue> key = getKey();
UpdateItemRequest.Builder uir = UpdateItemRequest.builder().tableName(tableName).key(key);
uir.updateExpression("SET #1 = if_not_exists(#1, :v100) + :v1, " +
"#4 = :v100 - if_not_exists( #4, :v100), " +
"#n1 = if_not_exists(#n1, :v0) + :v100, " +
"#n2 = if_not_exists(#n2, :v110)- :v10, " +
"#n5 = if_not_exists(#n5, :v10) + if_not_exists(#5, :v0), " +
"NEWCOL6 = if_not_exists(NEWCOL6, :v10)");
Map<String, String> exprAttrNames = new HashMap<>();
exprAttrNames.put("#1", "COL1");
exprAttrNames.put("#4", "COL4");
exprAttrNames.put("#n1", "NEWCOL1");
exprAttrNames.put("#n2", "NEWCOL2");
exprAttrNames.put("#5", "COL5");
exprAttrNames.put("#n5", "NEWCOL5");
uir.expressionAttributeNames(exprAttrNames);
Map<String, AttributeValue> exprAttrVal = new HashMap<>();
exprAttrVal.put(":v1", AttributeValue.builder().n("1").build());
exprAttrVal.put(":v10", AttributeValue.builder().n("10").build());
exprAttrVal.put(":v100", AttributeValue.builder().n("100").build());
exprAttrVal.put(":v110", AttributeValue.builder().n("110").build());
exprAttrVal.put(":v0", AttributeValue.builder().n("0").build());
uir.expressionAttributeValues(exprAttrVal);
dynamoDbClient.updateItem(uir.build());
phoenixDBClientV2.updateItem(uir.build());
validateItem(tableName, key);
}
/**
* REMOVE - Removes one or more attributes from an item.
*/
@Test(timeout = 120000)
public void testRemove() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// update item
Map<String, AttributeValue> key = getKey();
UpdateItemRequest.Builder uir = UpdateItemRequest.builder().tableName(tableName).key(key);
uir.updateExpression("REMOVE #1.#2[0], #3");
Map<String, String> exprAttrNames = new HashMap<>();
exprAttrNames.put("#1", "Reviews");
exprAttrNames.put("#2", "FiveStar");
exprAttrNames.put("#3", "COL1");
uir.expressionAttributeNames(exprAttrNames);
dynamoDbClient.updateItem(uir.build());
phoenixDBClientV2.updateItem(uir.build());
validateItem(tableName, key);
}
/**
* The ADD action only supports Number and set data types.
* In addition, ADD can only be used on top-level attributes, not nested attributes.
* Both sets must have the same primitive data type.
* <p>
* TODO: test with add fails if item had Double to begin with,
* e.g. 34.15 + 89.21, ddb --> 123.36, phoenix--> 123.35999999999999
* but 34 + 89.21, ddb=phoenix-->123.21
*/
@Test(timeout = 120000)
public void testAdd() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// update item
Map<String, AttributeValue> key = getKey();
UpdateItemRequest.Builder uir = UpdateItemRequest.builder().tableName(tableName).key(key);
uir.updateExpression("ADD #2 :v2, #3 :v3, #4 :v4");
Map<String, String> exprAttrNames = new HashMap<>();
exprAttrNames.put("#2", "COL1");
exprAttrNames.put("#3", "COL4");
exprAttrNames.put("#4", "TopLevelSet");
uir.expressionAttributeNames(exprAttrNames);
Map<String, AttributeValue> exprAttrVal = new HashMap<>();
exprAttrVal.put(":v2", AttributeValue.builder().n("-3.2").build());
exprAttrVal.put(":v3", AttributeValue.builder().n("89.21").build());
exprAttrVal.put(":v4", AttributeValue.builder().ss("setMember2").build());
uir.expressionAttributeValues(exprAttrVal);
dynamoDbClient.updateItem(uir.build());
phoenixDBClientV2.updateItem(uir.build());
validateItem(tableName, key);
}
/**
* The DELETE action only supports set data types.
* In addition, DELETE can only be used on top-level attributes, not nested attributes.
*/
@Test(timeout = 120000)
public void testDelete() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// update item
Map<String, AttributeValue> key = getKey();
UpdateItemRequest.Builder uir = UpdateItemRequest.builder().tableName(tableName).key(key);
uir.updateExpression("DELETE #4 :v4");
Map<String, String> exprAttrNames = new HashMap<>();
exprAttrNames.put("#4", "TopLevelSet");
uir.expressionAttributeNames(exprAttrNames);
Map<String, AttributeValue> exprAttrVal = new HashMap<>();
exprAttrVal.put(":v4", AttributeValue.builder().ss("setMember1", "setMember2").build());
uir.expressionAttributeValues(exprAttrVal);
dynamoDbClient.updateItem(uir.build());
phoenixDBClientV2.updateItem(uir.build());
validateItem(tableName, key);
}
@Test(timeout = 120000)
public void testSetDelete() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// update item
Map<String, AttributeValue> key = getKey();
UpdateItemRequest.Builder uir = UpdateItemRequest.builder().tableName(tableName).key(key);
uir.updateExpression("SET #1 = :v1, #2 = #2 + :v2 DELETE #4 :v4 ");
Map<String, String> exprAttrNames = new HashMap<>();
exprAttrNames.put("#1", "COL2");
exprAttrNames.put("#2", "COL1");
exprAttrNames.put("#4", "TopLevelSet");
uir.expressionAttributeNames(exprAttrNames);
Map<String, AttributeValue> exprAttrVal = new HashMap<>();
exprAttrVal.put(":v1", AttributeValue.builder().s("TiTlE2").build());
exprAttrVal.put(":v2", AttributeValue.builder().n("3.2").build());
exprAttrVal.put(":v4", AttributeValue.builder().ss("setMember1").build());
uir.expressionAttributeValues(exprAttrVal);
dynamoDbClient.updateItem(uir.build());
phoenixDBClientV2.updateItem(uir.build());
validateItem(tableName, key);
}
@Test(timeout = 120000)
public void testRemoveAdd() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// update item
Map<String, AttributeValue> key = getKey();
UpdateItemRequest.Builder uir = UpdateItemRequest.builder().tableName(tableName).key(key);
uir.updateExpression("REMOVE #3, #1.#2[0] ADD #4 :v4, #5 :v5");
Map<String, String> exprAttrNames = new HashMap<>();
exprAttrNames.put("#1", "Reviews");
exprAttrNames.put("#2", "FiveStar");
exprAttrNames.put("#3", "COL1");
exprAttrNames.put("#4", "TopLevelSet");
exprAttrNames.put("#5", "COL4");
uir.expressionAttributeNames(exprAttrNames);
Map<String, AttributeValue> exprAttrVal = new HashMap<>();
exprAttrVal.put(":v5", AttributeValue.builder().n("-3.2").build());
exprAttrVal.put(":v4", AttributeValue.builder().ss("setMember2").build());
uir.expressionAttributeValues(exprAttrVal);
dynamoDbClient.updateItem(uir.build());
phoenixDBClientV2.updateItem(uir.build());
validateItem(tableName, key);
}
@Test(timeout = 120000)
public void testAddSetRemove() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// update item
Map<String, AttributeValue> key = getKey();
UpdateItemRequest.Builder uir = UpdateItemRequest.builder().tableName(tableName).key(key);
uir.updateExpression("ADD #1 :v1, #2 :v2 SET #3 = :v3, #4 = #4 - :v4 REMOVE #5");
Map<String, String> exprAttrNames = new HashMap<>();
exprAttrNames.put("#1", "COL1");
exprAttrNames.put("#2", "TopLevelSet");
exprAttrNames.put("#3", "COL2");
exprAttrNames.put("#4", "COL4");
exprAttrNames.put("#5", "COL3");
uir.expressionAttributeNames(exprAttrNames);
Map<String, AttributeValue> exprAttrVal = new HashMap<>();
exprAttrVal.put(":v1", AttributeValue.builder().n("-3.2").build());
exprAttrVal.put(":v2", AttributeValue.builder().ss("setMember2").build());
exprAttrVal.put(":v3", AttributeValue.builder().s("TiTlE2").build());
exprAttrVal.put(":v4", AttributeValue.builder().n("-3").build());
uir.expressionAttributeValues(exprAttrVal);
dynamoDbClient.updateItem(uir.build());
phoenixDBClientV2.updateItem(uir.build());
validateItem(tableName, key);
}
@Test(timeout = 120000)
public void testDeleteRemoveSetAdd() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// update item
Map<String, AttributeValue> key = getKey();
UpdateItemRequest.Builder uir = UpdateItemRequest.builder().tableName(tableName).key(key);
uir.updateExpression(
"DELETE #1 :v1 REMOVE #2 ADD #3 :v3 SET #5 = if_not_exists(#6, :v5), #4 = :v4");
Map<String, String> exprAttrNames = new HashMap<>();
exprAttrNames.put("#1", "TopLevelSet");
exprAttrNames.put("#2", "Reviews");
exprAttrNames.put("#3", "COL1");
exprAttrNames.put("#4", "COL3");
exprAttrNames.put("#5", "NEWCOL");
exprAttrNames.put("#6", "COL2");
uir.expressionAttributeNames(exprAttrNames);
Map<String, AttributeValue> exprAttrVal = new HashMap<>();
exprAttrVal.put(":v1", AttributeValue.builder().ss("setMember2").build());
exprAttrVal.put(":v3", AttributeValue.builder().n("1000000").build());
exprAttrVal.put(":v4", AttributeValue.builder().s("dEsCrIpTiOn1").build());
exprAttrVal.put(":v5", AttributeValue.builder().s("Bob").build());
uir.expressionAttributeValues(exprAttrVal);
uir.returnValues(ALL_NEW);
UpdateItemResponse dynamoResult = dynamoDbClient.updateItem(uir.build());
UpdateItemResponse phoenixResult = phoenixDBClientV2.updateItem(uir.build());
Assert.assertEquals(dynamoResult.attributes(), phoenixResult.attributes());
validateItem(tableName, key);
}
protected void createTableAndPutItem(String tableName, boolean putItem) {
//create table
CreateTableRequest createTableRequest;
if (isSortKeyPresent) {
createTableRequest =
DDLTestUtils.getCreateTableRequest(tableName, "PK1", ScalarAttributeType.S,
"PK2", ScalarAttributeType.N);
} else {
createTableRequest =
DDLTestUtils.getCreateTableRequest(tableName, "PK1", ScalarAttributeType.S,
null, null);
}
phoenixDBClientV2.createTable(createTableRequest);
dynamoDbClient.createTable(createTableRequest);
if (putItem) {
Map<String, AttributeValue> item = getItem1();
PutItemRequest putItemRequest =
PutItemRequest.builder().tableName(tableName).item(item).build();
phoenixDBClientV2.putItem(putItemRequest);
dynamoDbClient.putItem(putItemRequest);
}
}
protected void validateItem(String tableName, Map<String, AttributeValue> key) {
GetItemRequest gir = GetItemRequest.builder().tableName(tableName).key(key).build();
GetItemResponse phoenixResult = phoenixDBClientV2.getItem(gir);
GetItemResponse dynamoResult = dynamoDbClient.getItem(gir);
Assert.assertEquals(dynamoResult.item(), phoenixResult.item());
}
protected Map<String, AttributeValue> getItem1() {
Map<String, AttributeValue> item = new HashMap<>();
item.put("PK1", AttributeValue.builder().s("A").build());
item.put("PK2", AttributeValue.builder().n("1").build());
item.put("COL1", AttributeValue.builder().n("1").build());
item.put("COL2", AttributeValue.builder().s("Title1").build());
item.put("COL3", AttributeValue.builder().s("Description1").build());
item.put("COL4", AttributeValue.builder().n("34").build());
item.put("COL5", AttributeValue.builder().n("67").build());
item.put("TopLevelSet", AttributeValue.builder().ss("setMember1").build());
Map<String, AttributeValue> reviewMap1 = new HashMap<>();
reviewMap1.put("reviewer", AttributeValue.builder().s("Alice").build());
Map<String, AttributeValue> fiveStarMap = new HashMap<>();
fiveStarMap.put("FiveStar",
AttributeValue.builder().l(AttributeValue.builder().m(reviewMap1).build()).build());
item.put("Reviews", AttributeValue.builder().m(fiveStarMap).build());
return item;
}
/**
* Test AttributeUpdates legacy parameter support with various data types.
* This tests that the legacy AttributeUpdates parameter works equivalently to UpdateExpression
* with comprehensive coverage of different data types and actions.
*/
@Test(timeout = 120000)
public void testAttributeUpdates() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
// Create update request using legacy AttributeUpdates parameter
Map<String, AttributeValue> key = getKey();
// Build AttributeUpdates map with various data types and actions
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
// PUT action with String (equivalent to SET)
attributeUpdates.put("COL2", AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedTitle").build()).build());
// PUT action with Number
attributeUpdates.put("NewNumberField",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().n("42.5").build()).build());
// PUT action with Boolean
attributeUpdates.put("NewBooleanField",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().bool(true).build()).build());
// PUT action with Binary
attributeUpdates.put("NewBinaryField",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder()
.b(SdkBytes.fromByteArray(new byte[] {1, 2, 3})).build()).build());
// PUT action with String Set
attributeUpdates.put("NewStringSet",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().ss("value1", "value2", "value3").build())
.build());
// PUT action with Number Set
attributeUpdates.put("NewNumberSet",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().ns("1", "2", "3").build()).build());
// PUT action with Map (nested object)
Map<String, AttributeValue> nestedMap = new HashMap<>();
nestedMap.put("nestedString", AttributeValue.builder().s("nested value").build());
nestedMap.put("nestedNumber", AttributeValue.builder().n("100").build());
attributeUpdates.put("NewMapField",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().m(nestedMap).build()).build());
// PUT action with List
attributeUpdates.put("NewListField",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder()
.l(AttributeValue.builder().s("item1").build(),
AttributeValue.builder().n("42").build(),
AttributeValue.builder().bool(false).build()).build())
.build());
// ADD action with Number
attributeUpdates.put("COL1", AttributeValueUpdate.builder().action(AttributeAction.ADD)
.value(AttributeValue.builder().n("5.5").build()).build());
// ADD action with String Set (should add to existing set)
attributeUpdates.put("AddToStringSet",
AttributeValueUpdate.builder().action(AttributeAction.ADD)
.value(AttributeValue.builder().ss("newValue1", "newValue2").build())
.build());
// ADD action with Number Set
attributeUpdates.put("AddToNumberSet",
AttributeValueUpdate.builder().action(AttributeAction.ADD)
.value(AttributeValue.builder().ns("10", "20").build()).build());
// DELETE action without value (remove entire attribute)
attributeUpdates.put("COL4",
AttributeValueUpdate.builder().action(AttributeAction.DELETE).build());
// DELETE action with String Set value (remove specific values from set)
attributeUpdates.put("DeleteFromStringSet",
AttributeValueUpdate.builder().action(AttributeAction.DELETE)
.value(AttributeValue.builder().ss("removeMe").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).build();
// Execute update with AttributeUpdates on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test AttributeUpdates with default PUT action (when Action is not specified).
*/
@Test(timeout = 120000)
public void testAttributeUpdatesDefaultAction() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build AttributeUpdates without specifying Action (should default to PUT)
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("DefaultActionField", AttributeValueUpdate.builder()
// No action specified - should default to PUT
.value(AttributeValue.builder().s("DefaultPutValue").build()).build());
attributeUpdates.put("COL2", AttributeValueUpdate.builder()
// No action specified - should default to PUT
.value(AttributeValue.builder().s("OverwrittenTitle").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).build();
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
validateItem(tableName, key);
}
/**
* Test AttributeUpdates validation - should reject when both UpdateExpression and AttributeUpdates are provided.
*/
@Test(timeout = 120000)
public void testAttributeUpdatesValidation() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("test", AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("value").build()).build());
// This should fail - cannot specify both UpdateExpression and AttributeUpdates
Map<String, AttributeValue> exprAttrValues = new HashMap<>();
exprAttrValues.put(":val", AttributeValue.builder().s("value").build());
UpdateItemRequest invalidRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.updateExpression("SET testField = :val").expressionAttributeValues(exprAttrValues)
.attributeUpdates(attributeUpdates).build();
// Should throw ValidationException for both DynamoDB and Phoenix
try {
dynamoDbClient.updateItem(invalidRequest);
Assert.fail("Expected ValidationException for DynamoDB");
} catch (DynamoDbException e) {
Assert.assertEquals(400, e.statusCode());
}
try {
phoenixDBClientV2.updateItem(invalidRequest);
Assert.fail("Expected ValidationException for Phoenix");
} catch (DynamoDbException e) {
Assert.assertEquals(400, e.statusCode());
}
}
/**
* Test legacy Expected parameter support with various comparison operators.
* This tests that the legacy Expected parameter works equivalently to ConditionExpression.
*/
@Test(timeout = 120000)
public void testExpectedParameter() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with various comparison operators
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// EQ condition (default operator) - COL1 has value "1"
expected.put("COL1",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("1").build())
.build());
// EXISTS condition (using NOT_NULL comparison operator)
expected.put("COL2",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NOT_NULL)
.build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("COL2", AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithExpected").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected).build();
// Execute update with Expected on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with ConditionalOperator OR.
*/
@Test(timeout = 120000)
public void testExpectedParameterWithOrOperator() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with OR operator
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// Condition 1: COL1 equals a different value (should be false)
expected.put("COL1",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("999").build())
.comparisonOperator(ComparisonOperator.EQ).build());
// Condition 2: COL2 exists (should be true)
expected.put("COL2",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NOT_NULL)
.build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("COL2", AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithOrCondition").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected)
.conditionalOperator(ConditionalOperator.OR) // OR operator
.build();
// Execute update with Expected OR on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with various comparison operators.
*/
@Test(timeout = 120000)
public void testExpectedParameterComparisonOperators() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with different comparison operators
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// GT condition (COL4 > 30) - COL4 has value 34
expected.put("COL4",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("30").build())
.comparisonOperator(ComparisonOperator.GT).build());
// BEGINS_WITH condition (COL2 begins with "Ti") - COL2 has value "Title1"
expected.put("COL2",
ExpectedAttributeValue.builder().value(AttributeValue.builder().s("Ti").build())
.comparisonOperator(ComparisonOperator.BEGINS_WITH).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("NewField", AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("AddedByComparison").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected)
.conditionalOperator(ConditionalOperator.AND) // AND operator (default)
.build();
// Execute update with Expected comparison operators on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter validation - should reject when both ConditionExpression and Expected are provided.
*/
@Test(timeout = 120000)
public void testExpectedParameterValidation() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
expected.put("COL1",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("1").build())
.build());
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("test", AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("value").build()).build());
// This should fail - cannot specify both ConditionExpression and Expected
UpdateItemRequest invalidRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.conditionExpression("attribute_exists(COL1)").expected(expected)
.attributeUpdates(attributeUpdates).build();
try {
dynamoDbClient.updateItem(invalidRequest);
Assert.fail("Expected ValidationException for DynamoDB");
} catch (DynamoDbException e) {
Assert.assertEquals(400, e.statusCode());
}
try {
phoenixDBClientV2.updateItem(invalidRequest);
Assert.fail("Expected ValidationException for Phoenix");
} catch (DynamoDbException e) {
Assert.assertEquals(400, e.statusCode());
}
}
/**
* Test legacy Expected parameter with exists(true) and exists(false) conditions.
*/
@Test(timeout = 120000)
public void testExpectedParameterExistsConditions1() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with exists conditions
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// exists(true) - attribute should exist
expected.put("COL2", ExpectedAttributeValue.builder().exists(true)
.value(AttributeValue.builder().s("1").build()).build());
// exists(false) - attribute should not exist
expected.put("NonExistentField", ExpectedAttributeValue.builder().exists(false).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("UpdatedField",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithExists").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected)
.conditionalOperator(ConditionalOperator.AND).build();
try {
dynamoDbClient.updateItem(updateRequest);
Assert.fail("DeleteItem should throw exception when condition check fails.");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
try {
phoenixDBClientV2.updateItem(updateRequest);
Assert.fail("DeleteItem should throw exception when condition check fails.");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with exists(true) and exists(false) conditions.
*/
@Test(timeout = 120000)
public void testExpectedParameterExistsConditions2() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with exists conditions
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// exists(true) - attribute should exist
expected.put("COL2", ExpectedAttributeValue.builder().exists(true)
.value(AttributeValue.builder().s("Title1").build()).build());
// exists(false) - attribute should not exist
expected.put("NonExistentField", ExpectedAttributeValue.builder().exists(false).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("UpdatedField",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithExists").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected)
.conditionalOperator(ConditionalOperator.AND).build();
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with BETWEEN operator.
*/
@Test(timeout = 120000)
public void testExpectedParameterBetweenOperator() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with BETWEEN operator
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// BETWEEN condition (COL4 BETWEEN 30 AND 40) - COL4 has value 34
expected.put("COL4", ExpectedAttributeValue.builder().attributeValueList(
Arrays.asList(AttributeValue.builder().n("30").build(),
AttributeValue.builder().n("40").build()))
.comparisonOperator(ComparisonOperator.BETWEEN).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("BetweenTest",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithBetween").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected).build();
// Execute update with BETWEEN condition on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with IN operator.
*/
@Test(timeout = 120000)
public void testExpectedParameterInOperator() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with IN operator
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// IN condition (COL2 IN ["Title1", "Title2", "Title3"]) - COL2 has value "Title1"
expected.put("COL2", ExpectedAttributeValue.builder().attributeValueList(
Arrays.asList(AttributeValue.builder().s("Title1").build(),
AttributeValue.builder().s("Title2").build(),
AttributeValue.builder().s("Title3").build()))
.comparisonOperator(ComparisonOperator.IN).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("InTest", AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithIn").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected).build();
// Execute update with IN condition on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with CONTAINS and NOT_CONTAINS operators.
*/
@Test(timeout = 120000)
public void testExpectedParameterContainsOperators() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with CONTAINS operators
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// CONTAINS condition (COL2 CONTAINS "itle") - COL2 has value "Title1"
expected.put("COL2",
ExpectedAttributeValue.builder().value(AttributeValue.builder().s("itle").build())
.comparisonOperator(ComparisonOperator.CONTAINS).build());
// NOT_CONTAINS condition (COL3 NOT_CONTAINS "xyz") - COL3 has value "Description1"
expected.put("COL3",
ExpectedAttributeValue.builder().value(AttributeValue.builder().s("xyz").build())
.comparisonOperator(ComparisonOperator.NOT_CONTAINS).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("ContainsTest",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithContains").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected)
.conditionalOperator(ConditionalOperator.AND).build();
// Execute update with CONTAINS conditions on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with all numeric comparison operators.
*/
@Test(timeout = 120000)
public void testExpectedParameterNumericComparisons() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with numeric comparison operators
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// LT condition (COL4 < 40) - COL4 has value 34
expected.put("COL4",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("40").build())
.comparisonOperator(ComparisonOperator.LT).build());
// GE condition (COL1 >= 1) - COL1 has value 1
expected.put("COL1",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("1").build())
.comparisonOperator(ComparisonOperator.GE).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("NumericTest",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithNumeric").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected)
.conditionalOperator(ConditionalOperator.AND).build();
// Execute update with numeric comparisons on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with NE (not equal) operator.
*/
@Test(timeout = 120000)
public void testExpectedParameterNotEqualOperator() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with NE operator
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// NE condition (COL2 != "WrongTitle") - COL2 has value "Title1"
expected.put("COL2", ExpectedAttributeValue.builder()
.value(AttributeValue.builder().s("WrongTitle").build())
.comparisonOperator(ComparisonOperator.NE).build());
// NE condition (COL1 != 999) - COL1 has value 1
expected.put("COL1",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("999").build())
.comparisonOperator(ComparisonOperator.NE).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("NotEqualTest",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithNotEqual").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected)
.conditionalOperator(ConditionalOperator.AND).build();
// Execute update with NE conditions on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with complex OR conditions.
*/
@Test(timeout = 120000)
public void testExpectedParameterComplexOrConditions() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with complex OR conditions
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// Condition 1: COL1 = 999 (should be false, COL1 = 1)
expected.put("COL1",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("999").build())
.comparisonOperator(ComparisonOperator.EQ).build());
// Condition 2: COL2 BEGINS_WITH "Wrong" (should be false, COL2 = "Title1")
expected.put("COL2",
ExpectedAttributeValue.builder().value(AttributeValue.builder().s("Wrong").build())
.comparisonOperator(ComparisonOperator.BEGINS_WITH).build());
// Condition 3: COL4 > 30 (should be true, COL4 = 34)
expected.put("COL4",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("30").build())
.comparisonOperator(ComparisonOperator.GT).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("ComplexOrTest",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("UpdatedWithComplexOr").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected).conditionalOperator(
ConditionalOperator.OR) // OR - only one condition needs to be true
.build();
// Execute update with complex OR conditions on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter failure case - condition should fail.
*/
@Test(timeout = 120000)
public void testExpectedParameterConditionFailure() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with conditions that should fail
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// Condition that will fail: COL1 = 999 (but COL1 = 1)
expected.put("COL1",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("999").build())
.comparisonOperator(ComparisonOperator.EQ).build());
// Build AttributeUpdates for the update
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
attributeUpdates.put("ShouldNotUpdate",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("ShouldNotBeSet").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected).build();
// Both DynamoDB and Phoenix should throw ConditionalCheckFailedException
try {
dynamoDbClient.updateItem(updateRequest);
Assert.fail("Expected ConditionalCheckFailedException for DynamoDB");
} catch (Exception e) {
Assert.assertTrue("Expected ConditionalCheckFailedException",
e.getMessage().contains("conditional") || e.getMessage()
.contains("Conditional"));
}
try {
phoenixDBClientV2.updateItem(updateRequest);
Assert.fail("Expected ConditionalCheckFailedException for Phoenix");
} catch (Exception e) {
Assert.assertTrue("Expected ConditionalCheckFailedException",
e.getMessage().contains("conditional") || e.getMessage()
.contains("Conditional"));
}
}
/**
* Test legacy Expected parameter with mixed data types and complex conditions.
*/
@Test(timeout = 120000)
public void testExpectedParameterMixedDataTypes() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Build Expected map with mixed data types
Map<String, ExpectedAttributeValue> expected = new HashMap<>();
// Set existence check
expected.put("TopLevelSet",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NOT_NULL)
.build());
// String comparison
expected.put("COL2",
ExpectedAttributeValue.builder().value(AttributeValue.builder().s("Title1").build())
.comparisonOperator(ComparisonOperator.EQ).build());
// Number comparison
expected.put("COL4",
ExpectedAttributeValue.builder().value(AttributeValue.builder().n("35").build())
.comparisonOperator(ComparisonOperator.LE) // 34 <= 35
.build());
// Build AttributeUpdates with mixed operations
Map<String, AttributeValueUpdate> attributeUpdates = new HashMap<>();
// PUT operation
attributeUpdates.put("MixedTest1",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("MixedDataTest").build()).build());
// ADD operation to existing number
attributeUpdates.put("COL1", AttributeValueUpdate.builder().action(AttributeAction.ADD)
.value(AttributeValue.builder().n("10").build()).build());
// ADD to existing set
attributeUpdates.put("TopLevelSet",
AttributeValueUpdate.builder().action(AttributeAction.ADD)
.value(AttributeValue.builder().ss("newSetMember").build()).build());
UpdateItemRequest updateRequest = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates).expected(expected)
.conditionalOperator(ConditionalOperator.AND).build();
// Execute update with mixed data types on both DynamoDB and Phoenix
dynamoDbClient.updateItem(updateRequest);
phoenixDBClientV2.updateItem(updateRequest);
// Validate that both produce the same result
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with NOT_NULL and NULL comparison operators.
* Tests both positive (should succeed) and negative (should fail) cases.
*/
@Test(timeout = 120000)
public void testExpectedParameterNotNullAndNullOperators() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Test 1: NOT_NULL on existing attribute (should succeed)
Map<String, ExpectedAttributeValue> expected1 = new HashMap<>();
expected1.put("COL2",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NOT_NULL)
.build());
Map<String, AttributeValueUpdate> attributeUpdates1 = new HashMap<>();
attributeUpdates1.put("TestField1",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("NotNullTest").build()).build());
UpdateItemRequest updateRequest1 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates1).expected(expected1).build();
// Both DynamoDB and Phoenix should succeed
dynamoDbClient.updateItem(updateRequest1);
phoenixDBClientV2.updateItem(updateRequest1);
// Test 2: NULL on non-existing attribute (should succeed)
Map<String, ExpectedAttributeValue> expected2 = new HashMap<>();
expected2.put("NonExistentField",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NULL)
.build());
Map<String, AttributeValueUpdate> attributeUpdates2 = new HashMap<>();
attributeUpdates2.put("TestField2",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("NullTest").build()).build());
UpdateItemRequest updateRequest2 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates2).expected(expected2).build();
// Both DynamoDB and Phoenix should succeed
dynamoDbClient.updateItem(updateRequest2);
phoenixDBClientV2.updateItem(updateRequest2);
// Test 3: NOT_NULL on non-existing attribute (should fail)
Map<String, ExpectedAttributeValue> expected3 = new HashMap<>();
expected3.put("AnotherNonExistentField",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NOT_NULL)
.build());
Map<String, AttributeValueUpdate> attributeUpdates3 = new HashMap<>();
attributeUpdates3.put("ShouldNotUpdate1",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("ShouldNotBeSet").build()).build());
UpdateItemRequest updateRequest3 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates3).expected(expected3).build();
// Both DynamoDB and Phoenix should throw ConditionalCheckFailedException
try {
dynamoDbClient.updateItem(updateRequest3);
Assert.fail(
"Expected ConditionalCheckFailedException for DynamoDB (NOT_NULL on non-existing)");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
try {
phoenixDBClientV2.updateItem(updateRequest3);
Assert.fail(
"Expected ConditionalCheckFailedException for Phoenix (NOT_NULL on non-existing)");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
// Test 4: NULL on existing attribute (should fail)
Map<String, ExpectedAttributeValue> expected4 = new HashMap<>();
expected4.put("COL1",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NULL)
.build());
Map<String, AttributeValueUpdate> attributeUpdates4 = new HashMap<>();
attributeUpdates4.put("ShouldNotUpdate2",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("ShouldNotBeSet").build()).build());
UpdateItemRequest updateRequest4 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates4).expected(expected4).build();
// Both DynamoDB and Phoenix should throw ConditionalCheckFailedException
try {
dynamoDbClient.updateItem(updateRequest4);
Assert.fail("Expected ConditionalCheckFailedException for DynamoDB (NULL on existing)");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
try {
phoenixDBClientV2.updateItem(updateRequest4);
Assert.fail("Expected ConditionalCheckFailedException for Phoenix (NULL on existing)");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
// Test 5: Complex condition with NOT_NULL and NULL using OR operator
Map<String, ExpectedAttributeValue> expected5 = new HashMap<>();
expected5.put("COL2",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NOT_NULL)
.build());
expected5.put("NonExistentField2",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NULL)
.build());
Map<String, AttributeValueUpdate> attributeUpdates5 = new HashMap<>();
attributeUpdates5.put("TestField3",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("ComplexTest").build()).build());
UpdateItemRequest updateRequest5 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates5).expected(expected5)
.conditionalOperator(ConditionalOperator.OR).build();
// Both DynamoDB and Phoenix should succeed (COL2 exists OR NonExistentField2 doesn't exist)
dynamoDbClient.updateItem(updateRequest5);
phoenixDBClientV2.updateItem(updateRequest5);
// Validate final state
validateItem(tableName, key);
}
/**
* Test legacy Expected parameter with comparison operators using AttributeValueList.
* Tests EQ, LT, GE, etc. with AttributeValueList instead of single Value field.
*/
@Test(timeout = 120000)
public void testExpectedParameterWithAttributeValueList() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);
Map<String, AttributeValue> key = getKey();
// Test 1: EQ with AttributeValueList (should succeed)
Map<String, ExpectedAttributeValue> expected1 = new HashMap<>();
expected1.put("COL1",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.EQ)
.attributeValueList(AttributeValue.builder().n("1").build()).build());
Map<String, AttributeValueUpdate> attributeUpdates1 = new HashMap<>();
attributeUpdates1.put("TestField1",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("EQTest").build()).build());
UpdateItemRequest updateRequest1 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates1).expected(expected1).build();
// Both DynamoDB and Phoenix should succeed (COL1 = 1)
dynamoDbClient.updateItem(updateRequest1);
phoenixDBClientV2.updateItem(updateRequest1);
// Test 2: LT with AttributeValueList (should succeed)
Map<String, ExpectedAttributeValue> expected2 = new HashMap<>();
expected2.put("COL4",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.LT)
.attributeValueList(AttributeValue.builder().n("50").build()).build());
Map<String, AttributeValueUpdate> attributeUpdates2 = new HashMap<>();
attributeUpdates2.put("TestField2",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("LTTest").build()).build());
UpdateItemRequest updateRequest2 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates2).expected(expected2).build();
// Both DynamoDB and Phoenix should succeed (COL4 = 42 < 50)
dynamoDbClient.updateItem(updateRequest2);
phoenixDBClientV2.updateItem(updateRequest2);
// Test 3: GE with AttributeValueList (should succeed)
Map<String, ExpectedAttributeValue> expected3 = new HashMap<>();
expected3.put("COL4",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.GE)
.attributeValueList(AttributeValue.builder().n("40").build()).build());
Map<String, AttributeValueUpdate> attributeUpdates3 = new HashMap<>();
attributeUpdates3.put("TestField3",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("GETest").build()).build());
UpdateItemRequest updateRequest3 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates3).expected(expected3).build();
try {
dynamoDbClient.updateItem(updateRequest3);
Assert.fail("Should have thrown an exception");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
try {
phoenixDBClientV2.updateItem(updateRequest3);
Assert.fail("Should have thrown an exception");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
// Test 4: NE with AttributeValueList (should succeed)
Map<String, ExpectedAttributeValue> expected4 = new HashMap<>();
expected4.put("COL1",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.NE)
.attributeValueList(AttributeValue.builder().n("999").build()).build());
Map<String, AttributeValueUpdate> attributeUpdates4 = new HashMap<>();
attributeUpdates4.put("TestField4",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("NETest").build()).build());
UpdateItemRequest updateRequest4 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates4).expected(expected4).build();
// Both DynamoDB and Phoenix should succeed (COL1 = 1 != 999)
dynamoDbClient.updateItem(updateRequest4);
phoenixDBClientV2.updateItem(updateRequest4);
// Test 5: BEGINS_WITH with AttributeValueList (should succeed)
Map<String, ExpectedAttributeValue> expected5 = new HashMap<>();
expected5.put("COL2",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.BEGINS_WITH)
.attributeValueList(AttributeValue.builder().s("1").build()).build());
Map<String, AttributeValueUpdate> attributeUpdates5 = new HashMap<>();
attributeUpdates5.put("TestField5",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("BeginsWithTest").build()).build());
UpdateItemRequest updateRequest5 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates5).expected(expected5).build();
try {
dynamoDbClient.updateItem(updateRequest5);
Assert.fail("Should have thrown an exception");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
try {
phoenixDBClientV2.updateItem(updateRequest5);
Assert.fail("Should have thrown an exception");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
// Test 6: CONTAINS with AttributeValueList (should succeed)
Map<String, ExpectedAttributeValue> expected6 = new HashMap<>();
expected6.put("COL2",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.CONTAINS)
.attributeValueList(AttributeValue.builder().s("1").build()).build());
Map<String, AttributeValueUpdate> attributeUpdates6 = new HashMap<>();
attributeUpdates6.put("TestField6",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("ContainsTest").build()).build());
UpdateItemRequest updateRequest6 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates6).expected(expected6).build();
// Both DynamoDB and Phoenix should succeed (COL2 = "1" contains "1")
dynamoDbClient.updateItem(updateRequest6);
phoenixDBClientV2.updateItem(updateRequest6);
// Test 7: Failure case - EQ with AttributeValueList (should fail)
Map<String, ExpectedAttributeValue> expected7 = new HashMap<>();
expected7.put("COL1",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.EQ)
.attributeValueList(AttributeValue.builder().n("999").build()).build());
Map<String, AttributeValueUpdate> attributeUpdates7 = new HashMap<>();
attributeUpdates7.put("ShouldNotUpdate",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("ShouldNotBeSet").build()).build());
UpdateItemRequest updateRequest7 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates7).expected(expected7).build();
// Both DynamoDB and Phoenix should throw ConditionalCheckFailedException
try {
dynamoDbClient.updateItem(updateRequest7);
Assert.fail(
"Expected ConditionalCheckFailedException for DynamoDB (EQ with wrong value)");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
try {
phoenixDBClientV2.updateItem(updateRequest7);
Assert.fail(
"Expected ConditionalCheckFailedException for Phoenix (EQ with wrong value)");
} catch (ConditionalCheckFailedException e) {
Assert.assertEquals(400, e.statusCode());
}
// Test 8: Complex condition with multiple AttributeValueList operators using AND
Map<String, ExpectedAttributeValue> expected8 = new HashMap<>();
expected8.put("COL1",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.GE)
.attributeValueList(AttributeValue.builder().n("1").build()).build());
expected8.put("COL4",
ExpectedAttributeValue.builder().comparisonOperator(ComparisonOperator.LE)
.attributeValueList(AttributeValue.builder().n("50").build()).build());
Map<String, AttributeValueUpdate> attributeUpdates8 = new HashMap<>();
attributeUpdates8.put("TestField8",
AttributeValueUpdate.builder().action(AttributeAction.PUT)
.value(AttributeValue.builder().s("ComplexTest").build()).build());
UpdateItemRequest updateRequest8 = UpdateItemRequest.builder().tableName(tableName).key(key)
.attributeUpdates(attributeUpdates8).expected(expected8)
.conditionalOperator(ConditionalOperator.AND).build();
// Both DynamoDB and Phoenix should succeed (COL1 >= 1 AND COL4 <= 50)
dynamoDbClient.updateItem(updateRequest8);
phoenixDBClientV2.updateItem(updateRequest8);
// Validate final state
validateItem(tableName, key);
}
protected Map<String, AttributeValue> getKey() {
Map<String, AttributeValue> key = new HashMap<>();
key.put("PK1", AttributeValue.builder().s("A").build());
if (isSortKeyPresent)
key.put("PK2", AttributeValue.builder().n("1").build());
return key;
}
}