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


##########
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:
   Yes i can make this, but since we need separate methods in SQL style 
comparison as this PR uses MVEL expression language and having separate 
function calls are clean for expression language, i kept it this way:
   
   - SQL style expression -> contains individual comparison methods -> 
CommonComparisonExpressionUtils -> single method for all comparisons
   - Document style expression -> contains individual comparison methods -> 
CommonComparisonExpressionUtils -> single method for all comparisons
   
   When i introduce ANTLR style expression evaluation as follow up Jira, i can 
make this change as well:
   
   - SQL style expression -> single method for all comparisons -> 
CommonComparisonExpressionUtils -> single method for all comparisons
   - Document style expression -> single method for all comparisons -> 
CommonComparisonExpressionUtils -> single method for all comparisons
   
   @kadirozde does this sound good?



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