This is an automated email from the ASF dual-hosted git repository.

palashc 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 f09449a1d2 PHOENIX-7673 BSON Condition Function size() (#2237)
f09449a1d2 is described below

commit f09449a1d22e680755bdc75eb86e5c91abed4be0
Author: Palash Chauhan <[email protected]>
AuthorDate: Tue Jul 22 10:04:08 2025 -0700

    PHOENIX-7673 BSON Condition Function size() (#2237)
    
    Co-authored-by: Palash Chauhan 
<[email protected]>
---
 .../src/main/antlr3/PhoenixBsonExpression.g        |   2 +
 .../util/bson/SQLComparisonExpressionUtils.java    | 220 +++++++++++++--------
 .../phoenix/parse/DocumentFieldSizeParseNode.java  |  51 +++++
 .../org/apache/phoenix/parse/ParseNodeFactory.java |   4 +
 .../java/org/apache/phoenix/end2end/Bson1IT.java   |  19 +-
 .../util/bson/ComparisonExpressionUtilsTest.java   | 202 +++++++++++++++++++
 6 files changed, 419 insertions(+), 79 deletions(-)

diff --git a/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g 
b/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
index 53ba10103b..f6660236c8 100644
--- a/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
+++ b/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
@@ -36,6 +36,7 @@ tokens
     CONTAINS = 'contains';
     FIELD_TYPE = 'field_type';
     ATTR_TYPE = 'attribute_type';
+    SIZE = 'size';
 }
 
 @parser::header {
@@ -250,6 +251,7 @@ value_expression returns [ParseNode ret]
 
 term returns [ParseNode ret]
     :   e=literal_or_bind { $ret = e; }
+    |   SIZE LPAREN t=literal RPAREN {$ret = factory.documentFieldSize(t); }
     |   LPAREN l=one_or_more_expressions RPAREN
        {
                if(l.size() == 1) {
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 b31da51d4c..7cf858ee30 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
@@ -28,6 +28,7 @@ import org.apache.phoenix.parse.BsonExpressionParser;
 import org.apache.phoenix.parse.DocumentFieldBeginsWithParseNode;
 import org.apache.phoenix.parse.DocumentFieldContainsParseNode;
 import org.apache.phoenix.parse.DocumentFieldExistsParseNode;
+import org.apache.phoenix.parse.DocumentFieldSizeParseNode;
 import org.apache.phoenix.parse.DocumentFieldTypeParseNode;
 import org.apache.phoenix.parse.EqualParseNode;
 import org.apache.phoenix.parse.GreaterThanOrEqualParseNode;
@@ -41,7 +42,10 @@ 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.BsonBinary;
 import org.bson.BsonDocument;
+import org.bson.BsonInt32;
+import org.bson.BsonNumber;
 import org.bson.BsonString;
 import org.bson.BsonValue;
 import org.bson.RawBsonDocument;
@@ -190,80 +194,72 @@ public final class SQLComparisonExpressionUtils {
       return isFieldOfType(fieldName, type, rawBsonDocument, 
comparisonValuesDocument);
     } else if (parseNode instanceof EqualParseNode) {
       final EqualParseNode equalParseNode = (EqualParseNode) parseNode;
-      final LiteralParseNode lhs = (LiteralParseNode) equalParseNode.getLHS();
+      Object lhs =
+        getLHS(rawBsonDocument, keyAliasDocument, sortedKeyNames, 
equalParseNode.getLHS());
       final LiteralParseNode rhs = (LiteralParseNode) equalParseNode.getRHS();
-      String fieldKey = (String) lhs.getValue();
-      fieldKey = replaceExpressionFieldNames(fieldKey, keyAliasDocument, 
sortedKeyNames);
       final String expectedFieldValue = (String) rhs.getValue();
-      return isEquals(fieldKey, expectedFieldValue, rawBsonDocument, 
comparisonValuesDocument);
+      return isEquals(lhs, expectedFieldValue, rawBsonDocument, 
comparisonValuesDocument);
     } else if (parseNode instanceof NotEqualParseNode) {
       final NotEqualParseNode notEqualParseNode = (NotEqualParseNode) 
parseNode;
-      final LiteralParseNode lhs = (LiteralParseNode) 
notEqualParseNode.getLHS();
+      Object lhs =
+        getLHS(rawBsonDocument, keyAliasDocument, sortedKeyNames, 
notEqualParseNode.getLHS());
       final LiteralParseNode rhs = (LiteralParseNode) 
notEqualParseNode.getRHS();
-      String fieldKey = (String) lhs.getValue();
-      fieldKey = replaceExpressionFieldNames(fieldKey, keyAliasDocument, 
sortedKeyNames);
       final String expectedFieldValue = (String) rhs.getValue();
-      return !isEquals(fieldKey, expectedFieldValue, rawBsonDocument, 
comparisonValuesDocument);
+      return !isEquals(lhs, expectedFieldValue, rawBsonDocument, 
comparisonValuesDocument);
     } else if (parseNode instanceof LessThanParseNode) {
       final LessThanParseNode lessThanParseNode = (LessThanParseNode) 
parseNode;
-      final LiteralParseNode lhs = (LiteralParseNode) 
lessThanParseNode.getLHS();
+      Object lhs =
+        getLHS(rawBsonDocument, keyAliasDocument, sortedKeyNames, 
lessThanParseNode.getLHS());
       final LiteralParseNode rhs = (LiteralParseNode) 
lessThanParseNode.getRHS();
-      String fieldKey = (String) lhs.getValue();
-      fieldKey = replaceExpressionFieldNames(fieldKey, keyAliasDocument, 
sortedKeyNames);
       final String expectedFieldValue = (String) rhs.getValue();
-      return lessThan(fieldKey, expectedFieldValue, rawBsonDocument, 
comparisonValuesDocument);
+      return lessThan(lhs, expectedFieldValue, rawBsonDocument, 
comparisonValuesDocument);
     } else if (parseNode instanceof LessThanOrEqualParseNode) {
       final LessThanOrEqualParseNode lessThanOrEqualParseNode =
         (LessThanOrEqualParseNode) parseNode;
-      final LiteralParseNode lhs = (LiteralParseNode) 
lessThanOrEqualParseNode.getLHS();
+      Object lhs = getLHS(rawBsonDocument, keyAliasDocument, sortedKeyNames,
+        lessThanOrEqualParseNode.getLHS());
       final LiteralParseNode rhs = (LiteralParseNode) 
lessThanOrEqualParseNode.getRHS();
-      String fieldKey = (String) lhs.getValue();
-      fieldKey = replaceExpressionFieldNames(fieldKey, keyAliasDocument, 
sortedKeyNames);
       final String expectedFieldValue = (String) rhs.getValue();
-      return lessThanOrEquals(fieldKey, expectedFieldValue, rawBsonDocument,
-        comparisonValuesDocument);
+      return lessThanOrEquals(lhs, expectedFieldValue, rawBsonDocument, 
comparisonValuesDocument);
     } else if (parseNode instanceof GreaterThanParseNode) {
       final GreaterThanParseNode greaterThanParseNode = (GreaterThanParseNode) 
parseNode;
-      final LiteralParseNode lhs = (LiteralParseNode) 
greaterThanParseNode.getLHS();
+      Object lhs =
+        getLHS(rawBsonDocument, keyAliasDocument, sortedKeyNames, 
greaterThanParseNode.getLHS());
       final LiteralParseNode rhs = (LiteralParseNode) 
greaterThanParseNode.getRHS();
-      String fieldKey = (String) lhs.getValue();
-      fieldKey = replaceExpressionFieldNames(fieldKey, keyAliasDocument, 
sortedKeyNames);
       final String expectedFieldValue = (String) rhs.getValue();
-      return greaterThan(fieldKey, expectedFieldValue, rawBsonDocument, 
comparisonValuesDocument);
+      return greaterThan(lhs, expectedFieldValue, rawBsonDocument, 
comparisonValuesDocument);
     } else if (parseNode instanceof GreaterThanOrEqualParseNode) {
       final GreaterThanOrEqualParseNode greaterThanOrEqualParseNode =
         (GreaterThanOrEqualParseNode) parseNode;
-      final LiteralParseNode lhs = (LiteralParseNode) 
greaterThanOrEqualParseNode.getLHS();
+      Object lhs = getLHS(rawBsonDocument, keyAliasDocument, sortedKeyNames,
+        greaterThanOrEqualParseNode.getLHS());
       final LiteralParseNode rhs = (LiteralParseNode) 
greaterThanOrEqualParseNode.getRHS();
-      String fieldKey = (String) lhs.getValue();
-      fieldKey = replaceExpressionFieldNames(fieldKey, keyAliasDocument, 
sortedKeyNames);
       final String expectedFieldValue = (String) rhs.getValue();
-      return greaterThanOrEquals(fieldKey, expectedFieldValue, rawBsonDocument,
+      return greaterThanOrEquals(lhs, expectedFieldValue, rawBsonDocument,
         comparisonValuesDocument);
     } else if (parseNode instanceof BetweenParseNode) {
       final BetweenParseNode betweenParseNode = (BetweenParseNode) parseNode;
-      final LiteralParseNode fieldKey = (LiteralParseNode) 
betweenParseNode.getChildren().get(0);
-      final LiteralParseNode lhs = (LiteralParseNode) 
betweenParseNode.getChildren().get(1);
-      final LiteralParseNode rhs = (LiteralParseNode) 
betweenParseNode.getChildren().get(2);
-      String fieldName = (String) fieldKey.getValue();
-      fieldName = replaceExpressionFieldNames(fieldName, keyAliasDocument, 
sortedKeyNames);
-      final String expectedFieldValue1 = (String) lhs.getValue();
-      final String expectedFieldValue2 = (String) rhs.getValue();
-      return betweenParseNode.isNegate() != between(fieldName, 
expectedFieldValue1,
-        expectedFieldValue2, rawBsonDocument, comparisonValuesDocument);
+      Object lhs = getLHS(rawBsonDocument, keyAliasDocument, sortedKeyNames,
+        betweenParseNode.getChildren().get(0));
+      final LiteralParseNode betweenParseNode1 =
+        (LiteralParseNode) betweenParseNode.getChildren().get(1);
+      final LiteralParseNode betweenParseNode2 =
+        (LiteralParseNode) betweenParseNode.getChildren().get(2);
+      final String expectedFieldValue1 = (String) betweenParseNode1.getValue();
+      final String expectedFieldValue2 = (String) betweenParseNode2.getValue();
+      return betweenParseNode.isNegate() != between(lhs, expectedFieldValue1, 
expectedFieldValue2,
+        rawBsonDocument, comparisonValuesDocument);
     } else if (parseNode instanceof InListParseNode) {
       final InListParseNode inListParseNode = (InListParseNode) parseNode;
       final List<ParseNode> childrenNodes = inListParseNode.getChildren();
-      final LiteralParseNode fieldKey = (LiteralParseNode) 
childrenNodes.get(0);
-      String fieldName = (String) fieldKey.getValue();
-      fieldName = replaceExpressionFieldNames(fieldName, keyAliasDocument, 
sortedKeyNames);
+      Object lhs = getLHS(rawBsonDocument, keyAliasDocument, sortedKeyNames, 
childrenNodes.get(0));
       final String[] inList = new String[childrenNodes.size() - 1];
       for (int i = 1; i < childrenNodes.size(); i++) {
         LiteralParseNode literalParseNode = (LiteralParseNode) 
childrenNodes.get(i);
         inList[i - 1] = ((String) literalParseNode.getValue());
       }
       return inListParseNode.isNegate()
-          != in(rawBsonDocument, comparisonValuesDocument, fieldName, inList);
+          != in(rawBsonDocument, comparisonValuesDocument, lhs, inList);
     } else if (parseNode instanceof AndParseNode) {
       AndParseNode andParseNode = (AndParseNode) parseNode;
       List<ParseNode> children = andParseNode.getChildren();
@@ -298,6 +294,65 @@ public final class SQLComparisonExpressionUtils {
     }
   }
 
+  /**
+   * Return the value for the given LHS ParseNode and replace any alias using 
the provided
+   * keyAliasDocument. The value can either be a literal or the size() 
function on a fieldKey in the
+   * provided document.
+   * @param doc              BSON Document value of the Cell.
+   * @param keyAliasDocument The BSON Document consisting of place-holder for 
keys.
+   * @param sortedKeyNames   The document key names in the descending sorted 
order of their string
+   *                         length.
+   * @param parseNode        ParseNode for either literal expression or size() 
function.
+   * @return Literal value if parseNode is LiteralParseNode or evaluate size() 
function if parseNode
+   *         is DocumentFieldSizeParseNode.
+   */
+  private static Object getLHS(final RawBsonDocument doc, final BsonDocument 
keyAliasDocument,
+    final List<String> sortedKeyNames, ParseNode parseNode) {
+    if (parseNode instanceof LiteralParseNode) {
+      String fieldKey = (String) ((LiteralParseNode) parseNode).getValue();
+      return replaceExpressionFieldNames(fieldKey, keyAliasDocument, 
sortedKeyNames);
+    } else if (parseNode instanceof DocumentFieldSizeParseNode) {
+      String fieldKey = (String) ((DocumentFieldSizeParseNode) 
parseNode).getValue();
+      fieldKey = replaceExpressionFieldNames(fieldKey, keyAliasDocument, 
sortedKeyNames);
+      return new BsonInt32(getSizeOfBsonValue(fieldKey, doc));
+    }
+    return null;
+  }
+
+  /**
+   * Returns the size of the field of the BsonDocument at the given key. If 
the field is not present
+   * in the document, returns 0. If the field is String, returns the length of 
the string. If the
+   * field is Binary, returns the length of the binary data. If the field is 
Set/Array/Document,
+   * returns the number of elements.
+   * @param fieldKey        The field key for which size has to be returned.
+   * @param rawBsonDocument Bson Document representing the cell value from 
which the field is to be
+   *                        retrieved.
+   */
+  private static Integer getSizeOfBsonValue(final String fieldKey,
+    final RawBsonDocument rawBsonDocument) {
+    BsonValue topLevelValue = rawBsonDocument.get(fieldKey);
+    BsonValue fieldValue = topLevelValue != null
+      ? topLevelValue
+      : CommonComparisonExpressionUtils.getFieldFromDocument(fieldKey, 
rawBsonDocument);
+    if (fieldValue == null) {
+      return 0;
+    }
+    if (fieldValue instanceof BsonString) {
+      return ((BsonString) fieldValue).getValue().length();
+    } else if (fieldValue instanceof BsonBinary) {
+      return ((BsonBinary) fieldValue).getData().length;
+    } else if (fieldValue instanceof BsonArray) {
+      return ((BsonArray) fieldValue).size();
+    } else if (CommonComparisonExpressionUtils.isBsonSet(fieldValue)) {
+      return ((BsonArray) ((BsonDocument) fieldValue).get("$set")).size();
+    } else if (fieldValue instanceof BsonDocument) {
+      return ((BsonDocument) fieldValue).size();
+    } else {
+      throw new BsonConditionInvalidArgumentException("Unsupported type for 
size() function. "
+        + fieldValue.getClass() + ", supported types: String, Binary, Set, 
Array, Document.");
+    }
+  }
+
   /**
    * Replaces expression field names with their corresponding actual field 
names. This method
    * supports field name aliasing by replacing placeholder expressions with 
actual field names from
@@ -347,13 +402,18 @@ public final class SQLComparisonExpressionUtils {
    * @param comparisonValuesDocument Bson Document with values placeholder.
    * @return True if the comparison is successful, False otherwise.
    */
-  private static boolean compare(final String fieldKey, final String 
expectedFieldValue,
+  private static boolean compare(final Object fieldKey, final String 
expectedFieldValue,
     final CommonComparisonExpressionUtils.CompareOp compareOp,
     final RawBsonDocument rawBsonDocument, final BsonDocument 
comparisonValuesDocument) {
-    BsonValue topLevelValue = rawBsonDocument.get(fieldKey);
-    BsonValue value = topLevelValue != null
-      ? topLevelValue
-      : CommonComparisonExpressionUtils.getFieldFromDocument(fieldKey, 
rawBsonDocument);
+    BsonValue value;
+    if (fieldKey instanceof BsonNumber) {
+      value = (BsonNumber) fieldKey;
+    } else {
+      BsonValue topLevelValue = rawBsonDocument.get((String) fieldKey);
+      value = topLevelValue != null
+        ? topLevelValue
+        : CommonComparisonExpressionUtils.getFieldFromDocument((String) 
fieldKey, rawBsonDocument);
+    }
     if (value != null) {
       BsonValue compareValue = 
comparisonValuesDocument.get(expectedFieldValue);
       return CommonComparisonExpressionUtils.compareValues(value, 
compareValue, compareOp);
@@ -380,17 +440,17 @@ public final class SQLComparisonExpressionUtils {
   /**
    * Returns true if the value of the field is less than the value represented 
by {@code
    * expectedFieldValue}. The comparison can happen only if the data type of 
both values match.
-   * @param fieldKey                 The field key for which value is compared 
against
-   *                                 expectedFieldValue.
+   * @param lhs                      LHS compared against expectedFieldValue. 
It can either be a
+   *                                 fieldKey in the document or size of a 
field.
    * @param expectedFieldValue       The literal value to compare 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 is less than expectedFieldValue.
    */
-  private static boolean lessThan(final String fieldKey, final String 
expectedFieldValue,
+  private static boolean lessThan(final Object lhs, final String 
expectedFieldValue,
     final RawBsonDocument rawBsonDocument, final BsonDocument 
comparisonValuesDocument) {
-    return compare(fieldKey, expectedFieldValue, 
CommonComparisonExpressionUtils.CompareOp.LESS,
+    return compare(lhs, expectedFieldValue, 
CommonComparisonExpressionUtils.CompareOp.LESS,
       rawBsonDocument, comparisonValuesDocument);
   }
 
@@ -398,35 +458,34 @@ public final class SQLComparisonExpressionUtils {
    * Returns true if the value of the field is less than or equal to the value 
represented by
    * {@code expectedFieldValue}. The comparison can happen only if the data 
type of both values
    * match.
-   * @param fieldKey                 The field key for which value is compared 
against
-   *                                 expectedFieldValue.
+   * @param lhs                      LHS compared against expectedFieldValue. 
It can either be a
+   *                                 fieldKey in the document or size of a 
field.
    * @param expectedFieldValue       The literal value to compare 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 is less than or equal to 
expectedFieldValue.
    */
-  private static boolean lessThanOrEquals(final String fieldKey, final String 
expectedFieldValue,
+  private static boolean lessThanOrEquals(final Object lhs, final String 
expectedFieldValue,
     final RawBsonDocument rawBsonDocument, final BsonDocument 
comparisonValuesDocument) {
-    return compare(fieldKey, expectedFieldValue,
-      CommonComparisonExpressionUtils.CompareOp.LESS_OR_EQUAL, rawBsonDocument,
-      comparisonValuesDocument);
+    return compare(lhs, expectedFieldValue, 
CommonComparisonExpressionUtils.CompareOp.LESS_OR_EQUAL,
+      rawBsonDocument, comparisonValuesDocument);
   }
 
   /**
    * Returns true if the value of the field is greater than the value 
represented by {@code
    * expectedFieldValue}. The comparison can happen only if the data type of 
both values match.
-   * @param fieldKey                 The field key for which value is compared 
against
-   *                                 expectedFieldValue.
+   * @param lhs                      LHS compared against expectedFieldValue. 
It can either be a
+   *                                 fieldKey in the document or size of a 
field.
    * @param expectedFieldValue       The literal value to compare 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 is greater than expectedFieldValue.
    */
-  private static boolean greaterThan(final String fieldKey, final String 
expectedFieldValue,
+  private static boolean greaterThan(final Object lhs, final String 
expectedFieldValue,
     final RawBsonDocument rawBsonDocument, final BsonDocument 
comparisonValuesDocument) {
-    return compare(fieldKey, expectedFieldValue, 
CommonComparisonExpressionUtils.CompareOp.GREATER,
+    return compare(lhs, expectedFieldValue, 
CommonComparisonExpressionUtils.CompareOp.GREATER,
       rawBsonDocument, comparisonValuesDocument);
   }
 
@@ -434,17 +493,17 @@ public final class SQLComparisonExpressionUtils {
    * Returns true if the value of the field is greater than or equal to the 
value represented by
    * {@code expectedFieldValue}. The comparison can happen only if the data 
type of both values
    * match.
-   * @param fieldKey                 The field key for which value is compared 
against
-   *                                 expectedFieldValue.
+   * @param lhs                      LHS compared against expectedFieldValue. 
It can either be a
+   *                                 fieldKey in the document or size of a 
field.
    * @param expectedFieldValue       The literal value to compare 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 is greater than or equal to 
expectedFieldValue.
    */
-  private static boolean greaterThanOrEquals(final String fieldKey, final 
String expectedFieldValue,
+  private static boolean greaterThanOrEquals(final Object lhs, final String 
expectedFieldValue,
     final RawBsonDocument rawBsonDocument, final BsonDocument 
comparisonValuesDocument) {
-    return compare(fieldKey, expectedFieldValue,
+    return compare(lhs, expectedFieldValue,
       CommonComparisonExpressionUtils.CompareOp.GREATER_OR_EQUAL, 
rawBsonDocument,
       comparisonValuesDocument);
   }
@@ -454,7 +513,8 @@ public final class SQLComparisonExpressionUtils {
    * {@code expectedFieldValue1} and less than or equal to the value 
represented by
    * {@code expectedFieldValue2}. The comparison can happen only if the data 
type of both values
    * match.
-   * @param fieldKey                 The field key for which value is compared 
against two values.
+   * @param lhs                      LHS which is compared against two values. 
It can either be a
+   *                                 fieldKey in the document or size of a 
field.
    * @param expectedFieldValue1      The first literal value to compare 
against the field value.
    * @param expectedFieldValue2      The second literal value to compare 
against the field value.
    * @param rawBsonDocument          Bson Document representing the cell value 
on which the
@@ -464,12 +524,11 @@ public final class SQLComparisonExpressionUtils {
    *         expectedFieldValue1 and less than or equal to the value 
represented by
    *         expectedFieldValue2.
    */
-  private static boolean between(final String fieldKey, final String 
expectedFieldValue1,
+  private static boolean between(final Object lhs, final String 
expectedFieldValue1,
     final String expectedFieldValue2, final RawBsonDocument rawBsonDocument,
     final BsonDocument comparisonValuesDocument) {
-    return greaterThanOrEquals(fieldKey, expectedFieldValue1, rawBsonDocument,
-      comparisonValuesDocument)
-      && lessThanOrEquals(fieldKey, expectedFieldValue2, rawBsonDocument, 
comparisonValuesDocument);
+    return greaterThanOrEquals(lhs, expectedFieldValue1, rawBsonDocument, 
comparisonValuesDocument)
+      && lessThanOrEquals(lhs, expectedFieldValue2, rawBsonDocument, 
comparisonValuesDocument);
   }
 
   /**
@@ -479,23 +538,28 @@ public final class SQLComparisonExpressionUtils {
    * @param rawBsonDocument          Bson Document representing the cell value 
on which the
    *                                 comparison is to be performed.
    * @param comparisonValuesDocument Bson Document with values placeholder.
-   * @param fieldKey                 The field key for which value is compared 
against
-   *                                 expectedInValues.
+   * @param lhs                      LHS which is compared against 
expectedInValues. It can either
+   *                                 be a fieldKey in the document or size of 
a field.
    * @param expectedInValues         The array of values for comparison, 
separated by comma.
    * @return True if the value of the field equals to any of the comma 
separated values represented
    *         by expectedInValues. The equality check is successful only if the 
value and the data
    *         type both match.
    */
   private static boolean in(final RawBsonDocument rawBsonDocument,
-    final BsonDocument comparisonValuesDocument, final String fieldKey,
+    final BsonDocument comparisonValuesDocument, final Object lhs,
     final String... expectedInValues) {
-    BsonValue topLevelValue = rawBsonDocument.get(fieldKey);
-    BsonValue value = topLevelValue != null
-      ? topLevelValue
-      : CommonComparisonExpressionUtils.getFieldFromDocument(fieldKey, 
rawBsonDocument);
+    BsonValue value;
+    if (lhs instanceof BsonNumber) {
+      value = (BsonNumber) lhs;
+    } else {
+      BsonValue topLevelValue = rawBsonDocument.get((String) lhs);
+      value = topLevelValue != null
+        ? topLevelValue
+        : CommonComparisonExpressionUtils.getFieldFromDocument((String) lhs, 
rawBsonDocument);
+    }
     if (value != null) {
       for (String expectedInVal : expectedInValues) {
-        if (isEquals(fieldKey, expectedInVal, rawBsonDocument, 
comparisonValuesDocument)) {
+        if (isEquals(lhs, expectedInVal, rawBsonDocument, 
comparisonValuesDocument)) {
           return true;
         }
       }
@@ -507,17 +571,17 @@ public final class SQLComparisonExpressionUtils {
    * Returns true if the value of the field is equal to the value represented 
by {@code
    * expectedFieldValue}. The equality check is successful only if the value 
and the data type both
    * match.
-   * @param fieldKey                 The field key for which value is compared 
against
-   *                                 expectedFieldValue.
+   * @param lhs                      LHS compared against expectedFieldValue. 
It can either be a
+   *                                 fieldKey in the document or size of a 
field.
    * @param expectedFieldValue       The literal value to compare 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 is equal to expectedFieldValue.
    */
-  private static boolean isEquals(final String fieldKey, final String 
expectedFieldValue,
+  private static boolean isEquals(final Object lhs, final String 
expectedFieldValue,
     final RawBsonDocument rawBsonDocument, final BsonDocument 
comparisonValuesDocument) {
-    return compare(fieldKey, expectedFieldValue, 
CommonComparisonExpressionUtils.CompareOp.EQUALS,
+    return compare(lhs, expectedFieldValue, 
CommonComparisonExpressionUtils.CompareOp.EQUALS,
       rawBsonDocument, comparisonValuesDocument);
   }
 
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DocumentFieldSizeParseNode.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DocumentFieldSizeParseNode.java
new file mode 100644
index 0000000000..a278fe3de1
--- /dev/null
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DocumentFieldSizeParseNode.java
@@ -0,0 +1,51 @@
+/*
+ * 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 java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+import org.apache.phoenix.compile.ColumnResolver;
+
+public class DocumentFieldSizeParseNode extends CompoundParseNode {
+
+  public DocumentFieldSizeParseNode(ParseNode children) {
+    super(Collections.singletonList(children));
+  }
+
+  @Override
+  public <T> T accept(ParseNodeVisitor<T> visitor) throws SQLException {
+    List<T> l = 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("size(");
+    children.get(0).toSQL(resolver, buf);
+    buf.append(")");
+  }
+
+  public Object getValue() {
+    return ((LiteralParseNode) getChildren().get(0)).getValue();
+  }
+}
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 083ad9279f..d28dcc7837 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
@@ -314,6 +314,10 @@ public class ParseNodeFactory {
     return new DocumentFieldTypeParseNode(fieldKey, value);
   }
 
+  public DocumentFieldSizeParseNode documentFieldSize(ParseNode fieldKey) {
+    return new DocumentFieldSizeParseNode(fieldKey);
+  }
+
   public ColumnDef columnDef(ColumnName columnDefName, String sqlTypeName, 
boolean isArray,
     Integer arrSize, Boolean isNull, Integer maxLength, Integer scale, boolean 
isPK,
     SortOrder sortOrder, String expressionStr, Integer encodedQualifier, 
boolean isRowTimestamp) {
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 c0441afa1f..ac3ed5216f 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
@@ -270,6 +270,22 @@ public class Bson1IT extends ParallelStatsDisabledIT {
         + conditionDoc.toJson() + "')";
       rs = conn.createStatement().executeQuery(query);
       assertFalse(rs.next());
+
+      conditionExpression = "size(#Title) > :size3";
+
+      conditionDoc = new BsonDocument();
+      conditionDoc.put("$EXPR", new BsonString(conditionExpression));
+      conditionDoc.put("$VAL", compareValuesDocument);
+      keyDoc = new BsonDocument();
+      keyDoc.put("#Title", new BsonString("Title"));
+      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));
+      assertFalse(rs.next());
     }
   }
 
@@ -282,7 +298,8 @@ public class Bson1IT extends ParallelStatsDisabledIT {
         + "  \":InPublication\" : false,\n" + "  \":NestedList1_xyz0123\" : 
\"xyz0123\",\n"
         + "  \":Attr5Value\" : \"str001\",\n" + "  \":NestedList1String\" : 
\"1234abcd\",\n"
         + "  \":NonExistentValue\" : \"does_not_exist\"\n" + "  \":L\" : 
\"L\"\n"
-        + "  \":NS\" : \"NS\"\n" + "}";
+        + "  \":NS\" : \"NS\"\n" + "  \":size3\" : 3\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 8ae3db4bb1..4fccf2aa35 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
@@ -2171,4 +2171,206 @@ public class ComparisonExpressionUtilsTest {
     return RawBsonDocument.parse(json);
   }
 
+  @Test
+  public void testSizeFunction() {
+    RawBsonDocument rawBsonDocument = getSizeTestDocument();
+    RawBsonDocument compareValues = getSizeCompareValDocument();
+
+    // Test string size - should return string length
+    assertTrue(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(ShortString) = :Size5", 
rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(LongString) = :Size24", 
rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(EmptyString) = :Size0", 
rawBsonDocument, compareValues));
+
+    // Test array size - should return number of elements
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(SmallArray)
 = :Size3",
+      rawBsonDocument, compareValues));
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(LargeArray)
 = :Size5",
+      rawBsonDocument, compareValues));
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(EmptyArray)
 = :Size0",
+      rawBsonDocument, compareValues));
+
+    // Test document size - should return number of fields
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(SmallDoc)
 = :Size2",
+      rawBsonDocument, compareValues));
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(LargeDoc)
 = :Size4",
+      rawBsonDocument, compareValues));
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(EmptyDoc)
 = :Size0",
+      rawBsonDocument, compareValues));
+
+    // Test set size - should return number of elements in set
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(StringSet)
 = :Size3",
+      rawBsonDocument, compareValues));
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(NumberSet)
 = :Size4",
+      rawBsonDocument, compareValues));
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(EmptySet)
 = :Size0",
+      rawBsonDocument, compareValues));
+
+    // Test binary size - should return byte array length
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(BinaryData)
 = :Size5",
+      rawBsonDocument, compareValues));
+
+    // Test nested field sizes
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(NestedDoc.NestedString) = :Size12", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(NestedDoc.NestedArray) = :Size3", rawBsonDocument, compareValues));
+
+    // Test non-existent field - should return 0
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(NonExistentField) = :Size0", rawBsonDocument, compareValues));
+
+    // Test size with comparison operators
+    assertTrue(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(LongString) > :Size20", 
rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(LongString) >= :Size24", 
rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(ShortString) < :Size10", 
rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(ShortString) <= :Size5", 
rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(ShortString) <> :Size10", 
rawBsonDocument, compareValues));
+
+    // Test size with IN operator
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(SmallArray) IN (:Size3, :Size4, :Size5)", rawBsonDocument, 
compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(SmallArray) IN (:Size1, :Size2, :Size4)", rawBsonDocument, 
compareValues));
+
+    // Test size in complex boolean expressions
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(ShortString) = :Size5 AND size(SmallArray) = :Size3", 
rawBsonDocument, compareValues));
+
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(EmptyString) = :Size0 OR size(LargeArray) = :Size5", 
rawBsonDocument, compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(ShortString) = :Size10 AND size(SmallArray) = :Size3", 
rawBsonDocument, compareValues));
+
+    // Test NOT with size
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "NOT size(ShortString) = :Size10", rawBsonDocument, compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "NOT size(ShortString) = :Size5", rawBsonDocument, compareValues));
+
+    // Test size combined with other functions
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(ShortString) = :Size5 AND field_exists(ShortString)", 
rawBsonDocument, compareValues));
+
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(NonExistentField) = :Size0 AND 
field_not_exists(NonExistentField)", rawBsonDocument,
+      compareValues));
+
+    // Test size with BETWEEN
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(LongString) BETWEEN :Size20 AND :Size30", rawBsonDocument, 
compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(ShortString) BETWEEN :Size10 AND :Size20", rawBsonDocument, 
compareValues));
+
+    // Test negative cases - wrong size comparisons
+    assertFalse(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(ShortString) = :Size10", 
rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(EmptyArray) = :Size5", 
rawBsonDocument, compareValues));
+    
assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression("size(SmallDoc)
 = :Size10",
+      rawBsonDocument, compareValues));
+
+    // Test array element size
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(ArrayOfStrings[0]) = :Size6", rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(ArrayOfStrings[1]) = :Size5", rawBsonDocument, compareValues));
+
+    // Test size comparison with specific values (size() only as LHS)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(ArrayOfStrings[0]) > :Size5", rawBsonDocument, compareValues)); // 
"String" (6) > 5
+
+    // Test edge cases with very large sizes
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "size(VeryLargeArray) = :Size100", rawBsonDocument, compareValues));
+
+    // Test size comparisons with specific values (size() only as LHS)
+    assertTrue(SQLComparisonExpressionUtils
+      .evaluateConditionExpression("size(ShortString) < :Size24", 
rawBsonDocument, compareValues)); // "Hello"
+    // (5)
+    // <
+    // 24
+
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(EmptyArray)
 < :Size3",
+      rawBsonDocument, compareValues)); // 0 < 3
+
+    
assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression("size(SmallDoc)
 < :Size4",
+      rawBsonDocument, compareValues)); // 2 < 4
+
+    // Test error cases for unsupported types
+    try {
+      
SQLComparisonExpressionUtils.evaluateConditionExpression("size(NumberField) = 
:Size5",
+        rawBsonDocument, compareValues);
+      fail("Expected BsonConditionInvalidArgumentException for number field");
+    } catch (BsonConditionInvalidArgumentException e) {
+      // expected
+    }
+
+    try {
+      
SQLComparisonExpressionUtils.evaluateConditionExpression("size(BooleanField) = 
:Size1",
+        rawBsonDocument, compareValues);
+      fail("Expected BsonConditionInvalidArgumentException for boolean field");
+    } catch (BsonConditionInvalidArgumentException e) {
+      // expected
+    }
+
+    try {
+      
SQLComparisonExpressionUtils.evaluateConditionExpression("size(NullField) = 
:Size0",
+        rawBsonDocument, compareValues);
+      fail("Expected BsonConditionInvalidArgumentException for null field");
+    } catch (BsonConditionInvalidArgumentException e) {
+      // expected
+    }
+  }
+
+  private static RawBsonDocument getSizeTestDocument() {
+    String json = "{\n" + "  \"ShortString\" : \"Hello\",\n"
+      + "  \"LongString\" : \"This is a longer string!\",\n" + "  
\"EmptyString\" : \"\",\n"
+      + "  \"SmallArray\" : [ 1, 2, 3 ],\n"
+      + "  \"LargeArray\" : [ \"a\", \"b\", \"c\", \"d\", \"e\" ],\n" + "  
\"EmptyArray\" : [ ],\n"
+      + "  \"SmallDoc\" : {\n" + "    \"field1\" : \"value1\",\n" + "    
\"field2\" : \"value2\"\n"
+      + "  },\n" + "  \"LargeDoc\" : {\n" + "    \"name\" : \"John\",\n" + "   
 \"age\" : 30,\n"
+      + "    \"city\" : \"New York\",\n" + "    \"active\" : true\n" + "  },\n"
+      + "  \"EmptyDoc\" : { },\n"
+      + "  \"StringSet\" : { \"$set\" : [ \"apple\", \"banana\", \"cherry\" ] 
},\n"
+      + "  \"NumberSet\" : { \"$set\" : [ 10, 20, 30, 40 ] },\n"
+      + "  \"EmptySet\" : { \"$set\" : [ ] },\n" + "  \"BinaryData\" : {\n"
+      + "    \"$binary\" : {\n" + "      \"base64\" : \"SGVsbG8=\",\n"
+      + "      \"subType\" : \"00\"\n" + "    }\n" + "  },\n" + "  
\"NestedDoc\" : {\n"
+      + "    \"NestedString\" : \"Nested Value\",\n"
+      + "    \"NestedArray\" : [ \"x\", \"y\", \"z\" ]\n" + "  },\n"
+      + "  \"ArrayOfStrings\" : [ \"String\", \"World\" ],\n" + "  
\"NumberField\" : 42,\n"
+      + "  \"BooleanField\" : true,\n" + "  \"NullField\" : null,\n" + "  
\"VeryLargeArray\" : [ ";
+
+    // Generate a large array with 100 elements
+    for (int i = 0; i < 100; i++) {
+      json += i;
+      if (i < 99) {
+        json += ", ";
+      }
+    }
+    json += " ]\n}";
+
+    return RawBsonDocument.parse(json);
+  }
+
+  private static RawBsonDocument getSizeCompareValDocument() {
+    String json = "{\n" + "  \":Size0\" : 0,\n" + "  \":Size1\" : 1,\n" + "  
\":Size2\" : 2,\n"
+      + "  \":Size3\" : 3,\n" + "  \":Size4\" : 4,\n" + "  \":Size5\" : 5,\n"
+      + "  \":Size6\" : 6,\n" + "  \":Size10\" : 10,\n" + "  \":Size12\" : 
12,\n"
+      + "  \":Size20\" : 20,\n" + "  \":Size24\" : 24,\n" + "  \":Size30\" : 
30,\n"
+      + "  \":Size100\" : 100\n" + "}";
+    return RawBsonDocument.parse(json);
+  }
+
 }


Reply via email to