kadirozde commented on code in PR #1906:
URL: https://github.com/apache/phoenix/pull/1906#discussion_r1706199338


##########
phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/UpdateExpressionUtils.java:
##########
@@ -0,0 +1,790 @@
+/*
+ * 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.expression.util.bson;
+
+import java.math.BigDecimal;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.bson.BsonArray;
+import org.bson.BsonDecimal128;
+import org.bson.BsonDocument;
+import org.bson.BsonDouble;
+import org.bson.BsonInt32;
+import org.bson.BsonInt64;
+import org.bson.BsonNumber;
+import org.bson.BsonString;
+import org.bson.BsonValue;
+import org.bson.types.Decimal128;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * BSON Update Expression Utility to perform the Document updates. All update 
expressions
+ * provided by this utility supports operations on nested document fields. The 
field key can
+ * represent any top level or nested fields within the document. The caller 
should use "."
+ * notation for accessing nested document elements and "[n]" notation for 
accessing nested array
+ * elements. Top level fields do not require any additional character.
+ */
+public class UpdateExpressionUtils {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(UpdateExpressionUtils.class);
+
+  /**
+   * Updates the given document based on the update expression.
+   *
+   * @param updateExpression Update Expression as a document.
+   * @param bsonDocument Document contents to be updated.
+   */
+  public static void updateExpression(final BsonDocument updateExpression,
+      final BsonDocument bsonDocument) {
+
+    LOGGER.info("Update Expression: {} , current bsonDocument: {}", 
updateExpression, bsonDocument);
+
+    if (updateExpression.containsKey("$SET")) {
+      executeSetExpression((BsonDocument) updateExpression.get("$SET"), 
bsonDocument);
+    }
+
+    if (updateExpression.containsKey("$UNSET")) {
+      executeRemoveExpression((BsonDocument) updateExpression.get("$UNSET"), 
bsonDocument);
+    }
+
+    if (updateExpression.containsKey("$ADD")) {
+      executeAddExpression((BsonDocument) updateExpression.get("$ADD"), 
bsonDocument);
+    }
+
+    if (updateExpression.containsKey("$DELETE")) {
+      executeDeleteExpression((BsonDocument) updateExpression.get("$DELETE"), 
bsonDocument);
+    }
+  }
+
+  /**
+   * Update the given document by performing DELETE operation. This operation 
is applicable
+   * only on Set data structure. The document is updated by removing the given 
set of elements from
+   * the given set of elements.
+   *
+   * @param deleteExpr Delete Expression Document with key-value pairs. Key 
represents field in the
+   * given document, on which operation is to be performed. Value represents 
set of elements to be
+   * removed from the existing set.
+   * @param bsonDocument Document contents to be updated.
+   */
+  private static void executeDeleteExpression(final BsonDocument deleteExpr,
+      final BsonDocument bsonDocument) {
+    for (Map.Entry<String, BsonValue> deleteEntry : deleteExpr.entrySet()) {
+      String fieldKey = deleteEntry.getKey();
+      BsonValue newVal = deleteEntry.getValue();
+      BsonValue topLevelValue = bsonDocument.get(fieldKey);
+      if (!isBsonSet(newVal)) {
+        throw new RuntimeException("Type of new value to be removed should be 
sets only");
+      }
+      if (topLevelValue != null) {
+        BsonValue value = modifyFieldValueByDelete(topLevelValue, newVal);
+        if (value == null) {
+          bsonDocument.remove(fieldKey);
+        } else {
+          bsonDocument.put(fieldKey, value);
+        }
+      } else if (!fieldKey.contains(".") && !fieldKey.contains("[")) {
+        LOGGER.info("Nothing to be removed as field with key {} does not 
exist", fieldKey);
+      } else {
+        updateNestedFieldEntryByDelete(fieldKey, bsonDocument, newVal);
+      }
+    }
+  }
+
+  private static void updateNestedFieldEntryByDelete(final String fieldKey,
+                                                     final BsonDocument 
bsonDocument,
+                                                     final BsonValue newVal) {
+    if (fieldKey.contains(".") || fieldKey.contains("[")) {
+      StringBuilder sb = new StringBuilder();
+      for (int i = 0; i < fieldKey.length(); i++) {
+        if (fieldKey.charAt(i) == '.') {
+          BsonValue value = bsonDocument.get(sb.toString());
+          if (value == null) {
+            LOGGER.error("Incorrect access. Should have found nested 
bsonDocument for {}", sb);
+            throw new RuntimeException("Map does not contain key: " + sb);
+          }
+          updateNestedFieldByDelete(value, i, fieldKey, newVal);
+          return;
+        } else if (fieldKey.charAt(i) == '[') {
+          BsonValue value = bsonDocument.get(sb.toString());
+          if (value == null) {
+            LOGGER.error("Incorrect access. Should have found nested list for 
{}", sb);
+            throw new RuntimeException("Map does not contain key: " + sb);
+          }
+          updateNestedFieldByDelete(value, i, fieldKey, newVal);
+          return;
+        } else {
+          sb.append(fieldKey.charAt(i));
+        }
+      }
+    }
+  }
+
+  private static void updateNestedFieldByDelete(final BsonValue value, final 
int idx,

Review Comment:
   It will be good to explain the meaning of the parameters and the basic logic 
in the method.



##########
phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/DocumentComparisonExpressionUtils.java:
##########
@@ -0,0 +1,370 @@
+/*
+ * 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.expression.util.bson;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bson.BsonArray;
+import org.bson.BsonBoolean;
+import org.bson.BsonDocument;
+import org.bson.BsonValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions;
+
+/**
+ * Document style condition expression evaluation support.
+ */
+public class DocumentComparisonExpressionUtils {
+
+  private static final Logger LOGGER =
+      LoggerFactory.getLogger(DocumentComparisonExpressionUtils.class);
+
+  private static final String EXISTS_OP = "$exists";
+  private static final String EQUALS_OP = "$eq";
+  private static final String NOT_EQUALS_OP = "$ne";
+  private static final String LESS_THAN_OP = "$lt";
+  private static final String LESS_THAN_OR_EQUALS_OP = "$lte";
+  private static final String GREATER_THAN_OP = "$gt";
+  private static final String GREATER_THAN_OR_EQUALS_OP = "$gte";
+
+  public static boolean isConditionExpressionMatching(final BsonDocument 
document,
+      final BsonDocument conditionExpression) {
+    if (document == null || conditionExpression == null) {
+      LOGGER.warn(
+          "Document and/or Condition Expression document are empty. Document: 
{}, "
+              + "conditionExpression: {}", document, conditionExpression);
+      return false;
+    }
+    return evaluateExpression(document, conditionExpression);
+  }
+
+  private static boolean evaluateExpression(final BsonDocument document,
+      final BsonDocument conditionExpression) {
+    final String firstFieldKey = conditionExpression.getFirstKey();
+    Preconditions.checkArgument(conditionExpression.size() == 1,
+        "Expected num of document entries is 1");
+    if (!firstFieldKey.startsWith("$")) {
+      BsonValue bsonValue = conditionExpression.get(firstFieldKey);
+      Preconditions.checkArgument(bsonValue instanceof BsonDocument,
+          "Expected type for Bson value is Document for field based condition 
operation");
+      BsonDocument bsonDocument = (BsonDocument) bsonValue;
+      if (bsonDocument.containsKey(EXISTS_OP)) {
+        return isExists(document, conditionExpression);
+      } else if (bsonDocument.containsKey(EQUALS_OP)) {
+        return equals(document, conditionExpression);
+      } else if (bsonDocument.containsKey(NOT_EQUALS_OP)) {
+        return notEquals(document, conditionExpression);
+      } else if (bsonDocument.containsKey(LESS_THAN_OP)) {
+        return lessThan(document, conditionExpression);
+      } else if (bsonDocument.containsKey(LESS_THAN_OR_EQUALS_OP)) {
+        return lessThanOrEquals(document, conditionExpression);
+      } else if (bsonDocument.containsKey(GREATER_THAN_OP)) {
+        return greaterThan(document, conditionExpression);
+      } else if (bsonDocument.containsKey(GREATER_THAN_OR_EQUALS_OP)) {
+        return greaterThanOrEquals(document, conditionExpression);
+      } else {
+        throw new IllegalArgumentException("Operator " + firstFieldKey + " is 
not supported");
+      }
+    } else {
+      switch (firstFieldKey) {
+        case "$or": {
+          BsonValue bsonValue = conditionExpression.get(firstFieldKey);
+          Preconditions.checkArgument(bsonValue instanceof BsonArray,
+              "Expected type for Bson value is Array for $or operator");
+          BsonArray bsonArray = (BsonArray) bsonValue;
+          List<BsonValue> bsonValues = bsonArray.getValues();
+          for (BsonValue value : bsonValues) {
+            if (evaluateExpression(document, (BsonDocument) value)) {
+              return true;
+            }
+          }
+          return false;
+        }
+        case "$and": {
+          BsonValue bsonValue = conditionExpression.get(firstFieldKey);
+          Preconditions.checkArgument(bsonValue instanceof BsonArray,
+              "Expected type for Bson value is Array for $and operator");
+          BsonArray bsonArray = (BsonArray) bsonValue;
+          List<BsonValue> bsonValues = bsonArray.getValues();
+          for (BsonValue value : bsonValues) {
+            if (!evaluateExpression(document, (BsonDocument) value)) {
+              return false;
+            }
+          }
+          return true;
+        }
+        default: {
+          throw new IllegalArgumentException(firstFieldKey + " is not a known 
operator");
+        }
+      }
+    }
+  }
+
+  private static boolean isExists(final BsonDocument document,
+      final BsonDocument conditionExpression) {
+    Set<Map.Entry<String, BsonValue>> entrySet = 
conditionExpression.entrySet();
+    Preconditions.checkArgument(entrySet.size() == 1, "Expected entry for the 
exists operation"
+        + " is 1");
+    for (Map.Entry<String, BsonValue> bsonValueEntry : entrySet) {
+      String fieldKey = bsonValueEntry.getKey();
+      BsonValue bsonValue = bsonValueEntry.getValue();
+      Preconditions.checkArgument(bsonValue instanceof BsonDocument,
+          "Expected type for Bson value is Document for exists operation");
+      BsonDocument bsonDocument = (BsonDocument) bsonValue;
+      BsonValue existsValue = bsonDocument.get(EXISTS_OP);
+      Preconditions.checkArgument(existsValue instanceof BsonBoolean,
+          "Expected type for $exists value is boolean");
+      BsonBoolean existsValBoolean = (BsonBoolean) existsValue;
+      if (existsValBoolean.getValue()) {
+        return exists(fieldKey, document);
+      } else {
+        return !exists(fieldKey, document);
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if the field provided in the condition expression has value 
greater than
+   * or equals to the value provided in the condition expression.
+   * Condition Expression format:
+   * {
+   *   <field>: {
+   *     "$gte": <value>
+   *   }
+   * }
+   *
+   * @param document The document used for comparison.
+   * @param conditionExpression Condition Expression Document.
+   * @return True if the field provided in the condition expression has value 
greater than
+   * or equals to the value provided in the condition expression.
+   */
+  private static boolean greaterThanOrEquals(final BsonDocument document,

Review Comment:
   You can still have a separate method for each of them if you need but they 
should call a common method to eliminate duplicate code.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to