This is an automated email from the ASF dual-hosted git repository.
virajjasani 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 69c111c7de PHOENIX-7849 : Support LIST_APPEND operator in BSON
UpdateExpression SET (#2468) (#2470)
69c111c7de is described below
commit 69c111c7de8acc91faf96f67ea54aa8fede3988e
Author: Palash Chauhan <[email protected]>
AuthorDate: Tue May 12 10:45:46 2026 -0700
PHOENIX-7849 : Support LIST_APPEND operator in BSON UpdateExpression SET
(#2468) (#2470)
Co-authored-by: Cursor <[email protected]>
---
.../util/bson/UpdateExpressionUtils.java | 68 +++++-
.../java/org/apache/phoenix/end2end/Bson2IT.java | 101 +++++++++
.../util/bson/UpdateExpressionUtilsTest.java | 247 +++++++++++++++++++++
3 files changed, 415 insertions(+), 1 deletion(-)
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 a3731819c4..09da9d71c7 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
@@ -587,7 +587,11 @@ public class UpdateExpressionUtils {
* 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.
+ * 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.
@@ -654,11 +658,73 @@ public class UpdateExpressionUtils {
Number result = resolveSetOperand(operands.get(0), bsonDocument);
result = subtractNum(result, resolveSetOperand(operands.get(1),
bsonDocument));
return getBsonNumberFromNumber(result);
+ } else if (doc.containsKey("$LIST_APPEND")) {
+ BsonArray operands = doc.getArray("$LIST_APPEND");
+ if (operands.size() != 2) {
+ throw new BsonUpdateInvalidArgumentException(
+ "Incorrect number of operands for operator or function; operator
or function: "
+ + "$LIST_APPEND, number of operands: " + operands.size());
+ }
+ BsonArray list1 = resolveListAppendOperand(operands.get(0),
bsonDocument);
+ BsonArray list2 = resolveListAppendOperand(operands.get(1),
bsonDocument);
+ BsonArray result = new BsonArray(new ArrayList<>(list1.size() +
list2.size()));
+ result.addAll(list1);
+ result.addAll(list2);
+ return result;
}
}
return curValue;
}
+ /**
+ * Resolve a single operand of <code>$LIST_APPEND</code> to a {@link
BsonArray}. Accepted operand
+ * shapes:
+ * <ul>
+ * <li>literal array — used as-is</li>
+ * <li>{@link BsonString} naming a top-level or nested document path — the
resolved value must
+ * exist and be an array; otherwise an exception is thrown</li>
+ * <li>{@code {"$IF_NOT_EXISTS": {<path>: <fallback>}}} — fallback is used
when the path is
+ * absent; the resolved value must be an array regardless of which branch is
taken</li>
+ * </ul>
+ * Any other operand shape (including a nested {@code $LIST_APPEND}) is
rejected.
+ */
+ private static BsonArray resolveListAppendOperand(final BsonValue operand,
+ final BsonDocument bsonDocument) {
+ if (operand == null) {
+ throw new BsonUpdateInvalidArgumentException(
+ "An operand in the update expression has an incorrect data type");
+ }
+ if (operand.isArray()) {
+ return operand.asArray();
+ }
+ if (operand instanceof BsonDocument && ((BsonDocument)
operand).get("$IF_NOT_EXISTS") != null) {
+ BsonValue resolved = resolveIfNotExists((BsonDocument) operand,
bsonDocument);
+ if (resolved == null || !resolved.isArray()) {
+ throw new BsonUpdateInvalidArgumentException(
+ "An operand in the update expression has an incorrect data type");
+ }
+ return resolved.asArray();
+ }
+ if (operand instanceof BsonString) {
+ String path = ((BsonString) operand).getValue();
+ BsonValue topLevelValue = bsonDocument.get(path);
+ BsonValue bsonValue = topLevelValue != null
+ ? topLevelValue
+ : CommonComparisonExpressionUtils.getFieldFromDocument(path,
bsonDocument);
+ if (bsonValue == null) {
+ throw new BsonUpdateInvalidArgumentException(
+ "The provided expression refers to an attribute that does not exist
in the item: "
+ + path);
+ }
+ if (!bsonValue.isArray()) {
+ throw new BsonUpdateInvalidArgumentException(
+ "An operand in the update expression has an incorrect data type");
+ }
+ return bsonValue.asArray();
+ }
+ throw new BsonUpdateInvalidArgumentException("Invalid operand for
$LIST_APPEND: " + operand);
+ }
+
/**
* Resolves an $IF_NOT_EXISTS expression. Returns the existing field value
if present, otherwise
* returns the fallback value.
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 9ad266523f..73b6b21fda 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
@@ -26,9 +26,12 @@ import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
+import java.util.Arrays;
import java.util.Properties;
import org.apache.phoenix.util.PropertiesUtil;
+import org.bson.BsonArray;
import org.bson.BsonDocument;
+import org.bson.BsonInt32;
import org.bson.BsonString;
import org.bson.RawBsonDocument;
import org.junit.Test;
@@ -1087,4 +1090,102 @@ public class Bson2IT extends ParallelStatsDisabledIT {
return RawBsonDocument.parse(json);
}
+ @Test
+ public void testListAppendUpdateExpression() throws Exception {
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ String tableName = generateUniqueName();
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ String ddl = "CREATE TABLE " + tableName
+ + " (PK1 VARCHAR NOT NULL, COL BSON CONSTRAINT pk PRIMARY KEY(PK1))";
+ conn.createStatement().execute(ddl);
+
+ BsonDocument initial = new BsonDocument()
+ .append("events", new BsonArray(Arrays.asList(new BsonString("a"), new
BsonString("b"))))
+ .append("counter", new BsonInt32(0));
+
+ PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " +
tableName + " VALUES (?, ?)");
+ stmt.setString(1, "pk1");
+ stmt.setObject(2, initial);
+ stmt.executeUpdate();
+ conn.commit();
+
+ BsonDocument appendExisting = new BsonDocument().append("$SET",
+ new BsonDocument().append("events",
+ new BsonDocument().append("$LIST_APPEND",
+ new BsonArray(Arrays.asList(new BsonString("events"),
+ new BsonArray(Arrays.asList(new BsonString("c"))))))));
+
+ stmt = conn.prepareStatement("UPSERT INTO " + tableName
+ + " VALUES (?) ON DUPLICATE KEY UPDATE COL =
BSON_UPDATE_EXPRESSION(COL, '" + appendExisting
+ + "')");
+ stmt.setString(1, "pk1");
+ stmt.executeUpdate();
+ conn.commit();
+
+ ResultSet rs =
+ conn.createStatement().executeQuery("SELECT COL FROM " + tableName + "
WHERE PK1 = 'pk1'");
+ assertTrue(rs.next());
+ BsonDocument afterAppend = (BsonDocument) rs.getObject(1);
+ BsonArray events = afterAppend.getArray("events");
+ assertEquals(3, events.size());
+ assertEquals("a", events.get(0).asString().getValue());
+ assertEquals("b", events.get(1).asString().getValue());
+ assertEquals("c", events.get(2).asString().getValue());
+
+ BsonDocument createOrAppend = new BsonDocument().append("$SET",
+ new BsonDocument()
+ .append("newQueue",
+ new BsonDocument().append("$LIST_APPEND",
+ new BsonArray(Arrays.asList(
+ new BsonDocument().append("$IF_NOT_EXISTS",
+ new BsonDocument().append("newQueue", new BsonArray())),
+ new BsonArray(Arrays.asList(new BsonString("ev1"), new
BsonString("ev2")))))))
+ .append("counter", new BsonDocument().append("$ADD",
+ new BsonArray(Arrays.asList(new BsonString("counter"), new
BsonInt32(1))))));
+
+ stmt = conn.prepareStatement("UPSERT INTO " + tableName
+ + " VALUES (?) ON DUPLICATE KEY UPDATE COL =
BSON_UPDATE_EXPRESSION(COL, '" + createOrAppend
+ + "')");
+ stmt.setString(1, "pk1");
+ stmt.executeUpdate();
+ conn.commit();
+
+ rs =
+ conn.createStatement().executeQuery("SELECT COL FROM " + tableName + "
WHERE PK1 = 'pk1'");
+ assertTrue(rs.next());
+ BsonDocument afterCreate = (BsonDocument) rs.getObject(1);
+
+ assertEquals(3, afterCreate.getArray("events").size());
+ BsonArray queue = afterCreate.getArray("newQueue");
+ assertEquals(2, queue.size());
+ assertEquals("ev1", queue.get(0).asString().getValue());
+ assertEquals("ev2", queue.get(1).asString().getValue());
+ assertEquals(1, afterCreate.getInt32("counter").getValue());
+
+ // Re-apply the same create-or-append. newQueue now exists, so
$IF_NOT_EXISTS resolves
+ // to the existing array (not the empty-array fallback) and the same
elements are
+ // appended again, producing duplicates. counter advances once more.
+ stmt = conn.prepareStatement("UPSERT INTO " + tableName
+ + " VALUES (?) ON DUPLICATE KEY UPDATE COL =
BSON_UPDATE_EXPRESSION(COL, '" + createOrAppend
+ + "')");
+ stmt.setString(1, "pk1");
+ stmt.executeUpdate();
+ conn.commit();
+
+ rs =
+ conn.createStatement().executeQuery("SELECT COL FROM " + tableName + "
WHERE PK1 = 'pk1'");
+ assertTrue(rs.next());
+ BsonDocument afterRepeat = (BsonDocument) rs.getObject(1);
+
+ assertEquals(3, afterRepeat.getArray("events").size());
+ BsonArray queueRepeat = afterRepeat.getArray("newQueue");
+ assertEquals(4, queueRepeat.size());
+ assertEquals("ev1", queueRepeat.get(0).asString().getValue());
+ assertEquals("ev2", queueRepeat.get(1).asString().getValue());
+ assertEquals("ev1", queueRepeat.get(2).asString().getValue());
+ assertEquals("ev2", queueRepeat.get(3).asString().getValue());
+ assertEquals(2, afterRepeat.getInt32("counter").getValue());
+ }
+ }
+
}
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 062e1ac813..db6dc05481 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
@@ -1264,4 +1264,251 @@ public class UpdateExpressionUtilsTest {
Assert.assertEquals(25, bsonDocument.getInt32("fieldB").getValue());
}
+ private static BsonDocument seedListAppendDoc() {
+ return BsonDocument
+ .parse("{" + "\"events\": [\"a\", \"b\"]," + "\"numeric\": 42," +
"\"text\": \"hello\","
+ + "\"colors\": {\"$set\": [\"red\", \"blue\"]}," + "\"nested\":
{\"queue\": [\"x\"]},"
+ + "\"matrix\": [[\"row0\"], [\"row1\"]]," + "\"counter\": 0" + "}");
+ }
+
+ @Test
+ public void testListAppend_op1PathToExistingList() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"events\": {\"$LIST_APPEND\": [\"events\",
[\"c\", \"d\"]]}}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ org.bson.BsonArray events = doc.getArray("events");
+ Assert.assertEquals(4, events.size());
+ Assert.assertEquals("a", events.get(0).asString().getValue());
+ Assert.assertEquals("b", events.get(1).asString().getValue());
+ Assert.assertEquals("c", events.get(2).asString().getValue());
+ Assert.assertEquals("d", events.get(3).asString().getValue());
+ }
+
+ @Test
+ public void testListAppend_op1LiteralArrayPrepend() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"events\": {\"$LIST_APPEND\": [[\"z\"],
\"events\"]}}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ org.bson.BsonArray events = doc.getArray("events");
+ Assert.assertEquals("z", events.get(0).asString().getValue());
+ Assert.assertEquals("a", events.get(1).asString().getValue());
+ Assert.assertEquals("b", events.get(2).asString().getValue());
+ }
+
+ @Test
+ public void testListAppend_op1IfNotExistsMissingEmptyFallback() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"newQueue\": {\"$LIST_APPEND\": ["
+ + "{\"$IF_NOT_EXISTS\": {\"newQueue\": []}}," + "[\"first\",
\"second\"]" + "]}}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ org.bson.BsonArray q = doc.getArray("newQueue");
+ Assert.assertEquals(2, q.size());
+ Assert.assertEquals("first", q.get(0).asString().getValue());
+ Assert.assertEquals("second", q.get(1).asString().getValue());
+ }
+
+ @Test
+ public void testListAppend_op1IfNotExistsExistingList() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"events\": {\"$LIST_APPEND\": ["
+ + "{\"$IF_NOT_EXISTS\": {\"events\": []}}," + "[\"c\"]" + "]}}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ org.bson.BsonArray events = doc.getArray("events");
+ Assert.assertEquals(3, events.size());
+ Assert.assertEquals("c", events.get(2).asString().getValue());
+ }
+
+ @Test
+ public void testListAppend_op1PathToMissing_throws() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"events\": {\"$LIST_APPEND\": [\"missing\",
[\"c\"]]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for missing
path");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("does not
exist"));
+ }
+ }
+
+ @Test
+ public void testListAppend_op1PathToNumber_throws() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"numeric\": {\"$LIST_APPEND\": [\"numeric\",
[\"c\"]]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for number
operand");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("incorrect
data type"));
+ }
+ }
+
+ @Test
+ public void testListAppend_op1PathToString_throws() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"text\": {\"$LIST_APPEND\": [\"text\",
[\"c\"]]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for string
operand");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("incorrect
data type"));
+ }
+ }
+
+ @Test
+ public void testListAppend_op1PathToSet_throws() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"colors\": {\"$LIST_APPEND\": [\"colors\",
[\"green\"]]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for set operand
(set != list)");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("incorrect
data type"));
+ }
+ }
+
+ @Test
+ public void testListAppend_op1LiteralNumber_throws() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"events\": {\"$LIST_APPEND\": [7,
\"events\"]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for non-array
operand");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("Invalid
operand"));
+ }
+ }
+
+ @Test
+ public void testListAppend_op1IfNotExistsMissingNonListFallback_throws() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"newQueue\": {\"$LIST_APPEND\": ["
+ + "{\"$IF_NOT_EXISTS\": {\"newQueue\": 0}}," + "[\"a\"]" + "]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for non-list
fallback");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("incorrect
data type"));
+ }
+ }
+
+ @Test
+ public void testListAppend_op1IfNotExistsExistingNonList_throws() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"numeric\": {\"$LIST_APPEND\": ["
+ + "{\"$IF_NOT_EXISTS\": {\"numeric\": []}}," + "[\"a\"]" + "]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for non-list
existing value");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("incorrect
data type"));
+ }
+ }
+
+ @Test
+ public void testListAppend_chainedNested_throws() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"events\": {\"$LIST_APPEND\": ["
+ + "{\"$LIST_APPEND\": [\"events\", [\"m\"]]}," + "[\"t\"]" + "]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for chained
$LIST_APPEND");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("Invalid
operand"));
+ }
+ }
+
+ @Test
+ public void testListAppend_op2PathSelfAppend() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"events\": {\"$LIST_APPEND\": [\"events\",
\"events\"]}}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ org.bson.BsonArray events = doc.getArray("events");
+ Assert.assertEquals(4, events.size());
+ Assert.assertEquals("a", events.get(0).asString().getValue());
+ Assert.assertEquals("b", events.get(1).asString().getValue());
+ Assert.assertEquals("a", events.get(2).asString().getValue());
+ Assert.assertEquals("b", events.get(3).asString().getValue());
+ }
+
+ @Test
+ public void testListAppend_resultSemantics() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"events\": {\"$LIST_APPEND\": [" +
"\"events\","
+ + "[\"a\", 3.14, true, null, {\"nested\": 1}]" + "]}}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ org.bson.BsonArray events = doc.getArray("events");
+ Assert.assertEquals(7, events.size());
+ Assert.assertTrue(events.get(2).isString());
+ Assert.assertTrue(events.get(3).isDouble());
+ Assert.assertTrue(events.get(4).isBoolean());
+ Assert.assertTrue(events.get(5).isNull());
+ Assert.assertTrue(events.get(6).isDocument());
+ }
+
+ @Test
+ public void testListAppend_bothEmpty() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {\"newQueue\": {\"$LIST_APPEND\": [[], []]}}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.assertEquals(0, doc.getArray("newQueue").size());
+ }
+
+ @Test
+ public void testListAppend_nestedDocumentPathTarget() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr =
+ "{\"$SET\": {\"nested.queue\": {\"$LIST_APPEND\": [" +
"\"nested.queue\", [\"y\"]" + "]}}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ org.bson.BsonArray q = doc.getDocument("nested").getArray("queue");
+ Assert.assertEquals(2, q.size());
+ Assert.assertEquals("x", q.get(0).asString().getValue());
+ Assert.assertEquals("y", q.get(1).asString().getValue());
+ }
+
+ @Test
+ public void testListAppend_arrayIndexPathTarget() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr =
+ "{\"$SET\": {\"matrix[0]\": {\"$LIST_APPEND\": [" + "\"matrix[0]\",
[\"appended\"]" + "]}}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ org.bson.BsonArray row0 = doc.getArray("matrix").get(0).asArray();
+ Assert.assertEquals(2, row0.size());
+ Assert.assertEquals("row0", row0.get(0).asString().getValue());
+ Assert.assertEquals("appended", row0.get(1).asString().getValue());
+ }
+
+ @Test
+ public void testListAppend_combinedWithArithmetic() {
+ BsonDocument doc = seedListAppendDoc();
+ String expr = "{\"$SET\": {" + "\"events\": {\"$LIST_APPEND\": ["
+ + " {\"$IF_NOT_EXISTS\": {\"events\": []}}," + " [\"ev1\"]" + "]},"
+ + "\"counter\": {\"$ADD\": [\"counter\", 1]}" + "}}";
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(expr), doc);
+ Assert.assertEquals(3, doc.getArray("events").size());
+ Assert.assertEquals("ev1",
doc.getArray("events").get(2).asString().getValue());
+ Assert.assertEquals(1, doc.getInt32("counter").getValue());
+ }
+
+ @Test
+ public void testListAppend_wrongArity_throws() {
+ BsonDocument doc = seedListAppendDoc();
+ String exprThree =
+ "{\"$SET\": {\"events\": {\"$LIST_APPEND\": [\"events\", [\"c\"],
[\"d\"]]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(exprThree),
doc);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for arity 3");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("Incorrect
number of operands"));
+ }
+
+ BsonDocument doc2 = seedListAppendDoc();
+ String exprOne = "{\"$SET\": {\"events\": {\"$LIST_APPEND\":
[\"events\"]}}}";
+ try {
+ UpdateExpressionUtils.updateExpression(RawBsonDocument.parse(exprOne),
doc2);
+ Assert.fail("expected BsonUpdateInvalidArgumentException for arity 1");
+ } catch
(org.apache.phoenix.expression.util.bson.BsonUpdateInvalidArgumentException e) {
+ Assert.assertTrue(e.getMessage(), e.getMessage().contains("Incorrect
number of operands"));
+ }
+ }
+
}