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 e8ff86e623 PHOENIX-7663 : BSON Condition Function field_type() (#2232)
e8ff86e623 is described below

commit e8ff86e6235f8bd65892d9126d797d497f3cf978
Author: Palash Chauhan <palashc...@gmail.com>
AuthorDate: Sat Jul 19 12:00:37 2025 -0700

    PHOENIX-7663 : BSON Condition Function field_type() (#2232)
    
    Co-authored-by: Palash Chauhan 
<p.chau...@pchauha-ltmgv47.internal.salesforce.com>
---
 .../src/main/antlr3/PhoenixBsonExpression.g        |   4 +
 .../util/bson/CommonComparisonExpressionUtils.java |  24 ++
 .../util/bson/SQLComparisonExpressionUtils.java    |  62 +++++
 .../phoenix/parse/DocumentFieldTypeParseNode.java  |  60 +++++
 .../org/apache/phoenix/parse/ParseNodeFactory.java |   4 +
 .../java/org/apache/phoenix/end2end/Bson1IT.java   |  25 +-
 .../util/bson/ComparisonExpressionUtilsTest.java   | 279 +++++++++++++++++++++
 7 files changed, 457 insertions(+), 1 deletion(-)

diff --git a/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g 
b/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
index 9edccc51d2..53ba10103b 100644
--- a/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
+++ b/phoenix-core-client/src/main/antlr3/PhoenixBsonExpression.g
@@ -34,6 +34,8 @@ tokens
     FIELD_NOT = 'field_not_exists';
     BEGINS_WITH = 'begins_with';
     CONTAINS = 'contains';
+    FIELD_TYPE = 'field_type';
+    ATTR_TYPE = 'attribute_type';
 }
 
 @parser::header {
@@ -238,6 +240,8 @@ boolean_expression returns [ParseNode ret]
                 {$ret = factory.documentFieldBeginsWith(l, r); } )
         |   CONTAINS ( LPAREN l=value_expression COMMA r=value_expression 
RPAREN
                 {$ret = factory.documentFieldContains(l, r); } )
+        |   (FIELD_TYPE | ATTR_TYPE) ( LPAREN l=value_expression COMMA 
r=value_expression RPAREN
+                {$ret = factory.documentFieldType(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 aec21fc8f5..89aec18eef 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
@@ -55,6 +55,30 @@ public class CommonComparisonExpressionUtils {
     return false;
   }
 
+  public static boolean isBsonNumberSet(final BsonValue bsonValue) {
+    if (!isBsonSet(bsonValue)) {
+      return false;
+    }
+    BsonArray bsonArray = ((BsonDocument) bsonValue).get("$set").asArray();
+    return !bsonArray.isEmpty() && bsonArray.get(0).isNumber();
+  }
+
+  public static boolean isBsonBinarySet(final BsonValue bsonValue) {
+    if (!isBsonSet(bsonValue)) {
+      return false;
+    }
+    BsonArray bsonArray = ((BsonDocument) bsonValue).get("$set").asArray();
+    return !bsonArray.isEmpty() && bsonArray.get(0).isBinary();
+  }
+
+  public static boolean isBsonStringSet(final BsonValue bsonValue) {
+    if (!isBsonSet(bsonValue)) {
+      return false;
+    }
+    BsonArray bsonArray = ((BsonDocument) bsonValue).get("$set").asArray();
+    return !bsonArray.isEmpty() && bsonArray.get(0).isString();
+  }
+
   /**
    * 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 eca475d12f..b31da51d4c 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.DocumentFieldTypeParseNode;
 import org.apache.phoenix.parse.EqualParseNode;
 import org.apache.phoenix.parse.GreaterThanOrEqualParseNode;
 import org.apache.phoenix.parse.GreaterThanParseNode;
@@ -178,6 +179,15 @@ public final class SQLComparisonExpressionUtils {
       fieldName = replaceExpressionFieldNames(fieldName, keyAliasDocument, 
sortedKeyNames);
       final String containsValue = (String) value.getValue();
       return contains(fieldName, containsValue, rawBsonDocument, 
comparisonValuesDocument);
+    } else if (parseNode instanceof DocumentFieldTypeParseNode) {
+      final DocumentFieldTypeParseNode documentFieldTypeParseNode =
+        (DocumentFieldTypeParseNode) parseNode;
+      final LiteralParseNode fieldKey = (LiteralParseNode) 
documentFieldTypeParseNode.getFieldKey();
+      final LiteralParseNode value = (LiteralParseNode) 
documentFieldTypeParseNode.getValue();
+      String fieldName = (String) fieldKey.getValue();
+      fieldName = replaceExpressionFieldNames(fieldName, keyAliasDocument, 
sortedKeyNames);
+      final String type = (String) value.getValue();
+      return isFieldOfType(fieldName, type, rawBsonDocument, 
comparisonValuesDocument);
     } else if (parseNode instanceof EqualParseNode) {
       final EqualParseNode equalParseNode = (EqualParseNode) parseNode;
       final LiteralParseNode lhs = (LiteralParseNode) equalParseNode.getLHS();
@@ -601,6 +611,58 @@ public final class SQLComparisonExpressionUtils {
     return false;
   }
 
+  /**
+   * Returns true if the type of the value of the field key is same as the 
provided type. The
+   * provided type should be one of the following: 
{N,BS,L,B,NULL,M,S,SS,NS,BOOL}.
+   * @param fieldKey                 The field key for which value is checked 
for field_type.
+   * @param type                     The type to check against the type of 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 of the given type, false 
otherwise
+   */
+  private static boolean isFieldOfType(final String fieldKey, final String 
type,
+    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 typeBsonVal = comparisonValuesDocument.get(type);
+    if (typeBsonVal == null) {
+      throw new BsonConditionInvalidArgumentException(
+        "Value for type was not found in the comparison values document.");
+    }
+    switch (((BsonString) typeBsonVal).getValue()) {
+      case "S":
+        return fieldValue.isString();
+      case "N":
+        return fieldValue.isNumber();
+      case "B":
+        return fieldValue.isBinary();
+      case "BOOL":
+        return fieldValue.isBoolean();
+      case "NULL":
+        return fieldValue.isNull();
+      case "L":
+        return fieldValue.isArray();
+      case "M":
+        return fieldValue.isDocument();
+      case "SS":
+        return CommonComparisonExpressionUtils.isBsonStringSet(fieldValue);
+      case "NS":
+        return CommonComparisonExpressionUtils.isBsonNumberSet(fieldValue);
+      case "BS":
+        return CommonComparisonExpressionUtils.isBsonBinarySet(fieldValue);
+      default:
+        throw new BsonConditionInvalidArgumentException(
+          "Unsupported type in field_type() for BsonConditionExpression: " + 
type
+            + ", valid types: valid types: {N,BS,L,B,NULL,M,S,SS,NS,BOOL}");
+    }
+  }
+
   private static boolean areEqual(BsonValue value1, BsonValue value2) {
     if (value1 == null && value2 == null) {
       return true;
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DocumentFieldTypeParseNode.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DocumentFieldTypeParseNode.java
new file mode 100644
index 0000000000..92a5115be4
--- /dev/null
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/DocumentFieldTypeParseNode.java
@@ -0,0 +1,60 @@
+/*
+ * 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.Arrays;
+import java.util.List;
+import org.apache.phoenix.compile.ColumnResolver;
+
+/**
+ * Parse Node to help determine whether the document field is of a given type.
+ */
+public class DocumentFieldTypeParseNode extends CompoundParseNode {
+
+  DocumentFieldTypeParseNode(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("field_type(");
+    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 757f81ea07..083ad9279f 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
@@ -310,6 +310,10 @@ public class ParseNodeFactory {
     return new DocumentFieldContainsParseNode(fieldKey, value);
   }
 
+  public DocumentFieldTypeParseNode documentFieldType(ParseNode fieldKey, 
ParseNode value) {
+    return new DocumentFieldTypeParseNode(fieldKey, value);
+  }
+
   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 faabeaca05..c0441afa1f 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
@@ -248,6 +248,28 @@ public class Bson1IT extends ParallelStatsDisabledIT {
       assertEquals(bsonDocument2, document2);
 
       assertFalse(rs.next());
+
+      conditionExpression = "field_type(#attr_5, :L)";
+      conditionDoc = new BsonDocument();
+      conditionDoc.put("$EXPR", new BsonString(conditionExpression));
+      conditionDoc.put("$VAL", compareValuesDocument);
+      keyDoc = new BsonDocument();
+      keyDoc.put("#attr_5", new BsonString("attr_5"));
+      conditionDoc.put("$KEYS", keyDoc);
+      query = "SELECT * FROM " + tableName + " WHERE 
BSON_CONDITION_EXPRESSION(COL, '"
+        + conditionDoc.toJson() + "')";
+      rs = conn.createStatement().executeQuery(query);
+      assertTrue(rs.next());
+      assertTrue(rs.next());
+
+      conditionExpression = "attribute_type(attr_5, :NS)";
+      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);
+      assertFalse(rs.next());
     }
   }
 
@@ -259,7 +281,8 @@ public class Bson1IT extends ParallelStatsDisabledIT {
         + "  \":Ids1\" : \"12\",\n" + "  \":NMap1_NList1\" : \"NListVal01\",\n"
         + "  \":InPublication\" : false,\n" + "  \":NestedList1_xyz0123\" : 
\"xyz0123\",\n"
         + "  \":Attr5Value\" : \"str001\",\n" + "  \":NestedList1String\" : 
\"1234abcd\",\n"
-        + "  \":NonExistentValue\" : \"does_not_exist\"\n" + "}";
+        + "  \":NonExistentValue\" : \"does_not_exist\"\n" + "  \":L\" : 
\"L\"\n"
+        + "  \":NS\" : \"NS\"\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 35b8c4dfa5..8ae3db4bb1 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
@@ -1892,4 +1892,283 @@ public class ComparisonExpressionUtilsTest {
     return RawBsonDocument.parse(json);
   }
 
+  @Test
+  public void testFieldTypeFunction() {
+    RawBsonDocument rawBsonDocument = getFieldTypeTestDocument();
+    RawBsonDocument compareValues = getFieldTypeCompareValDocument();
+
+    // Test String type (S)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringField, :TypeS)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringField, :TypeN)", rawBsonDocument, compareValues));
+
+    // Test Number type (N) - testing both integer and double
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NumberFieldInt, :TypeN)", rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NumberFieldDouble, :TypeN)", rawBsonDocument, 
compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NumberFieldInt, :TypeS)", rawBsonDocument, compareValues));
+
+    // Test Binary type (B)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(BinaryField, :TypeB)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(BinaryField, :TypeS)", rawBsonDocument, compareValues));
+
+    // Test Boolean type (BOOL)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(BooleanFieldTrue, :TypeBOOL)", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(BooleanFieldFalse, :TypeBOOL)", rawBsonDocument, 
compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(BooleanFieldTrue, :TypeS)", rawBsonDocument, compareValues));
+
+    // Test Null type (NULL)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NullField, :TypeNULL)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NullField, :TypeS)", rawBsonDocument, compareValues));
+
+    // Test List/Array type (L)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(ArrayField, :TypeL)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(ArrayField, :TypeS)", rawBsonDocument, compareValues));
+
+    // Test Map/Document type (M)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(DocumentField, :TypeM)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(DocumentField, :TypeS)", rawBsonDocument, compareValues));
+
+    // Test String Set type (SS)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringSetField, :TypeSS)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringSetField, :TypeNS)", rawBsonDocument, compareValues));
+
+    // Test Number Set type (NS)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NumberSetField, :TypeNS)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NumberSetField, :TypeSS)", rawBsonDocument, compareValues));
+
+    // Test Binary Set type (BS)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(BinarySetField, :TypeBS)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(BinarySetField, :TypeSS)", rawBsonDocument, compareValues));
+
+    // Test nested field types
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NestedDoc.NestedString, :TypeS)", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NestedDoc.NestedNumber, :TypeN)", rawBsonDocument, 
compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NestedDoc.NestedString, :TypeN)", rawBsonDocument, 
compareValues));
+
+    // Test non-existent field
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NonExistentField, :TypeS)", rawBsonDocument, compareValues));
+
+    // Test complex expressions with field_type
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringField, :TypeS) AND field_type(NumberFieldInt, 
:TypeN)", rawBsonDocument,
+      compareValues));
+
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringField, :TypeN) OR field_type(NumberFieldInt, :TypeN)", 
rawBsonDocument,
+      compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringField, :TypeN) AND field_type(NumberFieldInt, 
:TypeS)", rawBsonDocument,
+      compareValues));
+
+    // Test NOT with field_type
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "NOT field_type(StringField, :TypeN)", rawBsonDocument, compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "NOT field_type(StringField, :TypeS)", rawBsonDocument, compareValues));
+
+    // Test field_type combined with other functions
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringField, :TypeS) AND field_exists(StringField)", 
rawBsonDocument,
+      compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NonExistentField, :TypeS) AND 
field_exists(NonExistentField)", rawBsonDocument,
+      compareValues));
+
+    // Test attribute_type (alternative syntax for field_type)
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(StringField, :TypeS)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(StringField, :TypeN)", rawBsonDocument, compareValues));
+
+    // Test attribute_type with different data types
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(NumberFieldInt, :TypeN)", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(BooleanFieldTrue, :TypeBOOL)", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(ArrayField, :TypeL)", rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(DocumentField, :TypeM)", rawBsonDocument, 
compareValues));
+
+    // Test attribute_type with sets
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(StringSetField, :TypeSS)", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(NumberSetField, :TypeNS)", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(BinarySetField, :TypeBS)", rawBsonDocument, 
compareValues));
+
+    // Test attribute_type with nested fields
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(NestedDoc.NestedString, :TypeS)", rawBsonDocument, 
compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(NestedDoc.NestedString, :TypeN)", rawBsonDocument, 
compareValues));
+
+    // Test attribute_type in complex expressions
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(StringField, :TypeS) AND attribute_type(NumberFieldInt, 
:TypeN)",
+      rawBsonDocument, compareValues));
+
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(StringField, :TypeN) OR attribute_type(NumberFieldInt, 
:TypeN)",
+      rawBsonDocument, compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(StringField, :TypeN) AND attribute_type(NumberFieldInt, 
:TypeS)",
+      rawBsonDocument, compareValues));
+
+    // Test NOT with attribute_type
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "NOT attribute_type(StringField, :TypeN)", rawBsonDocument, 
compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "NOT attribute_type(StringField, :TypeS)", rawBsonDocument, 
compareValues));
+
+    // Test mixing field_type and attribute_type
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringField, :TypeS) AND attribute_type(NumberFieldInt, 
:TypeN)", rawBsonDocument,
+      compareValues));
+
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringField, :TypeN) OR attribute_type(NumberFieldInt, 
:TypeN)", rawBsonDocument,
+      compareValues));
+
+    // Test attribute_type with non-existent field
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(NonExistentField, :TypeS)", rawBsonDocument, 
compareValues));
+
+    // Test attribute_type combined with other functions
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(StringField, :TypeS) AND attribute_exists(StringField)", 
rawBsonDocument,
+      compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(NonExistentField, :TypeS) AND 
attribute_not_exists(NonExistentField)",
+      rawBsonDocument, compareValues));
+
+    // Test empty sets - should return false for type checking
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(EmptyStringSet, :TypeSS)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(EmptyNumberSet, :TypeNS)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(EmptyBinarySet, :TypeBS)", rawBsonDocument, compareValues));
+
+    // Test empty sets with attribute_type syntax
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(EmptyStringSet, :TypeSS)", rawBsonDocument, 
compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(EmptyNumberSet, :TypeNS)", rawBsonDocument, 
compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(EmptyBinarySet, :TypeBS)", rawBsonDocument, 
compareValues));
+
+    // Test empty sets in complex expressions
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(StringSetField, :TypeSS) AND NOT field_type(EmptyStringSet, 
:TypeSS)",
+      rawBsonDocument, compareValues));
+
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(EmptyStringSet, :TypeSS) AND field_type(StringSetField, 
:TypeSS)",
+      rawBsonDocument, compareValues));
+
+    // Test array element access at specific indices
+    // ArrayField contains: [ "item1", "item2", 123 ]
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(ArrayField[0], :TypeS)", rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(ArrayField[1], :TypeS)", rawBsonDocument, compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(ArrayField[2], :TypeN)", rawBsonDocument, compareValues));
+
+    // Test negative cases for array element types
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(ArrayField[0], :TypeN)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(ArrayField[2], :TypeS)", rawBsonDocument, compareValues));
+
+    // Test array element access with attribute_type
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(ArrayField[0], :TypeS)", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(ArrayField[2], :TypeN)", rawBsonDocument, 
compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "attribute_type(ArrayField[1], :TypeN)", rawBsonDocument, 
compareValues));
+
+    // Test nested array element access
+    // NestedDoc.NestedArray contains: [ 1, 2, 3 ]
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NestedDoc.NestedArray[0], :TypeN)", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NestedDoc.NestedArray[1], :TypeN)", rawBsonDocument, 
compareValues));
+    assertTrue(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NestedDoc.NestedArray[2], :TypeN)", rawBsonDocument, 
compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NestedDoc.NestedArray[0], :TypeS)", rawBsonDocument, 
compareValues));
+
+    // Test non-existent array indices
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(ArrayField[5], :TypeS)", rawBsonDocument, compareValues));
+    assertFalse(SQLComparisonExpressionUtils.evaluateConditionExpression(
+      "field_type(NestedDoc.NestedArray[10], :TypeN)", rawBsonDocument, 
compareValues));
+  }
+
+  private static RawBsonDocument getFieldTypeTestDocument() {
+    String json = "{\n" + "  \"StringField\" : \"Hello World\",\n" + "  
\"NumberFieldInt\" : 42,\n"
+      + "  \"NumberFieldDouble\" : 3.14159,\n" + "  \"BinaryField\" : {\n" + " 
   \"$binary\" : {\n"
+      + "      \"base64\" : \"SGVsbG8gV29ybGQ=\",\n" + "      \"subType\" : 
\"00\"\n" + "    }\n"
+      + "  },\n" + "  \"BooleanFieldTrue\" : true,\n" + "  
\"BooleanFieldFalse\" : false,\n"
+      + "  \"NullField\" : null,\n" + "  \"ArrayField\" : [ \"item1\", 
\"item2\", 123 ],\n"
+      + "  \"DocumentField\" : {\n" + "    \"nestedKey\" : \"nestedValue\",\n"
+      + "    \"nestedNum\" : 999\n" + "  },\n"
+      + "  \"StringSetField\" : { \"$set\" : [ \"apple\", \"banana\", 
\"cherry\" ] },\n"
+      + "  \"NumberSetField\" : { \"$set\" : [ 10, 20, 30 ] },\n"
+      + "  \"BinarySetField\" : { \"$set\" : [ {\n" + "    \"$binary\" : {\n"
+      + "      \"base64\" : \"VGVzdA==\",\n" + "      \"subType\" : \"00\"\n" 
+ "    }\n"
+      + "  }, {\n" + "    \"$binary\" : {\n" + "      \"base64\" : 
\"RGF0YQ==\",\n"
+      + "      \"subType\" : \"00\"\n" + "    }\n" + "  } ] },\n"
+      + "  \"EmptyStringSet\" : { \"$set\" : [ ] },\n"
+      + "  \"EmptyNumberSet\" : { \"$set\" : [ ] },\n"
+      + "  \"EmptyBinarySet\" : { \"$set\" : [ ] },\n" + "  \"NestedDoc\" : 
{\n"
+      + "    \"NestedString\" : \"nested value\",\n" + "    \"NestedNumber\" : 
777,\n"
+      + "    \"NestedArray\" : [ 1, 2, 3 ]\n" + "  }\n" + "}";
+    return RawBsonDocument.parse(json);
+  }
+
+  private static RawBsonDocument getFieldTypeCompareValDocument() {
+    String json =
+      "{\n" + "  \":TypeS\" : \"S\",\n" + "  \":TypeN\" : \"N\",\n" + "  
\":TypeB\" : \"B\",\n"
+        + "  \":TypeBOOL\" : \"BOOL\",\n" + "  \":TypeNULL\" : \"NULL\",\n"
+        + "  \":TypeL\" : \"L\",\n" + "  \":TypeM\" : \"M\",\n" + "  
\":TypeSS\" : \"SS\",\n"
+        + "  \":TypeNS\" : \"NS\",\n" + "  \":TypeBS\" : \"BS\"\n" + "}";
+    return RawBsonDocument.parse(json);
+  }
+
 }

Reply via email to