Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public UpdateItemBaseTests(boolean isSortKeyPresent) {
* attribute that is of type Number.
*/
@Test(timeout = 120000)
public void testSet() {
public void testSet1() {
final String tableName = testName.getMethodName().replaceAll("[\\[\\]]", "");
createTableAndPutItem(tableName, true);

Expand Down Expand Up @@ -171,6 +171,45 @@ public void testSet() {
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.
*/
Expand Down Expand Up @@ -404,6 +443,7 @@ protected Map<String, AttributeValue> getItem1() {
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonNull;
import org.bson.BsonNumber;
import org.bson.BsonString;
import org.bson.BsonValue;

import org.apache.phoenix.expression.util.bson.UpdateExpressionUtils;

/**
* Utility to convert DynamoDB UpdateExpression into BSON Update Expression.
*/
Expand All @@ -38,10 +36,12 @@ public class UpdateExpressionDdbToBson {
private static final String removeRegExPattern = "REMOVE\\s+(.+?)(?=\\s+(SET|ADD|DELETE)\\b|$)";
private static final String addRegExPattern = "ADD\\s+(.+?)(?=\\s+(SET|REMOVE|DELETE)\\b|$)";
private static final String deleteRegExPattern = "DELETE\\s+(.+?)(?=\\s+(SET|REMOVE|ADD)\\b|$)";
private static final String ifNotExistsPattern = "if_not_exists\\s*\\(\\s*([^,]+)\\s*,\\s*([^)]+)\\s*\\)";
private static final Pattern SET_PATTERN = Pattern.compile(setRegExPattern);
private static final Pattern REMOVE_PATTERN = Pattern.compile(removeRegExPattern);
private static final Pattern ADD_PATTERN = Pattern.compile(addRegExPattern);
private static final Pattern DELETE_PATTERN = Pattern.compile(deleteRegExPattern);
private static final Pattern IF_NOT_EXISTS_PATTERN = Pattern.compile(ifNotExistsPattern);

public static BsonDocument getBsonDocumentForUpdateExpression(
final String updateExpression,
Expand Down Expand Up @@ -75,27 +75,18 @@ public static BsonDocument getBsonDocumentForUpdateExpression(
BsonDocument bsonDocument = new BsonDocument();
if (!setString.isEmpty()) {
BsonDocument setBsonDoc = new BsonDocument();
String[] setExpressions = setString.split(",");
// split by comma only if comma is not within ()
String[] setExpressions = setString.split(",(?![^()]*+\\))");
for (int i = 0; i < setExpressions.length; i++) {
String setExpression = setExpressions[i].trim();
String[] keyVal = setExpression.split("\\s*=\\s*");
if (keyVal.length == 2) {
String attributeKey = keyVal[0].trim();
String attributeVal = keyVal[1].trim();
if (attributeVal.contains("+") || attributeVal.contains("-")) {
setBsonDoc.put(attributeKey, getArithmeticExpVal(attributeVal, comparisonValue));
setBsonDoc.put(attributeKey, getArithmeticDoc(attributeVal, comparisonValue));
} else if (attributeVal.startsWith("if_not_exists")) {
// attributeVal --> if_not_exists ( path
String ifNotExistsPath = attributeVal.split("\\(")[1].trim();
// next setExpression --> value)
String fallBackValue = setExpressions[i+1].split("\\)")[0].trim();
BsonValue fallBackValueBson = comparisonValue.get(fallBackValue);
BsonDocument fallBackDoc = new BsonDocument();
fallBackDoc.put(ifNotExistsPath, fallBackValueBson);
BsonDocument ifNotExistsDoc = new BsonDocument();
ifNotExistsDoc.put("$IF_NOT_EXISTS", fallBackDoc);
setBsonDoc.put(attributeKey, ifNotExistsDoc);
i++;
setBsonDoc.put(attributeKey, getIfNotExistsDoc(attributeVal, comparisonValue));
} else {
setBsonDoc.put(attributeKey, comparisonValue.get(attributeVal));
}
Expand Down Expand Up @@ -153,36 +144,52 @@ public static BsonDocument getBsonDocumentForUpdateExpression(
return bsonDocument;
}

private static BsonString getArithmeticExpVal(String attributeVal,
BsonDocument comparisonValuesDocument) {
String[] tokens = attributeVal.split("\\s+");
Pattern pattern = Pattern.compile("[#:$]?[^\\s\\n]+");
StringBuilder val = new StringBuilder();
for (String token : tokens) {
if (token.equals("+")) {
val.append(" + ");
continue;
} else if (token.equals("-")) {
val.append(" - ");
continue;
private static BsonDocument getArithmeticDoc(String expr, BsonDocument comparisonValue) {
BsonDocument arithmeticDoc = new BsonDocument();
String op;
String[] operands;
if (expr.contains("+")) {
op = "$ADD";
operands = expr.split("\\+");
} else if (expr.contains("-")) {
op = "$SUBTRACT";
operands = expr.split("-");
} else {
throw new IllegalArgumentException("Unsupported arithmetic operator for SET");
}
Matcher matcher = pattern.matcher(token);
if (matcher.find()) {
String operand = matcher.group();
if (operand.startsWith(":") || operand.startsWith("$") || operand.startsWith("#")) {
BsonValue bsonValue = comparisonValuesDocument.get(operand);
if (!bsonValue.isNumber() && !bsonValue.isDecimal128()) {
throw new IllegalArgumentException(
"Operand " + operand + " is not provided as number type");
BsonArray bsonOperands = new BsonArray();
for (String operand : operands) {
operand = operand.trim();
if (operand.startsWith("if_not_exists")) {
bsonOperands.add(getIfNotExistsDoc(operand, comparisonValue));
} else if (operand.startsWith(":") || operand.startsWith("$") || operand.startsWith("#")) {
BsonValue bsonValue = comparisonValue.get(operand);
if (!bsonValue.isNumber() && !bsonValue.isDecimal128()) {
throw new IllegalArgumentException(
"Operand " + operand + " is not provided as number type");
}
bsonOperands.add(bsonValue);
} else {
bsonOperands.add(new BsonString(operand));
}
Number numVal = UpdateExpressionUtils.getNumberFromBsonNumber((BsonNumber) bsonValue);
val.append(numVal);
} else {
val.append(operand);
}
}
}
return new BsonString(val.toString());
arithmeticDoc.put(op, bsonOperands);
return arithmeticDoc;
}

private static BsonDocument getIfNotExistsDoc(String expr, BsonDocument comparisonValue) {
Matcher m = IF_NOT_EXISTS_PATTERN.matcher(expr);
if (m.find()) {
String ifNotExistsPath = m.group(1).trim();
String fallBackValue = m.group(2).trim();
BsonValue fallBackValueBson = comparisonValue.get(fallBackValue);
BsonDocument fallBackDoc = new BsonDocument();
fallBackDoc.put(ifNotExistsPath, fallBackValueBson);
BsonDocument ifNotExistsDoc = new BsonDocument();
ifNotExistsDoc.put("$IF_NOT_EXISTS", fallBackDoc);
return ifNotExistsDoc;
} else {
throw new RuntimeException("Invalid format for if_not_exists(path, value)");
}
}
}
Loading