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);
+ }
+
}