This is an automated email from the ASF dual-hosted git repository.
vjasani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/master by this push:
new 328855df3b PHOENIX-7662 BSON Condition Function contains() (#2218)
328855df3b is described below
commit 328855df3b4f5e7fb8c06f1fed17f01c16523cf5
Author: Viraj Jasani <[email protected]>
AuthorDate: Fri Jul 11 13:59:45 2025 -0700
PHOENIX-7662 BSON Condition Function contains() (#2218)
---
.../src/main/antlr3/PhoenixBsonExpression.g | 3 +
.../util/bson/CommonComparisonExpressionUtils.java | 18 +++
.../util/bson/SQLComparisonExpressionUtils.java | 72 ++++++++-
.../util/bson/UpdateExpressionUtils.java | 26 +---
.../parse/DocumentFieldContainsParseNode.java | 62 ++++++++
.../org/apache/phoenix/parse/ParseNodeFactory.java | 5 +
.../java/org/apache/phoenix/end2end/Bson1IT.java | 48 +++++-
.../util/bson/ComparisonExpressionUtilsTest.java | 165 +++++++++++++++++++++
8 files changed, 376 insertions(+), 23 deletions(-)
diff --git a/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
b/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
index 86313300ef..9edccc51d2 100644
--- a/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
+++ b/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
@@ -33,6 +33,7 @@ tokens
FIELD = 'field_exists';
FIELD_NOT = 'field_not_exists';
BEGINS_WITH = 'begins_with';
+ CONTAINS = 'contains';
}
@parser::header {
@@ -235,6 +236,8 @@ boolean_expression returns [ParseNode ret]
| (ATTR_NOT | FIELD_NOT) ( LPAREN t=literal RPAREN {$ret =
factory.documentFieldExists(t, false); } )
| BEGINS_WITH ( LPAREN l=value_expression COMMA r=value_expression
RPAREN
{$ret = factory.documentFieldBeginsWith(l, r); } )
+ | CONTAINS ( LPAREN l=value_expression COMMA r=value_expression
RPAREN
+ {$ret = factory.documentFieldContains(l, r); } )
;
value_expression returns [ParseNode ret]
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/CommonComparisonExpressionUtils.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/CommonComparisonExpressionUtils.java
index af739b720d..51944a7468 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/CommonComparisonExpressionUtils.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/CommonComparisonExpressionUtils.java
@@ -39,6 +39,24 @@ public class CommonComparisonExpressionUtils {
private static final Logger LOGGER =
LoggerFactory.getLogger(CommonComparisonExpressionUtils.class);
+ /**
+ * Returns true if the given BsonValue represents Set data structure.
+ *
+ * @param bsonValue The value.
+ * @return True if the given BsonValue represents Set data structure.
+ */
+ static boolean isBsonSet(final BsonValue bsonValue) {
+ if (!bsonValue.isDocument()) {
+ return false;
+ }
+ BsonDocument bsonDocument = (BsonDocument) bsonValue;
+ if (bsonDocument.size() == 1 && bsonDocument.containsKey("$set")) {
+ BsonValue value = bsonDocument.get("$set");
+ return value != null && value.isArray();
+ }
+ return false;
+ }
+
/**
* Comparison operators supported for the Document value comparisons.
*/
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/SQLComparisonExpressionUtils.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/SQLComparisonExpressionUtils.java
index 92c22fc6ff..8f9126168a 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/SQLComparisonExpressionUtils.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/SQLComparisonExpressionUtils.java
@@ -7,7 +7,7 @@
* "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
+ * 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,
@@ -22,6 +22,7 @@ import org.apache.phoenix.parse.AndParseNode;
import org.apache.phoenix.parse.BetweenParseNode;
import org.apache.phoenix.parse.DocumentFieldExistsParseNode;
import org.apache.phoenix.parse.DocumentFieldBeginsWithParseNode;
+import org.apache.phoenix.parse.DocumentFieldContainsParseNode;
import org.apache.phoenix.parse.BsonExpressionParser;
import org.apache.phoenix.parse.EqualParseNode;
import org.apache.phoenix.parse.GreaterThanOrEqualParseNode;
@@ -34,6 +35,7 @@ import org.apache.phoenix.parse.NotEqualParseNode;
import org.apache.phoenix.parse.NotParseNode;
import org.apache.phoenix.parse.OrParseNode;
import org.apache.phoenix.parse.ParseNode;
+import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonString;
import org.bson.BsonValue;
@@ -177,6 +179,17 @@ public final class SQLComparisonExpressionUtils {
fieldName = replaceExpressionFieldNames(fieldName, keyAliasDocument,
sortedKeyNames);
final String prefixValue = (String) value.getValue();
return beginsWith(fieldName, prefixValue, rawBsonDocument,
comparisonValuesDocument);
+ } else if (parseNode instanceof DocumentFieldContainsParseNode) {
+ final DocumentFieldContainsParseNode documentFieldContainsParseNode =
+ (DocumentFieldContainsParseNode) parseNode;
+ final LiteralParseNode fieldKey =
+ (LiteralParseNode) documentFieldContainsParseNode.getFieldKey();
+ final LiteralParseNode value =
+ (LiteralParseNode) documentFieldContainsParseNode.getValue();
+ String fieldName = (String) fieldKey.getValue();
+ fieldName = replaceExpressionFieldNames(fieldName, keyAliasDocument,
sortedKeyNames);
+ final String containsValue = (String) value.getValue();
+ return contains(fieldName, containsValue, rawBsonDocument,
comparisonValuesDocument);
} else if (parseNode instanceof EqualParseNode) {
final EqualParseNode equalParseNode = (EqualParseNode) parseNode;
final LiteralParseNode lhs = (LiteralParseNode) equalParseNode.getLHS();
@@ -576,4 +589,61 @@ public final class SQLComparisonExpressionUtils {
}
}
+ /**
+ * Returns true if the value of the field contains the specified value. The
field can be:
+ * - A String that contains a particular substring
+ * - A Set that contains a particular element within the set
+ * - A List that contains a particular element within the list
+ * For other data types, returns false.
+ *
+ * @param fieldKey The field key for which value is checked for contains.
+ * @param containsValue The value to check against the field value.
+ * @param rawBsonDocument Bson Document representing the cell value on which
the comparison is
+ * to be performed.
+ * @param comparisonValuesDocument Bson Document with values placeholder.
+ * @return True if the value of the field contains containsValue, false
otherwise.
+ */
+ private static boolean contains(final String fieldKey, final String
containsValue,
+ final RawBsonDocument rawBsonDocument,
+ final BsonDocument comparisonValuesDocument)
{
+ BsonValue topLevelValue = rawBsonDocument.get(fieldKey);
+ BsonValue fieldValue = topLevelValue != null ?
+ topLevelValue :
+ CommonComparisonExpressionUtils.getFieldFromDocument(fieldKey,
rawBsonDocument);
+ if (fieldValue == null) {
+ return false;
+ }
+ BsonValue containsBsonValue = comparisonValuesDocument.get(containsValue);
+ if (containsBsonValue == null) {
+ return false;
+ }
+
+ if (fieldValue.isString()) {
+ if (!containsBsonValue.isString()) {
+ return false;
+ }
+ String fieldStr = ((BsonString) fieldValue).getValue();
+ String containsStr = ((BsonString) containsBsonValue).getValue();
+ return fieldStr.contains(containsStr);
+ } else if (fieldValue.isArray()) {
+ List<BsonValue> fieldValues = ((BsonArray) fieldValue).getValues();
+ return fieldValues.stream().anyMatch(element -> areEqual(element,
containsBsonValue));
+ } else if (CommonComparisonExpressionUtils.isBsonSet(fieldValue)) {
+ List<BsonValue> fieldValues =
+ ((BsonArray) ((BsonDocument)
fieldValue).get("$set")).getValues();
+ return fieldValues.stream().anyMatch(element -> areEqual(element,
containsBsonValue));
+ }
+ return false;
+ }
+
+ private static boolean areEqual(BsonValue value1, BsonValue value2) {
+ if (value1 == null && value2 == null) {
+ return true;
+ }
+ if (value1 == null || value2 == null) {
+ return false;
+ }
+ return value1.equals(value2);
+ }
+
}
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 031a93b04c..02420076a1 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
@@ -147,7 +147,7 @@ public class UpdateExpressionUtils {
String fieldKey = deleteEntry.getKey();
BsonValue newVal = deleteEntry.getValue();
BsonValue topLevelValue = bsonDocument.get(fieldKey);
- if (!isBsonSet(newVal)) {
+ if (!CommonComparisonExpressionUtils.isBsonSet(newVal)) {
throw new RuntimeException("Type of new value to be removed should be
sets only");
}
// If the top level field exists, perform the operation here and return.
@@ -225,7 +225,8 @@ public class UpdateExpressionUtils {
String fieldKey = addEntry.getKey();
BsonValue newVal = addEntry.getValue();
BsonValue topLevelValue = bsonDocument.get(fieldKey);
- if (!newVal.isNumber() && !newVal.isDecimal128() && !isBsonSet(newVal)) {
+ if (!newVal.isNumber() && !newVal.isDecimal128()
+ && !CommonComparisonExpressionUtils.isBsonSet(newVal)) {
throw new RuntimeException(
"Type of new value to be updated should be either number or sets
only");
}
@@ -777,24 +778,6 @@ public class UpdateExpressionUtils {
}
}
- /**
- * Returns true if the given BsonValue represents Set data structure.
- *
- * @param bsonValue The value.
- * @return True if the given BsonValue represents Set data structure.
- */
- private static boolean isBsonSet(final BsonValue bsonValue) {
- if (!bsonValue.isDocument()) {
- return false;
- }
- BsonDocument bsonDocument = (BsonDocument) bsonValue;
- if (bsonDocument.size() == 1 && bsonDocument.containsKey("$set")) {
- BsonValue value = bsonDocument.get("$set");
- return value != null && value.isArray();
- }
- return false;
- }
-
/**
* Returns true if both values represent Set data structure and the contents
of the Set are
* of same type.
@@ -806,7 +789,8 @@ public class UpdateExpressionUtils {
*/
private static boolean areBsonSetOfSameType(final BsonValue bsonValue1,
final BsonValue bsonValue2) {
- if (!isBsonSet(bsonValue1) || !isBsonSet(bsonValue2)) {
+ if (!CommonComparisonExpressionUtils.isBsonSet(bsonValue1)
+ || !CommonComparisonExpressionUtils.isBsonSet(bsonValue2)) {
return false;
}
BsonArray bsonArray1 = (BsonArray) ((BsonDocument) bsonValue1).get("$set");
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DocumentFieldContainsParseNode.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DocumentFieldContainsParseNode.java
new file mode 100644
index 0000000000..e8e736f734
--- /dev/null
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DocumentFieldContainsParseNode.java
@@ -0,0 +1,62 @@
+/*
+ * 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.parse;
+
+import org.apache.phoenix.compile.ColumnResolver;
+
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Parse Node to help determine whether the document field contains a given
value.
+ */
+public class DocumentFieldContainsParseNode extends CompoundParseNode {
+
+ DocumentFieldContainsParseNode(ParseNode fieldKey, ParseNode value) {
+ super(Arrays.asList(fieldKey, value));
+ }
+
+ @Override
+ public <T> T accept(ParseNodeVisitor<T> visitor) throws SQLException {
+ List<T> l = java.util.Collections.emptyList();
+ if (visitor.visitEnter(this)) {
+ l = acceptChildren(visitor);
+ }
+ return visitor.visitLeave(this, l);
+ }
+
+ @Override
+ public void toSQL(ColumnResolver resolver, StringBuilder buf) {
+ List<ParseNode> children = getChildren();
+ buf.append("contains(");
+ children.get(0).toSQL(resolver, buf);
+ buf.append(", ");
+ children.get(1).toSQL(resolver, buf);
+ buf.append(")");
+ }
+
+ public ParseNode getFieldKey() {
+ return getChildren().get(0);
+ }
+
+ public ParseNode getValue() {
+ return getChildren().get(1);
+ }
+}
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
index f70f4ad16f..d48352d125 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
@@ -303,6 +303,11 @@ public class ParseNodeFactory {
return new DocumentFieldBeginsWithParseNode(fieldKey, value);
}
+ public DocumentFieldContainsParseNode documentFieldContains(ParseNode
fieldKey,
+ ParseNode
value) {
+ return new DocumentFieldContainsParseNode(fieldKey, value);
+ }
+
public ColumnDef columnDef(ColumnName columnDefName, String sqlTypeName,
boolean isArray, Integer arrSize, Boolean
isNull,
Integer maxLength, Integer scale, boolean isPK,
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson1IT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson1IT.java
index 5ed87f4837..f93f85b64e 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson1IT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson1IT.java
@@ -207,6 +207,49 @@ public class Bson1IT extends ParallelStatsDisabledIT {
assertEquals(bsonDocument2, document2);
assertFalse(rs.next());
+
+ conditionExpression =
+ "begins_with(Title, :TitlePrefix) AND contains(#attr_5,
:Attr5Value) "
+ + "AND contains(#0, :NestedList1String)";
+
+ conditionDoc = new BsonDocument();
+ conditionDoc.put("$EXPR", new BsonString(conditionExpression));
+ conditionDoc.put("$VAL", compareValuesDocument);
+ BsonDocument keyDoc = new BsonDocument();
+ keyDoc.put("#attr_5", new BsonString("attr_5"));
+ keyDoc.put("#0", new BsonString("NestedList1"));
+ conditionDoc.put("$KEYS", keyDoc);
+
+ query = "SELECT * FROM " + tableName + " WHERE
BSON_CONDITION_EXPRESSION(COL, '"
+ + conditionDoc.toJson() + "')";
+ rs = conn.createStatement().executeQuery(query);
+
+ assertTrue(rs.next());
+ assertEquals("pk0002", rs.getString(1));
+ assertEquals(4596.354, rs.getDouble(2), 0.0);
+ document2 = (BsonDocument) rs.getObject(3);
+ assertEquals(bsonDocument2, document2);
+
+ assertFalse(rs.next());
+
+ conditionExpression =
+ "contains(attr_5, :NonExistentValue) OR begins_with(Title,
:TitlePrefix)";
+
+ conditionDoc = new BsonDocument();
+ conditionDoc.put("$EXPR", new BsonString(conditionExpression));
+ conditionDoc.put("$VAL", compareValuesDocument);
+
+ query = "SELECT * FROM " + tableName + " WHERE
BSON_CONDITION_EXPRESSION(COL, '"
+ + conditionDoc.toJson() + "')";
+ rs = conn.createStatement().executeQuery(query);
+
+ assertTrue(rs.next());
+ assertEquals("pk0002", rs.getString(1));
+ assertEquals(4596.354, rs.getDouble(2), 0.0);
+ document2 = (BsonDocument) rs.getObject(3);
+ assertEquals(bsonDocument2, document2);
+
+ assertFalse(rs.next());
}
}
@@ -222,7 +265,10 @@ public class Bson1IT extends ParallelStatsDisabledIT {
" \":Ids1\" : \"12\",\n" +
" \":NMap1_NList1\" : \"NListVal01\",\n" +
" \":InPublication\" : false,\n" +
- " \":NestedList1_xyz0123\" : \"xyz0123\"\n" +
+ " \":NestedList1_xyz0123\" : \"xyz0123\",\n" +
+ " \":Attr5Value\" : \"str001\",\n" +
+ " \":NestedList1String\" : \"1234abcd\",\n" +
+ " \":NonExistentValue\" : \"does_not_exist\"\n" +
"}";
return RawBsonDocument.parse(json);
}
diff --git
a/phoenix-core/src/test/java/org/apache/phoenix/util/bson/ComparisonExpressionUtilsTest.java
b/phoenix-core/src/test/java/org/apache/phoenix/util/bson/ComparisonExpressionUtilsTest.java
index a6eafb6be7..1178cbcb0a 100644
---
a/phoenix-core/src/test/java/org/apache/phoenix/util/bson/ComparisonExpressionUtilsTest.java
+++
b/phoenix-core/src/test/java/org/apache/phoenix/util/bson/ComparisonExpressionUtilsTest.java
@@ -1665,6 +1665,171 @@ public class ComparisonExpressionUtilsTest {
rawBsonDocument, compareValues));
}
+ @Test
+ public void testContainsFunction() {
+ RawBsonDocument rawBsonDocument = getContainsTestDocument();
+ RawBsonDocument compareValues = getContainsCompareValDocument();
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Title, :TitleSubstring)", rawBsonDocument,
compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Description, :DescriptionWord)", rawBsonDocument,
compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(NestedMap1.Title, :TitleSubstring)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Title, :NonExistentSubstring)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Description, :WrongWord)", rawBsonDocument,
compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Tags, :TagScience)", rawBsonDocument, compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Numbers, :NumberFive)", rawBsonDocument, compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(NestedList1, :NestedListString)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Tags, :NonExistentTag)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Numbers, :NumberTen)", rawBsonDocument, compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Categories, :CategoryFiction)", rawBsonDocument,
compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(StatusSet, :StatusActive)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Categories, :NonExistentCategory)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(StatusSet, :StatusInactive)", rawBsonDocument,
compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(BinaryDataSet, :BinaryHello)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(BinaryDataSet, :BinaryNotFound)", rawBsonDocument,
compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Title, :TitleSubstring) AND contains(Tags,
:TagScience)", rawBsonDocument,
+ compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Title, :NonExistentSubstring) OR contains(Tags,
:TagScience)",
+ rawBsonDocument, compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Title, :NonExistentSubstring) AND contains(Tags,
:TagScience)",
+ rawBsonDocument, compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "NOT contains(Title, :NonExistentSubstring)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "NOT contains(Title, :TitleSubstring)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(NonExistentField, :TitleSubstring)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Title, :NonExistentValue)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Title, :NumberFive)", rawBsonDocument, compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Numbers, :TitleSubstring)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(Id, :TitleSubstring)", rawBsonDocument, compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(InPublication, :TitleSubstring)", rawBsonDocument,
compareValues));
+
+ assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(NestedMap1.SubTags, :TagMath)", rawBsonDocument,
compareValues));
+
+ assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+ "contains(NestedMap1.SubTags, :NonExistentTag)", rawBsonDocument,
compareValues));
+ }
+
+ private static RawBsonDocument getContainsTestDocument() {
+ String json = "{\n" +
+ " \"Title\" : \"Advanced Data Science and Machine Learning\",\n" +
+ " \"Description\" : \"This book covers comprehensive topics in
Quantum Computing\",\n" +
+ " \"Tags\" : [ \"science\", \"technology\", \"programming\",
\"AI\" ],\n" +
+ " \"Numbers\" : [ 1, 2, 3, 5, 8, 13 ],\n" +
+ " \"NestedList1\" : [ -485.34, \"1234abcd\", \"xyz0123\",
\"test_string\" ],\n" +
+ " \"Categories\" : { \"$set\" : [ \"fiction\", \"educational\",
\"technical\" ] },\n" +
+ " \"StatusSet\" : { \"$set\" : [ \"active\", \"published\",
\"available\" ] },\n" +
+ " \"BinaryDataSet\" : { \"$set\" : [ {\n" +
+ " \"$binary\" : {\n" +
+ " \"base64\" : \"SGVsbG8=\",\n" +
+ " \"subType\" : \"00\"\n" +
+ " }\n" +
+ " }, {\n" +
+ " \"$binary\" : {\n" +
+ " \"base64\" : \"V29ybGQ=\",\n" +
+ " \"subType\" : \"00\"\n" +
+ " }\n" +
+ " }, {\n" +
+ " \"$binary\" : {\n" +
+ " \"base64\" : \"VGVzdA==\",\n" +
+ " \"subType\" : \"00\"\n" +
+ " }\n" +
+ " } ] },\n" +
+ " \"NestedMap1\" : {\n" +
+ " \"Title\" : \"Nested Advanced Data Science Guide\",\n" +
+ " \"SubTags\" : [ \"mathematics\", \"statistics\",
\"algorithms\" ],\n" +
+ " \"InnerSet\" : { \"$set\" : [ \"regression\",
\"classification\" ] }\n" +
+ " },\n" +
+ " \"Id\" : 101.01,\n" +
+ " \"InPublication\" : true\n" +
+ "}";
+ return RawBsonDocument.parse(json);
+ }
+
+ private static RawBsonDocument getContainsCompareValDocument() {
+ String json = "{\n" +
+ " \":TitleSubstring\" : \"Data Science\",\n" +
+ " \":DescriptionWord\" : \"Quantum Comput\",\n" +
+ " \":NonExistentSubstring\" : \"Quantum Physics\",\n" +
+ " \":WrongWord\" : \"geology\",\n" +
+ " \":TagScience\" : \"science\",\n" +
+ " \":TagMath\" : \"mathematics\",\n" +
+ " \":NonExistentTag\" : \"biology\",\n" +
+ " \":NumberFive\" : 5,\n" +
+ " \":NumberTen\" : 10,\n" +
+ " \":NestedListString\" : \"test_string\",\n" +
+ " \":CategoryFiction\" : \"fiction\",\n" +
+ " \":NonExistentCategory\" : \"romance\",\n" +
+ " \":StatusActive\" : \"active\",\n" +
+ " \":StatusInactive\" : \"inactive\",\n" +
+ " \":NonExistentValue\" : \"does_not_exist\",\n" +
+ " \":BinaryHello\" : {\n" +
+ " \"$binary\" : {\n" +
+ " \"base64\" : \"SGVsbG8=\",\n" +
+ " \"subType\" : \"00\"\n" +
+ " }\n" +
+ " },\n" +
+ " \":BinaryNotFound\" : {\n" +
+ " \"$binary\" : {\n" +
+ " \"base64\" : \"Tm90Rm91bmQ=\",\n" +
+ " \"subType\" : \"00\"\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+ return RawBsonDocument.parse(json);
+ }
+
private static RawBsonDocument getCompareValDocument() {
String json = "{\n" +
" \"$Id20\" : 101.011,\n" +