This is an automated email from the ASF dual-hosted git repository.

nehapawar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new b0ad7ca  Support inbuilt transform functions (#5312)
b0ad7ca is described below

commit b0ad7ca706f59cb9c0065040841f41030258dc56
Author: Neha Pawar <neha.pawa...@gmail.com>
AuthorDate: Tue Apr 28 09:24:04 2020 -0700

    Support inbuilt transform functions (#5312)
    
    Wiring the FunctionExpressionEvaluator with the ExpressionEvaluatorFactory 
to be able to execute inbuilt transform functions.
---
 ...essionEvaluator.java => DateTimeFunctions.java} | 24 ++++-----
 ...valuator.java => DefaultFunctionEvaluator.java} | 16 ++++--
 ...essionEvaluator.java => FunctionEvaluator.java} |  6 +--
 ...rFactory.java => FunctionEvaluatorFactory.java} | 51 ++++++++++---------
 .../pinot/core/data/function/FunctionRegistry.java | 16 ++++++
 ...Evaluator.java => GroovyFunctionEvaluator.java} |  6 +--
 ...aluator.java => TimeSpecFunctionEvaluator.java} |  6 +--
 .../recordtransformer/ExpressionTransformer.java   | 18 +++----
 .../org/apache/pinot/core/util/SchemaUtils.java    | 16 +++---
 .../function/DateTimeFunctionEvaluatorTest.java    | 59 ++++++++++++++++++++++
 ...Test.java => DefaultFunctionEvaluatorTest.java} | 12 +++--
 ...rTest.java => GroovyFunctionEvaluatorTest.java} |  4 +-
 .../apache/pinot/core/util/SchemaUtilsTest.java    | 25 ++++++---
 .../data/readers/AbstractRecordExtractorTest.java  |  2 +-
 .../groovy_transform_functions_schema.json         |  2 +-
 15 files changed, 182 insertions(+), 81 deletions(-)

diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/ExpressionEvaluator.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/DateTimeFunctions.java
similarity index 59%
copy from 
pinot-core/src/main/java/org/apache/pinot/core/data/function/ExpressionEvaluator.java
copy to 
pinot-core/src/main/java/org/apache/pinot/core/data/function/DateTimeFunctions.java
index dc51f0b..533a497 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/ExpressionEvaluator.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/DateTimeFunctions.java
@@ -18,26 +18,26 @@
  */
 package org.apache.pinot.core.data.function;
 
-import java.util.List;
-import org.apache.pinot.spi.data.readers.GenericRow;
+import java.util.concurrent.TimeUnit;
 
 
 /**
- * Interface for evaluators of transform function expressions of schema field 
specs
- * They transformFunction follows the convention:
- *  "transformFunction": "FunctionType({function}, argument1, 
argument2,...argumentN)"
- *  For example,
- *  "transformFunction" : "Groovy({firstName + ' ' + lastName}, firstName, 
lastName)"
+ * Inbuilt date time related transform functions
+ * TODO: Exhaustively add all time conversion functions
  */
-public interface ExpressionEvaluator {
+public class DateTimeFunctions {
 
   /**
-   * Get the arguments of the function
+   * Convert epoch millis to epoch hours
    */
-  List<String> getArguments();
+  static Long toEpochHours(Long millis) {
+    return TimeUnit.MILLISECONDS.toHours(millis);
+  }
 
   /**
-   * Evaluate the function on the generic row and return the result
+   * Convert epoch millis to epoch minutes, bucketed by given bucket 
granularity
    */
-  Object evaluate(GenericRow genericRow);
+  static Long toEpochMinutes(Long millis, String bucket) {
+    return TimeUnit.MILLISECONDS.toMinutes(millis) / Integer.parseInt(bucket);
+  }
 }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionExpressionEvaluator.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/DefaultFunctionEvaluator.java
similarity index 91%
rename from 
pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionExpressionEvaluator.java
rename to 
pinot-core/src/main/java/org/apache/pinot/core/data/function/DefaultFunctionEvaluator.java
index 662639f..568fb64 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionExpressionEvaluator.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/DefaultFunctionEvaluator.java
@@ -19,6 +19,7 @@
 package org.apache.pinot.core.data.function;
 
 import com.google.common.base.Preconditions;
+import java.util.ArrayList;
 import java.util.List;
 import org.apache.pinot.common.request.transform.TransformExpressionTree;
 import org.apache.pinot.spi.data.readers.GenericRow;
@@ -42,12 +43,14 @@ import org.apache.pinot.spi.data.readers.GenericRow;
  *   </li>
  * </ul>
  */
-public class FunctionExpressionEvaluator {
+public class DefaultFunctionEvaluator implements FunctionEvaluator {
   // Root of the execution tree
   private final ExecutableNode _rootNode;
+  private final List<String> _arguments;
 
-  public FunctionExpressionEvaluator(String expression)
+  public DefaultFunctionEvaluator(String expression)
       throws Exception {
+    _arguments = new ArrayList<>();
     _rootNode = 
planExecution(TransformExpressionTree.compileToExpressionTree(expression));
   }
 
@@ -65,7 +68,9 @@ public class FunctionExpressionEvaluator {
           childNode = planExecution(childExpression);
           break;
         case IDENTIFIER:
-          childNode = new ColumnExecutionNode(childExpression.getValue());
+          String columnName = childExpression.getValue();
+          childNode = new ColumnExecutionNode(columnName);
+          _arguments.add(columnName);
           break;
         case LITERAL:
           childNode = new ConstantExecutionNode(childExpression.getValue());
@@ -81,6 +86,11 @@ public class FunctionExpressionEvaluator {
     return new FunctionExecutionNode(functionInfo, childNodes);
   }
 
+  @Override
+  public List<String> getArguments() {
+    return _arguments;
+  }
+
   public Object evaluate(GenericRow row) {
     return _rootNode.execute(row);
   }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/ExpressionEvaluator.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionEvaluator.java
similarity index 81%
rename from 
pinot-core/src/main/java/org/apache/pinot/core/data/function/ExpressionEvaluator.java
rename to 
pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionEvaluator.java
index dc51f0b..e503f19 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/ExpressionEvaluator.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionEvaluator.java
@@ -24,12 +24,8 @@ import org.apache.pinot.spi.data.readers.GenericRow;
 
 /**
  * Interface for evaluators of transform function expressions of schema field 
specs
- * They transformFunction follows the convention:
- *  "transformFunction": "FunctionType({function}, argument1, 
argument2,...argumentN)"
- *  For example,
- *  "transformFunction" : "Groovy({firstName + ' ' + lastName}, firstName, 
lastName)"
  */
-public interface ExpressionEvaluator {
+public interface FunctionEvaluator {
 
   /**
    * Get the arguments of the function
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/ExpressionEvaluatorFactory.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionEvaluatorFactory.java
similarity index 68%
rename from 
pinot-core/src/main/java/org/apache/pinot/core/data/function/ExpressionEvaluatorFactory.java
rename to 
pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionEvaluatorFactory.java
index 0959581..ddd4c5c 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/ExpressionEvaluatorFactory.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionEvaluatorFactory.java
@@ -19,36 +19,32 @@
 package org.apache.pinot.core.data.function;
 
 import javax.annotation.Nullable;
+import org.apache.pinot.core.util.SchemaUtils;
 import org.apache.pinot.spi.data.FieldSpec;
 import org.apache.pinot.spi.data.TimeFieldSpec;
 import org.apache.pinot.spi.data.TimeGranularitySpec;
-import org.apache.pinot.core.util.SchemaUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 
 /**
- * Factory class to create an {@link ExpressionEvaluator} for the field spec 
based on the {@link FieldSpec#getTransformFunction()}
+ * Factory class to create an {@link FunctionEvaluator} for the field spec 
based on the {@link FieldSpec#getTransformFunction()}
  */
-public class ExpressionEvaluatorFactory {
+public class FunctionEvaluatorFactory {
 
-  private static final Logger LOGGER = 
LoggerFactory.getLogger(ExpressionEvaluatorFactory.class);
-
-  private ExpressionEvaluatorFactory() {
+  private FunctionEvaluatorFactory() {
 
   }
 
   /**
-   * Creates the {@link ExpressionEvaluator} for the given field spec
+   * Creates the {@link FunctionEvaluator} for the given field spec
    *
-   * 1. If transform expression is defined, use it to create {@link 
ExpressionEvaluator}
-   * 2. For TIME column, if conversion is needed, {@link 
DefaultTimeSpecEvaluator} for backward compatible handling of time spec. This 
is needed until we migrate to {@link 
org.apache.pinot.spi.data.DateTimeFieldSpec}
-   * 3. For columns ending with __KEYS or __VALUES (used for interpreting Map 
column in Avro), create default functions for handing the Map
+   * 1. If transform expression is defined, use it to create the appropriate 
{@link FunctionEvaluator}
+   * 2. For TIME column, if conversion is needed, {@link 
TimeSpecFunctionEvaluator} for backward compatible handling of time spec. This 
is needed until we migrate to {@link 
org.apache.pinot.spi.data.DateTimeFieldSpec}
+   * 3. For columns ending with __KEYS or __VALUES (used for interpreting Map 
column in Avro), create default groovy functions for handing the Map
    * 4. Return null, if none of the above
    */
   @Nullable
-  public static ExpressionEvaluator getExpressionEvaluator(FieldSpec 
fieldSpec) {
-    ExpressionEvaluator expressionEvaluator = null;
+  public static FunctionEvaluator getExpressionEvaluator(FieldSpec fieldSpec) {
+    FunctionEvaluator functionEvaluator = null;
 
     String columnName = fieldSpec.getName();
     String transformExpression = fieldSpec.getTransformFunction();
@@ -56,7 +52,7 @@ public class ExpressionEvaluatorFactory {
 
       // if transform function expression present, use it to generate function 
evaluator
       try {
-        expressionEvaluator = getExpressionEvaluator(transformExpression);
+        functionEvaluator = getExpressionEvaluator(transformExpression);
       } catch (Exception e) {
         throw new IllegalStateException(
             "Caught exception while constructing expression evaluator for 
transform expression:" + transformExpression
@@ -70,7 +66,7 @@ public class ExpressionEvaluatorFactory {
       TimeGranularitySpec outgoingGranularitySpec = 
timeFieldSpec.getOutgoingGranularitySpec();
       if (!incomingGranularitySpec.equals(outgoingGranularitySpec)) {
         if 
(!incomingGranularitySpec.getName().equals(outgoingGranularitySpec.getName())) {
-          expressionEvaluator = new 
DefaultTimeSpecEvaluator(incomingGranularitySpec, outgoingGranularitySpec);
+          functionEvaluator = new 
TimeSpecFunctionEvaluator(incomingGranularitySpec, outgoingGranularitySpec);
         } else {
           throw new IllegalStateException(
               "Invalid timeSpec - Incoming and outgoing field specs are 
different, but name " + incomingGranularitySpec
@@ -84,23 +80,30 @@ public class ExpressionEvaluatorFactory {
       String sourceMapName =
           columnName.substring(0, columnName.length() - 
SchemaUtils.MAP_KEY_COLUMN_SUFFIX.length());
       String defaultMapKeysTransformExpression = 
getDefaultMapKeysTransformExpression(sourceMapName);
-      expressionEvaluator = 
getExpressionEvaluator(defaultMapKeysTransformExpression);
+      functionEvaluator = 
getExpressionEvaluator(defaultMapKeysTransformExpression);
     } else if (columnName.endsWith(SchemaUtils.MAP_VALUE_COLUMN_SUFFIX)) {
       // for backward compatible handling of Map type in avro (currently only 
in Avro)
       String sourceMapName =
           columnName.substring(0, columnName.length() - 
SchemaUtils.MAP_VALUE_COLUMN_SUFFIX.length());
       String defaultMapValuesTransformExpression = 
getDefaultMapValuesTransformExpression(sourceMapName);
-      expressionEvaluator = 
getExpressionEvaluator(defaultMapValuesTransformExpression);
+      functionEvaluator = 
getExpressionEvaluator(defaultMapValuesTransformExpression);
     }
-    return expressionEvaluator;
+    return functionEvaluator;
   }
 
-  private static ExpressionEvaluator getExpressionEvaluator(String 
transformExpression) {
-    ExpressionEvaluator expressionEvaluator = null;
-    if 
(transformExpression.startsWith(GroovyExpressionEvaluator.getGroovyExpressionPrefix()))
 {
-      expressionEvaluator = new GroovyExpressionEvaluator(transformExpression);
+  private static FunctionEvaluator getExpressionEvaluator(String 
transformExpression) {
+    FunctionEvaluator functionEvaluator;
+    try {
+      if 
(transformExpression.startsWith(GroovyFunctionEvaluator.getGroovyExpressionPrefix()))
 {
+        functionEvaluator = new GroovyFunctionEvaluator(transformExpression);
+      } else {
+        functionEvaluator = new DefaultFunctionEvaluator(transformExpression);
+      }
+    } catch (Exception e) {
+      throw new IllegalStateException(
+          "Could not construct FunctionEvaluator for transformFunction: " + 
transformExpression, e);
     }
-    return expressionEvaluator;
+    return functionEvaluator;
   }
 
   private static String getDefaultMapKeysTransformExpression(String 
mapColumnName) {
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionRegistry.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionRegistry.java
index 3b8d53f..8a8dfac 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionRegistry.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/FunctionRegistry.java
@@ -25,12 +25,28 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
+/**
+ * Registry for inbuilt Pinot functions
+ */
 public class FunctionRegistry {
 
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(FunctionRegistry.class);
+
   static Map<String, List<FunctionInfo>> _functionInfoMap = new HashMap<>();
 
+  static {
+    try {
+      
registerStaticFunction(DateTimeFunctions.class.getDeclaredMethod("toEpochHours",
 Long.class));
+      
registerStaticFunction(DateTimeFunctions.class.getDeclaredMethod("toEpochMinutes",
 Long.class, String.class));
+    } catch (NoSuchMethodException e) {
+      LOGGER.error("Caught exception when registering function", e);
+    }
+  }
+
   public static FunctionInfo resolve(String functionName, Class<?>[] 
argumentTypes) {
     List<FunctionInfo> list = _functionInfoMap.get(functionName.toLowerCase());
     FunctionInfo bestMatch = null;
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/GroovyExpressionEvaluator.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/GroovyFunctionEvaluator.java
similarity index 92%
rename from 
pinot-core/src/main/java/org/apache/pinot/core/data/function/GroovyExpressionEvaluator.java
rename to 
pinot-core/src/main/java/org/apache/pinot/core/data/function/GroovyFunctionEvaluator.java
index b0362c9..64e7d9a 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/GroovyExpressionEvaluator.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/GroovyFunctionEvaluator.java
@@ -31,7 +31,7 @@ import org.apache.pinot.spi.data.readers.GenericRow;
 
 
 /**
- * An {@link ExpressionEvaluator} for evaluating schema transform expressions 
written in Groovy.
+ * An {@link FunctionEvaluator} for evaluating transform function expressions 
of a Schema field spec written in Groovy.
  * GroovyShell is used to execute expressions.
  *
  * The transform expression must follow the convention Groovy({expression}, 
arguments1, argument2...)
@@ -44,7 +44,7 @@ import org.apache.pinot.spi.data.readers.GenericRow;
  *     }
  *  ]
  */
-public class GroovyExpressionEvaluator implements ExpressionEvaluator {
+public class GroovyFunctionEvaluator implements FunctionEvaluator {
 
   private static final String GROOVY_EXPRESSION_PREFIX = "Groovy";
   private static final String GROOVY_FUNCTION_REGEX = 
"Groovy\\(\\{(?<script>.+)}(,(?<arguments>.+))?\\)";
@@ -58,7 +58,7 @@ public class GroovyExpressionEvaluator implements 
ExpressionEvaluator {
   private final Binding _binding;
   private final Script _script;
 
-  public GroovyExpressionEvaluator(String transformExpression) {
+  public GroovyFunctionEvaluator(String transformExpression) {
     Matcher matcher = GROOVY_FUNCTION_PATTERN.matcher(transformExpression);
     Preconditions.checkState(matcher.matches(), "Invalid transform expression: 
%s", transformExpression);
     String arguments = matcher.group(ARGUMENTS_GROUP_NAME);
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/DefaultTimeSpecEvaluator.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/TimeSpecFunctionEvaluator.java
similarity index 91%
rename from 
pinot-core/src/main/java/org/apache/pinot/core/data/function/DefaultTimeSpecEvaluator.java
rename to 
pinot-core/src/main/java/org/apache/pinot/core/data/function/TimeSpecFunctionEvaluator.java
index a0466a0..f2cec35 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/data/function/DefaultTimeSpecEvaluator.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/data/function/TimeSpecFunctionEvaluator.java
@@ -29,16 +29,16 @@ import org.apache.pinot.spi.utils.TimeUtils;
 
 
 /**
- * The {@code DefaultTimeSpecEvaluator} class will convert the time value 
based on the {@link TimeFieldSpec}.
+ * An implementation of {@link FunctionEvaluator} for converting the time 
value based on the {@link TimeFieldSpec}.
  */
-public class DefaultTimeSpecEvaluator implements ExpressionEvaluator {
+public class TimeSpecFunctionEvaluator implements FunctionEvaluator {
   private final String _incomingTimeColumn;
   private final String _outgoingTimeColumn;
   private final TimeConverter _incomingTimeConverter;
   private final TimeConverter _outgoingTimeConverter;
   private boolean _isValidated = false;
 
-  public DefaultTimeSpecEvaluator(TimeGranularitySpec incomingGranularitySpec,
+  public TimeSpecFunctionEvaluator(TimeGranularitySpec incomingGranularitySpec,
       TimeGranularitySpec outgoingGranularitySpec) {
     
Preconditions.checkState(!incomingGranularitySpec.equals(outgoingGranularitySpec));
     _incomingTimeColumn = incomingGranularitySpec.getName();
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/data/recordtransformer/ExpressionTransformer.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/data/recordtransformer/ExpressionTransformer.java
index facdcf7..00babe5 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/data/recordtransformer/ExpressionTransformer.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/data/recordtransformer/ExpressionTransformer.java
@@ -23,8 +23,8 @@ import java.util.Map;
 import org.apache.pinot.spi.data.FieldSpec;
 import org.apache.pinot.spi.data.Schema;
 import org.apache.pinot.spi.data.readers.GenericRow;
-import org.apache.pinot.core.data.function.ExpressionEvaluator;
-import org.apache.pinot.core.data.function.ExpressionEvaluatorFactory;
+import org.apache.pinot.core.data.function.FunctionEvaluator;
+import org.apache.pinot.core.data.function.FunctionEvaluatorFactory;
 
 
 /**
@@ -34,14 +34,14 @@ import 
org.apache.pinot.core.data.function.ExpressionEvaluatorFactory;
  */
 public class ExpressionTransformer implements RecordTransformer {
 
-  private final Map<String, ExpressionEvaluator> _expressionEvaluators = new 
HashMap<>();
+  private final Map<String, FunctionEvaluator> _expressionEvaluators = new 
HashMap<>();
 
   public ExpressionTransformer(Schema schema) {
     for (FieldSpec fieldSpec : schema.getAllFieldSpecs()) {
       if (!fieldSpec.isVirtualColumn()) {
-        ExpressionEvaluator expressionEvaluator = 
ExpressionEvaluatorFactory.getExpressionEvaluator(fieldSpec);
-        if (expressionEvaluator != null) {
-          _expressionEvaluators.put(fieldSpec.getName(), expressionEvaluator);
+        FunctionEvaluator functionEvaluator = 
FunctionEvaluatorFactory.getExpressionEvaluator(fieldSpec);
+        if (functionEvaluator != null) {
+          _expressionEvaluators.put(fieldSpec.getName(), functionEvaluator);
         }
       }
     }
@@ -49,13 +49,13 @@ public class ExpressionTransformer implements 
RecordTransformer {
 
   @Override
   public GenericRow transform(GenericRow record) {
-    for (Map.Entry<String, ExpressionEvaluator> entry : 
_expressionEvaluators.entrySet()) {
+    for (Map.Entry<String, FunctionEvaluator> entry : 
_expressionEvaluators.entrySet()) {
       String column = entry.getKey();
-      ExpressionEvaluator transformExpressionEvaluator = entry.getValue();
+      FunctionEvaluator transformFunctionEvaluator = entry.getValue();
       // Skip transformation if column value already exist.
       // NOTE: column value might already exist for OFFLINE data
       if (record.getValue(column) == null) {
-        Object result = transformExpressionEvaluator.evaluate(record);
+        Object result = transformFunctionEvaluator.evaluate(record);
         record.putValue(column, result);
       }
     }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/util/SchemaUtils.java 
b/pinot-core/src/main/java/org/apache/pinot/core/util/SchemaUtils.java
index d152647..550c754 100644
--- a/pinot-core/src/main/java/org/apache/pinot/core/util/SchemaUtils.java
+++ b/pinot-core/src/main/java/org/apache/pinot/core/util/SchemaUtils.java
@@ -21,8 +21,8 @@ package org.apache.pinot.core.util;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import org.apache.pinot.core.data.function.ExpressionEvaluator;
-import org.apache.pinot.core.data.function.ExpressionEvaluatorFactory;
+import org.apache.pinot.core.data.function.FunctionEvaluator;
+import org.apache.pinot.core.data.function.FunctionEvaluatorFactory;
 import org.apache.pinot.spi.data.FieldSpec;
 import org.apache.pinot.spi.data.Schema;
 import org.apache.pinot.spi.data.TimeFieldSpec;
@@ -52,9 +52,9 @@ public class SchemaUtils {
     Set<String> sourceFieldNames = new HashSet<>();
     for (FieldSpec fieldSpec : schema.getAllFieldSpecs()) {
       if (!fieldSpec.isVirtualColumn()) {
-        ExpressionEvaluator expressionEvaluator = 
ExpressionEvaluatorFactory.getExpressionEvaluator(fieldSpec);
-        if (expressionEvaluator != null) {
-          sourceFieldNames.addAll(expressionEvaluator.getArguments());
+        FunctionEvaluator functionEvaluator = 
FunctionEvaluatorFactory.getExpressionEvaluator(fieldSpec);
+        if (functionEvaluator != null) {
+          sourceFieldNames.addAll(functionEvaluator.getArguments());
         }
         sourceFieldNames.add(fieldSpec.getName());
       }
@@ -83,9 +83,9 @@ public class SchemaUtils {
           String column = fieldSpec.getName();
           String transformFunction = fieldSpec.getTransformFunction();
           if (transformFunction != null) {
-            ExpressionEvaluator expressionEvaluator = 
ExpressionEvaluatorFactory.getExpressionEvaluator(fieldSpec);
-            if (expressionEvaluator != null) {
-              List<String> arguments = expressionEvaluator.getArguments();
+            FunctionEvaluator functionEvaluator = 
FunctionEvaluatorFactory.getExpressionEvaluator(fieldSpec);
+            if (functionEvaluator != null) {
+              List<String> arguments = functionEvaluator.getArguments();
               // output column used as input
               if (arguments.contains(column)) {
                 logger.error("The arguments of transform function: {}, should 
not contain the destination column: {}",
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionEvaluatorTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionEvaluatorTest.java
new file mode 100644
index 0000000..d6b1a04
--- /dev/null
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionEvaluatorTest.java
@@ -0,0 +1,59 @@
+/**
+ * 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.pinot.core.data.function;
+
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.pinot.spi.data.readers.GenericRow;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+
+/**
+ * Tests the Pinot inbuilt transform functions in {@link DateTimeFunctions} 
which perform date time conversion
+ */
+public class DateTimeFunctionEvaluatorTest {
+
+  @Test(dataProvider = "dateTimeFunctionsTestDataProvider")
+  public void testExpressionWithColumn(String transformFunction, List<String> 
arguments, GenericRow row, Object result)
+      throws Exception {
+    DefaultFunctionEvaluator evaluator = new 
DefaultFunctionEvaluator(transformFunction);
+    Assert.assertEquals(evaluator.getArguments(), arguments);
+    Assert.assertEquals(evaluator.evaluate(row), result);
+  }
+
+  @DataProvider(name = "dateTimeFunctionsTestDataProvider")
+  public Object[][] dateTimeFunctionsDataProvider() {
+    List<Object[]> inputs = new ArrayList<>();
+
+    // toEpochHours
+    GenericRow row1 = new GenericRow();
+    row1.putValue("timestamp", 1585724400000L);
+    inputs.add(new Object[]{"toEpochHours(timestamp)", 
Lists.newArrayList("timestamp"), row1, 440479L});
+
+    // toEpochMinutes w/ bucketing fixed
+    GenericRow row2 = new GenericRow();
+    row2.putValue("millis", 1585724400000L);
+    inputs.add(new Object[]{"toEpochMinutes(millis, 5)", 
Lists.newArrayList("millis"), row2, 5285748L});
+
+    return inputs.toArray(new Object[0][]);
+  }
+}
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/data/function/FunctionExpressionEvaluatorTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DefaultFunctionEvaluatorTest.java
similarity index 86%
rename from 
pinot-core/src/test/java/org/apache/pinot/core/data/function/FunctionExpressionEvaluatorTest.java
rename to 
pinot-core/src/test/java/org/apache/pinot/core/data/function/DefaultFunctionEvaluatorTest.java
index 907e822..7c6d4cf 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/data/function/FunctionExpressionEvaluatorTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DefaultFunctionEvaluatorTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.pinot.core.data.function;
 
+import com.google.common.collect.Lists;
 import java.lang.reflect.Method;
 import org.apache.pinot.spi.data.readers.GenericRow;
 import org.joda.time.DateTime;
@@ -28,7 +29,7 @@ import org.testng.Assert;
 import org.testng.annotations.Test;
 
 
-public class FunctionExpressionEvaluatorTest {
+public class DefaultFunctionEvaluatorTest {
 
   @Test
   public void testExpressionWithColumn()
@@ -39,7 +40,8 @@ public class FunctionExpressionEvaluatorTest {
     System.out.println(functionInfo);
     String expression = "reverseString(testColumn)";
 
-    FunctionExpressionEvaluator evaluator = new 
FunctionExpressionEvaluator(expression);
+    DefaultFunctionEvaluator evaluator = new 
DefaultFunctionEvaluator(expression);
+    Assert.assertEquals(evaluator.getArguments(), 
Lists.newArrayList("testColumn"));
     GenericRow row = new GenericRow();
     for (int i = 0; i < 5; i++) {
       String value = "testValue" + i;
@@ -57,7 +59,8 @@ public class FunctionExpressionEvaluatorTest {
     String input = "1980-01-01";
     String format = "yyyy-MM-dd";
     String expression = String.format("daysSinceEpoch('%s', '%s')", input, 
format);
-    FunctionExpressionEvaluator evaluator = new 
FunctionExpressionEvaluator(expression);
+    DefaultFunctionEvaluator evaluator = new 
DefaultFunctionEvaluator(expression);
+    Assert.assertTrue(evaluator.getArguments().isEmpty());
     GenericRow row = new GenericRow();
     Object result = evaluator.evaluate(row);
     Assert.assertEquals(result, MyFunc.daysSinceEpoch(input, format));
@@ -73,7 +76,8 @@ public class FunctionExpressionEvaluatorTest {
     String reversedInput = MyFunc.reverseString(input);
     String format = "yyyy-MM-dd";
     String expression = String.format("daysSinceEpoch(reverseString('%s'), 
'%s')", reversedInput, format);
-    FunctionExpressionEvaluator evaluator = new 
FunctionExpressionEvaluator(expression);
+    DefaultFunctionEvaluator evaluator = new 
DefaultFunctionEvaluator(expression);
+    Assert.assertTrue(evaluator.getArguments().isEmpty());
     GenericRow row = new GenericRow();
     Object result = evaluator.evaluate(row);
     Assert.assertEquals(result, MyFunc.daysSinceEpoch(input, format));
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/data/function/GroovyExpressionEvaluatorTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/GroovyFunctionEvaluatorTest.java
similarity index 96%
rename from 
pinot-core/src/test/java/org/apache/pinot/core/data/function/GroovyExpressionEvaluatorTest.java
rename to 
pinot-core/src/test/java/org/apache/pinot/core/data/function/GroovyFunctionEvaluatorTest.java
index 0ff8fad..677c05a 100644
--- 
a/pinot-core/src/test/java/org/apache/pinot/core/data/function/GroovyExpressionEvaluatorTest.java
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/data/function/GroovyFunctionEvaluatorTest.java
@@ -32,12 +32,12 @@ import org.testng.collections.Lists;
 /**
  * Tests Groovy functions for transforming schema columns
  */
-public class GroovyExpressionEvaluatorTest {
+public class GroovyFunctionEvaluatorTest {
 
   @Test(dataProvider = "groovyFunctionEvaluationDataProvider")
   public void testGroovyFunctionEvaluation(String transformFunction, 
List<String> arguments, GenericRow genericRow, Object expectedResult) {
 
-    GroovyExpressionEvaluator groovyExpressionEvaluator = new 
GroovyExpressionEvaluator(transformFunction);
+    GroovyFunctionEvaluator groovyExpressionEvaluator = new 
GroovyFunctionEvaluator(transformFunction);
     Assert.assertEquals(groovyExpressionEvaluator.getArguments(), arguments);
 
     Object result = groovyExpressionEvaluator.evaluate(genericRow);
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/util/SchemaUtilsTest.java 
b/pinot-core/src/test/java/org/apache/pinot/core/util/SchemaUtilsTest.java
index 43f1812..cb7e6b9 100644
--- a/pinot-core/src/test/java/org/apache/pinot/core/util/SchemaUtilsTest.java
+++ b/pinot-core/src/test/java/org/apache/pinot/core/util/SchemaUtilsTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.pinot.core.util;
 
+import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -47,7 +48,6 @@ public class SchemaUtilsTest {
     DimensionFieldSpec dimensionFieldSpec = new DimensionFieldSpec("d1", 
FieldSpec.DataType.STRING, true);
     dimensionFieldSpec.setTransformFunction("Groovy({function}, argument1, 
argument2)");
     schema.addField(dimensionFieldSpec);
-
     List<String> extract = new 
ArrayList<>(SchemaUtils.extractSourceFields(schema));
     Assert.assertEquals(extract.size(), 3);
     Assert.assertTrue(extract.containsAll(Arrays.asList("d1", "argument1", 
"argument2")));
@@ -57,7 +57,6 @@ public class SchemaUtilsTest {
     dimensionFieldSpec = new DimensionFieldSpec("d1", 
FieldSpec.DataType.STRING, true);
     dimensionFieldSpec.setTransformFunction("Groovy({function})");
     schema.addField(dimensionFieldSpec);
-
     extract = new ArrayList<>(SchemaUtils.extractSourceFields(schema));
     Assert.assertEquals(extract.size(), 1);
     Assert.assertTrue(extract.contains("d1"));
@@ -66,7 +65,6 @@ public class SchemaUtilsTest {
     schema = new Schema();
     dimensionFieldSpec = new DimensionFieldSpec("map__KEYS", 
FieldSpec.DataType.INT, false);
     schema.addField(dimensionFieldSpec);
-
     extract = new ArrayList<>(SchemaUtils.extractSourceFields(schema));
     Assert.assertEquals(extract.size(), 2);
     Assert.assertTrue(extract.containsAll(Arrays.asList("map", "map__KEYS")));
@@ -75,7 +73,6 @@ public class SchemaUtilsTest {
     schema = new Schema();
     dimensionFieldSpec = new DimensionFieldSpec("map__VALUES", 
FieldSpec.DataType.LONG, false);
     schema.addField(dimensionFieldSpec);
-
     extract = new ArrayList<>(SchemaUtils.extractSourceFields(schema));
     Assert.assertEquals(extract.size(), 2);
     Assert.assertTrue(extract.containsAll(Arrays.asList("map", 
"map__VALUES")));
@@ -85,7 +82,6 @@ public class SchemaUtilsTest {
     schema = new Schema();
     TimeFieldSpec timeFieldSpec = new TimeFieldSpec("time", 
FieldSpec.DataType.LONG, TimeUnit.MILLISECONDS);
     schema.addField(timeFieldSpec);
-
     extract = new ArrayList<>(SchemaUtils.extractSourceFields(schema));
     Assert.assertEquals(extract.size(), 1);
     Assert.assertTrue(extract.contains("time"));
@@ -96,10 +92,27 @@ public class SchemaUtilsTest {
         new TimeFieldSpec("in", FieldSpec.DataType.LONG, 
TimeUnit.MILLISECONDS, "out", FieldSpec.DataType.LONG,
             TimeUnit.MILLISECONDS);
     schema.addField(timeFieldSpec);
-
     extract = new ArrayList<>(SchemaUtils.extractSourceFields(schema));
     Assert.assertEquals(extract.size(), 2);
     Assert.assertTrue(extract.containsAll(Arrays.asList("in", "out")));
+
+    // inbuilt functions
+    schema = new Schema();
+    dimensionFieldSpec = new DimensionFieldSpec("hoursSinceEpoch", 
FieldSpec.DataType.LONG, true);
+    dimensionFieldSpec.setTransformFunction("toEpochHours(timestamp)");
+    schema.addField(dimensionFieldSpec);
+    extract = new ArrayList<>(SchemaUtils.extractSourceFields(schema));
+    Assert.assertEquals(extract.size(), 2);
+    Assert.assertTrue(extract.containsAll(Arrays.asList("timestamp", 
"hoursSinceEpoch")));
+
+    // inbuilt functions with literal
+    schema = new Schema();
+    dimensionFieldSpec = new DimensionFieldSpec("tenMinutesSinceEpoch", 
FieldSpec.DataType.LONG, true);
+    dimensionFieldSpec.setTransformFunction("toEpochMinutes(timestamp, 10)");
+    schema.addField(dimensionFieldSpec);
+    extract = new ArrayList<>(SchemaUtils.extractSourceFields(schema));
+    Assert.assertEquals(extract.size(), 2);
+    
Assert.assertTrue(extract.containsAll(Lists.newArrayList("tenMinutesSinceEpoch",
 "timestamp")));
   }
 
   @Test
diff --git 
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/readers/AbstractRecordExtractorTest.java
 
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/readers/AbstractRecordExtractorTest.java
index 636262a..9a9f3af 100644
--- 
a/pinot-spi/src/test/java/org/apache/pinot/spi/data/readers/AbstractRecordExtractorTest.java
+++ 
b/pinot-spi/src/test/java/org/apache/pinot/spi/data/readers/AbstractRecordExtractorTest.java
@@ -39,7 +39,7 @@ import org.testng.annotations.Test;
 
 
 /**
- * Tests the RecordReader for schema with groovy transform functions
+ * Tests the RecordReader for schema with transform functions
  */
 public abstract class AbstractRecordExtractorTest {
 
diff --git 
a/pinot-spi/src/test/resources/groovy_transform_functions_schema.json 
b/pinot-spi/src/test/resources/groovy_transform_functions_schema.json
index f6bf8b5..8f1ab8f 100644
--- a/pinot-spi/src/test/resources/groovy_transform_functions_schema.json
+++ b/pinot-spi/src/test/resources/groovy_transform_functions_schema.json
@@ -39,6 +39,6 @@
       "timeFormat" : "EPOCH",
       "timeType": "HOURS"
     },
-    "transformFunction": "Groovy({timestamp/(1000*60*60)}, timestamp)"
+    "transformFunction": "toEpochHours(timestamp)"
   }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org
For additional commands, e-mail: commits-h...@pinot.apache.org

Reply via email to