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 <[email protected]>
AuthorDate: Sat Jul 19 12:00:37 2025 -0700
PHOENIX-7663 : BSON Condition Function field_type() (#2232)
Co-authored-by: Palash Chauhan
<[email protected]>
---
.../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);
+ }
+
}