This is an automated email from the ASF dual-hosted git repository.
palashc pushed a commit to branch 5.3
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/5.3 by this push:
new aab3124c61 PHOENIX-7880 Do not treat literal string SET values as
arithmetic in BSON UpdateExpression (#2496) (#2498)
aab3124c61 is described below
commit aab3124c61112955502e52f71b608de11070a23b
Author: Palash Chauhan <[email protected]>
AuthorDate: Wed Jun 10 11:55:23 2026 -0700
PHOENIX-7880 Do not treat literal string SET values as arithmetic in BSON
UpdateExpression (#2496) (#2498)
Co-authored-by: Cursor <[email protected]>
---
.../util/bson/UpdateExpressionUtils.java | 109 +++------------------
.../java/org/apache/phoenix/end2end/Bson2IT.java | 4 +-
.../java/org/apache/phoenix/end2end/Bson3IT.java | 20 +++-
.../java/org/apache/phoenix/end2end/Bson4IT.java | 17 +++-
.../java/org/apache/phoenix/end2end/Bson5IT.java | 8 +-
.../util/bson/UpdateExpressionUtilsTest.java | 58 ++++++++---
6 files changed, 97 insertions(+), 119 deletions(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/UpdateExpressionUtils.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/UpdateExpressionUtils.java
index 09da9d71c7..93efa2a366 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/UpdateExpressionUtils.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/UpdateExpressionUtils.java
@@ -18,14 +18,10 @@
package org.apache.phoenix.expression.util.bson;
import java.math.BigDecimal;
-import java.text.NumberFormat;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.bson.BsonArray;
import org.bson.BsonDecimal128;
import org.bson.BsonDocument;
@@ -102,7 +98,7 @@ public class UpdateExpressionUtils {
public static void updateExpression(final BsonDocument updateExpression,
final BsonDocument bsonDocument) {
- LOGGER.info("Update Expression: {} , current bsonDocument: {}",
updateExpression, bsonDocument);
+ LOGGER.debug("Update Expression: {} , current bsonDocument: {}",
updateExpression, bsonDocument);
if (updateExpression.containsKey("$SET")) {
executeSetExpression((BsonDocument) updateExpression.get("$SET"),
bsonDocument);
@@ -578,73 +574,26 @@ public class UpdateExpressionUtils {
}
/**
- * Retrieve the value to be updated for the given current value. If the
current value does not
- * contain any arithmetic operators, the current value is returned without
any modifications. If
- * the current value contains arithmetic expressions like "a + b" or "a -
b", the values of
- * operands are retrieved from the given document and if the values are
numeric, the given
- * arithmetic operation is performed. If the current value is a bson
document with an entry from
- * $IF_NOT_EXISTS to a document with a key and a fallback value, we lookup
if the key is already
- * present in the document. If it is, we return its value. Otherwise, we
return the provided
- * fallback value. If the current value is a bson document with $ADD or
$SUBTRACT as key, we get
- * the array of operands from this document and perform the corresponding
operation. Operand can
- * be an $IF_NOT_EXISTS bson document. If the current value is a bson
document with $LIST_APPEND
- * as key, the value is a two-element array of operands; each operand
resolves to a BsonArray
- * (literal array, a path string referring to an existing array attribute,
or an $IF_NOT_EXISTS
- * document whose resolved value is an array) and the two arrays are
concatenated in order with
- * duplicates preserved.
+ * Retrieve the value to be updated for the given current value. Arithmetic
and other computed SET
+ * values are always carried as explicit BSON documents (never inferred from
the textual content
+ * of a string value), so any non-document value - including a {@link
BsonString} that happens to
+ * contain " + " or " - " - is treated as a literal and returned without
modification. If the
+ * current value is a bson document with an entry from $IF_NOT_EXISTS to a
document with a key and
+ * a fallback value, we lookup if the key is already present in the
document. If it is, we return
+ * its value. Otherwise, we return the provided fallback value. If the
current value is a bson
+ * document with $ADD or $SUBTRACT as key, we get the array of operands from
this document and
+ * perform the corresponding operation. Operand can be an $IF_NOT_EXISTS
bson document. If the
+ * current value is a bson document with $LIST_APPEND as key, the value is a
two-element array of
+ * operands; each operand resolves to a BsonArray (literal array, a path
string referring to an
+ * existing array attribute, or an $IF_NOT_EXISTS document whose resolved
value is an array) and
+ * the two arrays are concatenated in order with duplicates preserved.
* @param curValue The current value.
* @param bsonDocument The document with all field key-value pairs.
* @return Updated values to be used by SET operation.
*/
private static BsonValue getNewFieldValue(final BsonValue curValue,
final BsonDocument bsonDocument) {
- if (
- curValue != null && curValue.isString()
- && (((BsonString) curValue).getValue().contains(" + ")
- || ((BsonString) curValue).getValue().contains(" - "))
- ) {
- String[] tokens = ((BsonString) curValue).getValue().split("\\s+");
- boolean addNum = true;
- // Pattern pattern = Pattern.compile(":?[a-zA-Z0-9]+");
- Pattern pattern = Pattern.compile("[#:$]?[^\\s\\n]+");
- Number newNum = null;
- for (String token : tokens) {
- if (token.equals("+")) {
- addNum = true;
- continue;
- } else if (token.equals("-")) {
- addNum = false;
- continue;
- }
- Matcher matcher = pattern.matcher(token);
- if (matcher.find()) {
- String operand = matcher.group();
- Number literalNum;
- BsonValue topLevelValue = bsonDocument.get(operand);
- BsonValue bsonValue = topLevelValue != null
- ? topLevelValue
- : CommonComparisonExpressionUtils.getFieldFromDocument(operand,
bsonDocument);
-
- if (bsonValue == null && (literalNum = stringToNumber(operand)) !=
null) {
- Number val = literalNum;
- newNum =
- newNum == null ? val : (addNum ? addNum(newNum, val) :
subtractNum(newNum, val));
- } else {
- if (bsonValue == null) {
- throw new IllegalArgumentException("Operand " + operand + " does
not exist");
- }
- if (!bsonValue.isNumber() && !bsonValue.isDecimal128()) {
- throw new IllegalArgumentException(
- "Operand " + operand + " is not provided as number type");
- }
- Number val = getNumberFromBsonNumber((BsonNumber) bsonValue);
- newNum =
- newNum == null ? val : (addNum ? addNum(newNum, val) :
subtractNum(newNum, val));
- }
- }
- }
- return getBsonNumberFromNumber(newNum);
- } else if (curValue instanceof BsonDocument) {
+ if (curValue instanceof BsonDocument) {
BsonDocument doc = (BsonDocument) curValue;
if (doc.get("$IF_NOT_EXISTS") != null) {
return resolveIfNotExists(doc, bsonDocument);
@@ -823,34 +772,6 @@ public class UpdateExpressionUtils {
throw new RuntimeException("Number type is not known for number: " +
number);
}
- /**
- * Convert the given String to Number.
- * @param number The String represented numeric value.
- * @return The Number object.
- */
- private static Number stringToNumber(String number) {
- try {
- return Integer.parseInt(number);
- } catch (NumberFormatException e) {
- // no-op
- }
- try {
- return Long.parseLong(number);
- } catch (NumberFormatException e) {
- // no-op
- }
- try {
- return Double.parseDouble(number);
- } catch (NumberFormatException e) {
- // no-op
- }
- try {
- return NumberFormat.getInstance().parse(number);
- } catch (ParseException e) {
- return null;
- }
- }
-
/**
* Convert Number to BsonNumber.
* @param number The Number object.
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson2IT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson2IT.java
index 73b6b21fda..855795627d 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson2IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson2IT.java
@@ -258,9 +258,9 @@ public class Bson2IT extends ParallelStatsDisabledIT {
*/
updateExp = "{\n" + " \"$SET\": {\n" + " \"Id1\": \"12345\",\n"
- + " \"NestedList1[0]\": \"NestedList1[0] + 12.22\",\n"
+ + " \"NestedList1[0]\": { \"$ADD\": [ \"NestedList1[0]\", 12.22 ]
},\n"
+ " \"NestedList1[3]\": null,\n" + " \"NestedList1[4]\": true,\n"
- + " \"attr_5[0]\": \"attr_5[0] - 10\"\n" + " }\n" + "}";
+ + " \"attr_5[0]\": { \"$SUBTRACT\": [ \"attr_5[0]\", 10 ] }\n" + "
}\n" + "}";
stmt = conn.prepareStatement("UPSERT INTO " + tableName
+ " VALUES (?,?) ON DUPLICATE KEY UPDATE COL =
BSON_UPDATE_EXPRESSION(COL, '" + updateExp
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson3IT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson3IT.java
index a0ccdb3765..1f78df07c0 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson3IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson3IT.java
@@ -232,7 +232,9 @@ public class Bson3IT extends ParallelStatsDisabledIT {
.append("browserling", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 529.435")))
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonDouble(529.435))))))
.append("$UNSET",
new BsonDocument().append("track[0].shot[2][0].city.flame", new
BsonNull()));
@@ -764,7 +766,9 @@ public class Bson3IT extends ParallelStatsDisabledIT {
.append("browserling", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 529.435")))
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonDouble(529.435))))))
.append("$UNSET",
new BsonDocument().append("track[0].shot[2][0].city.flame", new
BsonNull()));
@@ -1163,7 +1167,9 @@ public class Bson3IT extends ParallelStatsDisabledIT {
.append("browserling", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 529.435")))
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonDouble(529.435))))))
.append("$UNSET",
new BsonDocument().append("track[0].shot[2][0].city.flame", new
BsonNull()));
@@ -1536,7 +1542,9 @@ public class Bson3IT extends ParallelStatsDisabledIT {
.append("browserling", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 529.435")))
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonDouble(529.435))))))
.append("$UNSET",
new BsonDocument().append("track[0].shot[2][0].city.flame", new
BsonNull()));
@@ -1951,7 +1959,9 @@ public class Bson3IT extends ParallelStatsDisabledIT {
.append("browserling", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 529.435")))
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonDouble(529.435))))))
.append("$UNSET",
new BsonDocument().append("track[0].shot[2][0].city.flame", new
BsonNull()));
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
index f35cc50f5c..f7f226e1c4 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
@@ -60,6 +60,7 @@ import org.bson.BsonArray;
import org.bson.BsonBinary;
import org.bson.BsonDocument;
import org.bson.BsonDouble;
+import org.bson.BsonInt32;
import org.bson.BsonNull;
import org.bson.BsonString;
import org.bson.Document;
@@ -641,7 +642,9 @@ public class Bson4IT extends ParallelStatsDisabledIT {
.append("browserling", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 529.435")))
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonDouble(529.435))))))
.append("$UNSET",
new BsonDocument().append("track[0].shot[2][0].city.flame", new
BsonNull()));
@@ -725,7 +728,9 @@ public class Bson4IT extends ParallelStatsDisabledIT {
.append("new_field1", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft_new_val"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 123")));
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonInt32(123))))));
stmt = conn.prepareStatement(
"UPSERT INTO " + tableName + " VALUES (?) ON DUPLICATE KEY UPDATE COL
= CASE WHEN"
@@ -827,7 +832,9 @@ public class Bson4IT extends ParallelStatsDisabledIT {
.append("browserling", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 529.435")))
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonDouble(529.435))))))
.append("$UNSET",
new BsonDocument().append("track[0].shot[2][0].city.flame", new
BsonNull()));
@@ -907,7 +914,9 @@ public class Bson4IT extends ParallelStatsDisabledIT {
.append("new_field1", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft_new_val"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 123")));
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonInt32(123))))));
stmt = conn.prepareStatement(
"UPSERT INTO " + tableName + " VALUES (?) ON DUPLICATE KEY UPDATE_ONLY
COL = CASE WHEN"
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java
index 3e6a4f1989..9d1b477d17 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson5IT.java
@@ -252,7 +252,9 @@ public class Bson5IT extends ParallelStatsDisabledIT {
.append("browserling", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 529.435")))
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonDouble(529.435))))))
.append("$UNSET",
new BsonDocument().append("track[0].shot[2][0].city.flame", new
BsonNull()));
@@ -487,7 +489,9 @@ public class Bson5IT extends ParallelStatsDisabledIT {
.append("browserling", new
BsonBinary(PDouble.INSTANCE.toBytes(-505169340.54880095)))
.append("track[0].shot[2][0].city.standard[5]", new
BsonString("soft"))
.append("track[0].shot[2][0].city.problem[2]",
- new BsonString("track[0].shot[2][0].city.problem[2] + 529.435")))
+ new BsonDocument().append("$ADD", new BsonArray(Arrays.asList(
+ new BsonString("track[0].shot[2][0].city.problem[2]"),
+ new BsonDouble(529.435))))))
.append("$UNSET",
new BsonDocument().append("track[0].shot[2][0].city.flame", new
BsonNull()));
diff --git
a/phoenix-core/src/test/java/org/apache/phoenix/util/bson/UpdateExpressionUtilsTest.java
b/phoenix-core/src/test/java/org/apache/phoenix/util/bson/UpdateExpressionUtilsTest.java
index db6dc05481..eff0c8a9c9 100644
---
a/phoenix-core/src/test/java/org/apache/phoenix/util/bson/UpdateExpressionUtilsTest.java
+++
b/phoenix-core/src/test/java/org/apache/phoenix/util/bson/UpdateExpressionUtilsTest.java
@@ -21,6 +21,7 @@ import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.expression.util.bson.UpdateExpressionUtils;
import org.bson.BsonBinaryReader;
import org.bson.BsonDocument;
+import org.bson.BsonString;
import org.bson.RawBsonDocument;
import org.bson.codecs.BsonDocumentCodec;
import org.bson.codecs.DecoderContext;
@@ -854,7 +855,7 @@ public class UpdateExpressionUtilsTest {
+ " \"InPublication\" : false,\n" + " \"ColorBytes\" : {\n" + "
\"$binary\" : {\n"
+ " \"base64\" : \"QmxhY2s=\",\n" + " \"subType\" : \"00\"\n"
+ " }\n" + " },\n"
+ " \"ISBN\" : \"111-1111111111\",\n"
- + " \"NestedList1\" : [ -473.11999999999995, \"1234abcd\", [
\"xyz0123\", {\n"
+ + " \"NestedList1\" : [ \"NestedList1[0] + 12.22\", \"1234abcd\", [
\"xyz0123\", {\n"
+ " \"InPublication\" : false,\n" + " \"BinaryTitleSet\" : {\n"
+ " \"$set\" : [ {\n" + " \"$binary\" : {\n"
+ " \"base64\" : \"Qm9vayAxMDExIFRpdGxlIEJpbmFyeQ==\",\n"
@@ -888,10 +889,10 @@ public class UpdateExpressionUtilsTest {
+ " \"base64\" : \"MjA0OHU1bmJsd2plaVdGR1RIKDRiZjkzMA==\",\n"
+ " \"subType\" : \"00\"\n" + " }\n" + " },\n" + "
\"n_attr_3\" : true,\n"
+ " \"n_attr_4\" : null,\n" + " \"n_attr_10\" : true,\n"
- + " \"n_attr_20\" : \"str_val_0\"\n" + " },\n" + " \"attr_5\" : [
1224, \"str001\", {\n"
- + " \"$binary\" : {\n" + " \"base64\" : \"AAECAwQF\",\n"
- + " \"subType\" : \"00\"\n" + " }\n" + " } ],\n"
- + " \"NestedList12\" : [ -485.34, \"1234abcd\", [ {\n"
+ + " \"n_attr_20\" : \"str_val_0\"\n" + " },\n"
+ + " \"attr_5\" : [ \"attr_5[0] - 10\", \"str001\", {\n" + "
\"$binary\" : {\n"
+ + " \"base64\" : \"AAECAwQF\",\n" + " \"subType\" : \"00\"\n"
+ " }\n"
+ + " } ],\n" + " \"NestedList12\" : [ -485.34, \"1234abcd\", [ {\n"
+ " \"$set\" : [ \"xyz01234\", \"xyz0123\", \"abc01234\" ]\n" + " },
{\n"
+ " \"$set\" : [ {\n" + " \"$binary\" : {\n" + "
\"base64\" : \"dmFsMDE=\",\n"
+ " \"subType\" : \"00\"\n" + " }\n" + " }, {\n" + "
\"$binary\" : {\n"
@@ -942,7 +943,7 @@ public class UpdateExpressionUtilsTest {
// }
// },
// "ISBN" : "111-1111111111",
- // "NestedList1" : [ -473.11999999999995, "1234abcd", [ "xyz0123", {
+ // "NestedList1" : [ "NestedList1[0] + 12.22", "1234abcd", [ "xyz0123", {
// "InPublication" : false,
// "BinaryTitleSet" : {
// "$set" : [ {
@@ -1016,7 +1017,7 @@ public class UpdateExpressionUtilsTest {
// "n_attr_10" : true,
// "n_attr_20" : "str_val_0"
// },
- // "attr_5" : [ 1224, "str001", {
+ // "attr_5" : [ "attr_5[0] - 10", "str001", {
// "$binary" : {
// "base64" : "AAECAwQF",
// "subType" : "00"
@@ -1198,9 +1199,10 @@ public class UpdateExpressionUtilsTest {
String updateExpression = "{\n" + " \"$SET\": {\n"
// 1. Simple key = value
+ " \"simpleField\": \"newValue\",\n" + " \"numericField\": 42,\n"
- // 2. string-based arithmetic: fieldA + fieldB = 10 + 25 = 35
+ // 2. literal string containing " + " is stored verbatim (NOT
interpreted as arithmetic).
+ // Arithmetic is only performed via the explicit $ADD / $SUBTRACT
document forms below.
+ " \"sumField\": \"fieldA + fieldB\",\n"
- // 2b. string-based arithmetic with subtraction: fieldB - fieldA = 25 -
10 = 15
+ // 2b. literal string containing " - " is stored verbatim (NOT
interpreted as arithmetic).
+ " \"diffField\": \"fieldB - fieldA\",\n"
// 3. Array element set: items[1] = 999
+ " \"items[1]\": 999,\n"
@@ -1233,9 +1235,10 @@ public class UpdateExpressionUtilsTest {
Assert.assertEquals("newValue",
bsonDocument.getString("simpleField").getValue());
Assert.assertEquals(42, bsonDocument.getInt32("numericField").getValue());
- // 2. Verify string-based arithmetic
- Assert.assertEquals(35, bsonDocument.getInt32("sumField").getValue());
- Assert.assertEquals(15, bsonDocument.getInt32("diffField").getValue());
+ // 2. Verify literal strings containing " + " / " - " are stored verbatim
and are never
+ // mistaken for arithmetic expressions.
+ Assert.assertEquals("fieldA + fieldB",
bsonDocument.getString("sumField").getValue());
+ Assert.assertEquals("fieldB - fieldA",
bsonDocument.getString("diffField").getValue());
// 3. Verify array element set
Assert.assertEquals(999,
bsonDocument.getArray("items").get(1).asInt32().getValue());
@@ -1264,6 +1267,37 @@ public class UpdateExpressionUtilsTest {
Assert.assertEquals(25, bsonDocument.getInt32("fieldB").getValue());
}
+ /**
+ * Regression test for literal string SET values that contain " + " or " -
". Such values must be
+ * stored verbatim and must never be mistaken for arithmetic expressions
(which previously threw
+ * "Operand ... does not exist"). Arithmetic is only performed via the
explicit $ADD / $SUBTRACT
+ * document forms.
+ */
+ @Test
+ public void testLiteralStringWithArithmeticOperatorsIsStoredVerbatim() {
+ BsonDocument bsonDocument = new BsonDocument();
+
+ // Literal JSON string containing " - ".
+ String propertiesLiteral = "{\"teamId\":\"abc123\"," + "\"teamName\":\"Foo
- Bar Baz Service\","
+ + "\"channelName\":\"#foo-notifications\"}";
+
+ BsonDocument setDoc = new BsonDocument();
+ setDoc.put("properties", new BsonString(propertiesLiteral));
+ // Literal string containing " + ".
+ setDoc.put("equation", new BsonString("E = mc^2 + offset"));
+ // Plain literal that looks like a subtraction of two field names.
+ setDoc.put("company", new BsonString("Acme - Widgets"));
+
+ BsonDocument updateExpression = new BsonDocument();
+ updateExpression.put("$SET", setDoc);
+
+ UpdateExpressionUtils.updateExpression(updateExpression, bsonDocument);
+
+ Assert.assertEquals(propertiesLiteral,
bsonDocument.getString("properties").getValue());
+ Assert.assertEquals("E = mc^2 + offset",
bsonDocument.getString("equation").getValue());
+ Assert.assertEquals("Acme - Widgets",
bsonDocument.getString("company").getValue());
+ }
+
private static BsonDocument seedListAppendDoc() {
return BsonDocument
.parse("{" + "\"events\": [\"a\", \"b\"]," + "\"numeric\": 42," +
"\"text\": \"hello\","