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


##########
phoenix-core-client/src/main/java/org/apache/phoenix/expression/util/bson/SQLComparisonExpressionUtils.java:
##########
@@ -0,0 +1,344 @@
+/*
+ * 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 org.bson.BsonDocument;
+import org.bson.BsonValue;
+import org.bson.RawBsonDocument;
+import org.mvel2.MVEL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * SQL style condition expression evaluation support.
+ */
+public class SQLComparisonExpressionUtils {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(SQLComparisonExpressionUtils.class);
+
+  /**
+   * All supported operators. Used to parse the input string and identify how 
many of the
+   * operators are in use and accordingly performs string conversions using 
individual
+   * pattern-matcher-replace.
+   */
+  private static final String ALL_SUPPORTED_OPS =
+          
"\\b(field_not_exists|field_exists|BETWEEN|IN|AND|OR|NOT)\\b|<=|>=|!=|==|=|<>|<|>";
+  private static final Pattern ALL_SUPPORTED_OPS_PATTERN = 
Pattern.compile(ALL_SUPPORTED_OPS);
+
+  private static final String FIELD_NOT_EXISTS = 
"field_not_exists\\(([^)]+)\\)";
+  private static final String FIELD_EXISTS = "field_exists\\(([^)]+)\\)";
+  private static final String EQUALS1 = 
"\\b([\\w.\\[\\]]+)\\s*=\\s*([#:$]*\\w+)";
+  private static final String EQUALS2 = 
"\\b([\\w.\\[\\]]+)\\s*==\\s*([#:$]*\\w+)";
+  private static final String NOT_EQUALS1 = 
"\\b([\\w.\\[\\]]+)\\s*!=\\s*([#:$]*\\w+)";
+  private static final String NOT_EQUALS2 = 
"\\b([\\w.\\[\\]]+)\\s*<>\\s*([#:$]*\\w+)";
+  private static final String LESS_THAN = 
"\\b([\\w.\\[\\]]+)\\s*<\\s*([#:$]*\\w+)";
+  private static final String LESS_THAN_OR_EQUALS = 
"\\b([\\w.\\[\\]]+)\\s*<=\\s*([#:$]*\\w+)";
+  private static final String GREATER_THAN = 
"\\b([\\w.\\[\\]]+)\\s*>\\s*([#:$]*\\w+)";
+  private static final String GREATER_THAN_OR_EQUALS = 
"\\b([\\w.\\[\\]]+)\\s*>=\\s*([#:$]*\\w+)";
+  private static final String BETWEEN =
+          
"\\b([\\w.\\[\\]]+)\\s+BETWEEN\\s+([#:$]*\\w+)\\s+AND\\s+([#:$]*\\w+)";
+  private static final String IN = "\\b([\\w.\\[\\]]+)\\s+IN\\s+\\(([^)]+)\\)";
+  private static final String AND = "\\bAND\\b";
+  private static final String OR = "\\bOR\\b";
+  private static final String NOT = "\\bNOT\\b\\s*";
+
+  private static final Pattern FIELD_NOT_EXISTS_PATTERN = 
Pattern.compile(FIELD_NOT_EXISTS);
+  private static final Pattern FIELD_EXISTS_PATTERN = 
Pattern.compile(FIELD_EXISTS);
+  private static final Pattern EQUALS1_PATTERN = Pattern.compile(EQUALS1);
+  private static final Pattern EQUALS2_PATTERN = Pattern.compile(EQUALS2);
+  private static final Pattern NOT_EQUALS1_PATTERN = 
Pattern.compile(NOT_EQUALS1);
+  private static final Pattern NOT_EQUALS2_PATTERN = 
Pattern.compile(NOT_EQUALS2);
+  private static final Pattern LESS_THAN_PATTERN = Pattern.compile(LESS_THAN);
+  private static final Pattern LESS_THAN_OR_EQUALS_PATTERN = 
Pattern.compile(LESS_THAN_OR_EQUALS);
+  private static final Pattern GREATER_THAN_PATTERN = 
Pattern.compile(GREATER_THAN);
+  private static final Pattern GREATER_THAN_OR_EQUALS_PATTERN =
+          Pattern.compile(GREATER_THAN_OR_EQUALS);
+  private static final Pattern BETWEEN_PATTERN = Pattern.compile(BETWEEN);
+  private static final Pattern IN_PATTERN = Pattern.compile(IN);
+  private static final Pattern AND_PATTERN = Pattern.compile(AND);
+  private static final Pattern OR_PATTERN = Pattern.compile(OR);
+  private static final Pattern NOT_PATTERN = Pattern.compile(NOT);
+
+  private static final String FUNC_FIELD_NOT_EXISTS = "!exists('$1')";
+  private static final String FUNC_FIELD_EXISTS = "exists('$1')";
+  private static final String FUNC_EQUALS = "isEquals('$1', '$2')";
+  private static final String FUNC_NOT_EQUALS = "!isEquals('$1', '$2')";
+  private static final String FUNC_LESS_THAN = "lessThan('$1', '$2')";
+  private static final String FUNC_LESS_THAN_OR_EQUALS = 
"lessThanOrEquals('$1', '$2')";
+  private static final String FUNC_GREATER_THAN = "greaterThan('$1', '$2')";
+  private static final String FUNC_GREATER_THAN_OR_EQUALS = 
"greaterThanOrEquals('$1', '$2')";
+  private static final String FUNC_BETWEEN = "between('$1', '$2', '$3')";
+  private static final String FUNC_IN = "in('$1', '$2')";
+  private static final String OP_AND = "&&";
+  private static final String OP_OR = "||";
+  private static final String OP_NOT = "!";
+
+  private final RawBsonDocument rawBsonDocument;
+  private final BsonDocument comparisonValuesDocument;
+
+  public SQLComparisonExpressionUtils(RawBsonDocument rawBsonDocument,
+      BsonDocument comparisonValuesDocument) {
+    this.rawBsonDocument = rawBsonDocument;
+    this.comparisonValuesDocument = comparisonValuesDocument;
+  }
+
+  public boolean isConditionExpressionMatching(final String 
conditionExpression) {
+    String expression = convertExpression(conditionExpression);
+    LOGGER.trace("Evaluating Expression: {}", expression);
+    try {
+      Object result = MVEL.eval(expression, this);
+      return result instanceof Boolean && (Boolean) result;
+    } catch (Exception e) {
+      LOGGER.error("Error while evaluating expression: {}", expression, e);
+      throw new RuntimeException("Expression could not be evaluated: " + 
expression);
+    }
+  }
+
+  /**
+   * Converts the input string expression into Java executable statement.
+   *
+   * @param expression Input string expression.
+   * @return Executable string conversion statement.
+   */
+  public String convertExpression(String expression) {
+    Set<String> patternsMatched = new HashSet<>();
+    Matcher matcher = ALL_SUPPORTED_OPS_PATTERN.matcher(expression);
+    while (matcher.find()) {
+      patternsMatched.add(matcher.group());
+    }
+
+    if (patternsMatched.contains("field_not_exists")) {
+      expression = 
FIELD_NOT_EXISTS_PATTERN.matcher(expression).replaceAll(FUNC_FIELD_NOT_EXISTS);
+    }
+    if (patternsMatched.contains("field_exists")) {
+      expression = 
FIELD_EXISTS_PATTERN.matcher(expression).replaceAll(FUNC_FIELD_EXISTS);
+    }
+    if (patternsMatched.contains("=")) {
+      expression = EQUALS1_PATTERN.matcher(expression).replaceAll(FUNC_EQUALS);
+    }
+    if (patternsMatched.contains("==")) {
+      expression = EQUALS2_PATTERN.matcher(expression).replaceAll(FUNC_EQUALS);
+    }
+    if (patternsMatched.contains("!=")) {
+      expression = 
NOT_EQUALS1_PATTERN.matcher(expression).replaceAll(FUNC_NOT_EQUALS);
+    }
+    if (patternsMatched.contains("<>")) {
+      expression = 
NOT_EQUALS2_PATTERN.matcher(expression).replaceAll(FUNC_NOT_EQUALS);
+    }
+    if (patternsMatched.contains("<")) {
+      expression = 
LESS_THAN_PATTERN.matcher(expression).replaceAll(FUNC_LESS_THAN);
+    }
+    if (patternsMatched.contains("<=")) {
+      expression =
+              
LESS_THAN_OR_EQUALS_PATTERN.matcher(expression).replaceAll(FUNC_LESS_THAN_OR_EQUALS);
+    }
+    if (patternsMatched.contains(">")) {
+      expression = 
GREATER_THAN_PATTERN.matcher(expression).replaceAll(FUNC_GREATER_THAN);
+    }
+    if (patternsMatched.contains(">=")) {
+      expression = GREATER_THAN_OR_EQUALS_PATTERN.matcher(expression)
+              .replaceAll(FUNC_GREATER_THAN_OR_EQUALS);
+    }
+    if (patternsMatched.contains("BETWEEN")) {
+      expression = 
BETWEEN_PATTERN.matcher(expression).replaceAll(FUNC_BETWEEN);
+    }
+    if (patternsMatched.contains("IN")) {
+      expression = IN_PATTERN.matcher(expression).replaceAll(FUNC_IN);
+    }
+    if (patternsMatched.contains("AND")) {
+      expression = AND_PATTERN.matcher(expression).replaceAll(OP_AND);
+    }
+    if (patternsMatched.contains("OR")) {
+      expression = OR_PATTERN.matcher(expression).replaceAll(OP_OR);
+    }
+    if (patternsMatched.contains("NOT")) {
+      expression = NOT_PATTERN.matcher(expression).replaceAll(OP_NOT);
+    }
+    return expression;
+  }
+
+  /**
+   * Returns true if the given field exists in the document.
+   *
+   * @param documentField The document field.
+   * @return True if the given field exists in the document.
+   */
+  public boolean exists(final String documentField) {
+    BsonValue topLevelValue = rawBsonDocument.get(documentField);
+    if (topLevelValue != null) {
+      return true;
+    }
+    return CommonComparisonExpressionUtils.getFieldFromDocument(documentField, 
rawBsonDocument)
+        != null;
+  }
+
+  /**
+   * 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 expectedFieldValue The literal value to compare against the field 
value.
+   * @return True if the value of the field is less than expectedFieldValue.
+   */
+  public boolean lessThan(final String fieldKey, final String 
expectedFieldValue) {

Review Comment:
   This is taken care of in the latest revision.



##########
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:
   Done



-- 
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