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

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


The following commit(s) were added to refs/heads/master by this push:
     new 857d2775c7 Add support for EXTRACT syntax and converts it to 
appropriate Pinot expression (#9184)
857d2775c7 is described below

commit 857d2775c762a56b337beff932244e67d1aab01a
Author: tanmeshnm <[email protected]>
AuthorDate: Wed Sep 7 03:06:22 2022 -0700

    Add support for EXTRACT syntax and converts it to appropriate Pinot 
expression (#9184)
    
    * supporting EXTRACT syntax
    
    * fixed the licence header
    
    * completed part1 -- updated CalciteSqlParser to support Extract query
    
    * fixed based on the comments left
    
    * adding ExtractTransformFunction
    
    * tmp
    
    * adding changes in ExtractTransformFunction based on comments
    
    * added Field enum and updated based on comments
    
    * updating changes after reformatting
    
    * fixed some lint errors
    
    * fixing checkstyle errors
    
    Co-authored-by: Tanmesh <[email protected]>
    Co-authored-by: Tanmesh <[email protected]>
---
 .../common/function/TransformFunctionType.java     |   2 +
 .../apache/pinot/sql/parsers/CalciteSqlParser.java |   2 +
 .../pinot/sql/parsers/CalciteSqlCompilerTest.java  |  58 ++++++++---
 .../function/ExtractTransformFunction.java         | 111 +++++++++++++++++++++
 .../function/TransformFunctionFactory.java         |   2 +
 .../function/ExtractTransformFunctionTest.java     |  66 ++++++++++++
 6 files changed, 228 insertions(+), 13 deletions(-)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java
index 1bff9c05fa..d058ea3169 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java
@@ -99,6 +99,8 @@ public enum TransformFunctionType {
   LOOKUP("lookUp"),
   GROOVY("groovy"),
 
+  EXTRACT("extract"),
+
   // Regexp functions
   REGEXP_EXTRACT("regexpExtract"),
 
diff --git 
a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java 
b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java
index 0065c76c1f..18cbca3f41 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/sql/parsers/CalciteSqlParser.java
@@ -675,6 +675,8 @@ public class CalciteSqlParser {
           return RequestUtils.getIdentifierExpression(((SqlIdentifier) 
node).getSimple());
         }
         return RequestUtils.getIdentifierExpression(node.toString());
+      case INTERVAL_QUALIFIER:
+        return RequestUtils.getLiteralExpression(node.toString());
       case LITERAL:
         return RequestUtils.getLiteralExpression((SqlLiteral) node);
       case AS:
diff --git 
a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
 
b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
index 63501cb9b5..5f69fd7001 100644
--- 
a/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
+++ 
b/pinot-common/src/test/java/org/apache/pinot/sql/parsers/CalciteSqlCompilerTest.java
@@ -184,6 +184,38 @@ public class CalciteSqlCompilerTest {
             .getIdentifier().getName(), "Martha''s Vineyard");
   }
 
+  @Test
+  public void testExtract() {
+    {
+      // Case 1 -- Year and date format ('2017-06-15')
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("SELECT 
EXTRACT(YEAR FROM '2017-06-15')");
+      Function function = pinotQuery.getSelectList().get(0).getFunctionCall();
+      
Assert.assertEquals(function.getOperands().get(0).getLiteral().getStringValue(),
 "YEAR");
+      
Assert.assertEquals(function.getOperands().get(1).getLiteral().getStringValue(),
 "2017-06-15");
+    }
+    {
+      // Case 2 -- date format ('2017-06-15 09:34:21')
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("SELECT 
EXTRACT(YEAR FROM '2017-06-15 09:34:21')");
+      Function function = pinotQuery.getSelectList().get(0).getFunctionCall();
+      
Assert.assertEquals(function.getOperands().get(0).getLiteral().getStringValue(),
 "YEAR");
+      
Assert.assertEquals(function.getOperands().get(1).getLiteral().getStringValue(),
 "2017-06-15 09:34:21");
+    }
+    {
+      // Case 3 -- Month
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("SELECT 
EXTRACT(MONTH FROM '2017-06-15')");
+      Function function = pinotQuery.getSelectList().get(0).getFunctionCall();
+      
Assert.assertEquals(function.getOperands().get(0).getLiteral().getStringValue(),
 "MONTH");
+      
Assert.assertEquals(function.getOperands().get(1).getLiteral().getStringValue(),
 "2017-06-15");
+    }
+    {
+      // Case 4 -- Day
+      PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery("SELECT 
EXTRACT(DAY FROM '2017-06-15')");
+      Function function = pinotQuery.getSelectList().get(0).getFunctionCall();
+      
Assert.assertEquals(function.getOperands().get(0).getLiteral().getStringValue(),
 "DAY");
+      
Assert.assertEquals(function.getOperands().get(1).getLiteral().getStringValue(),
 "2017-06-15");
+    }
+  }
+
   @Test
   public void testFilterClauses() {
     {
@@ -274,7 +306,7 @@ public class CalciteSqlCompilerTest {
 
     {
       PinotQuery pinotQuery =
-          CalciteSqlParser.compileToPinotQuery("select * from vegetable where 
startsWith(g, " + "'str')");
+          CalciteSqlParser.compileToPinotQuery("select * from vegetable where 
startsWith(g, 'str')");
       Function func = pinotQuery.getFilterExpression().getFunctionCall();
       Assert.assertEquals(func.getOperator(), FilterKind.EQUALS.name());
       
Assert.assertEquals(func.getOperands().get(0).getFunctionCall().getOperator(), 
"startswith");
@@ -283,7 +315,7 @@ public class CalciteSqlCompilerTest {
 
     {
       PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(
-          "select * from vegetable where startsWith(g, " + "'str')=true and 
startsWith(f, 'str')");
+          "select * from vegetable where startsWith(g, 'str')=true and 
startsWith(f, 'str')");
       Function func = pinotQuery.getFilterExpression().getFunctionCall();
       Assert.assertEquals(func.getOperator(), FilterKind.AND.name());
       List<Expression> operands = func.getOperands();
@@ -302,7 +334,7 @@ public class CalciteSqlCompilerTest {
 
     {
       PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(
-          "select * from vegetable where (startsWith(g, " + "'str')=true and 
startsWith(f, 'str')) AND (e and d=true)");
+          "select * from vegetable where (startsWith(g, 'str')=true and 
startsWith(f, 'str')) AND (e and d=true)");
       Function func = pinotQuery.getFilterExpression().getFunctionCall();
       Assert.assertEquals(func.getOperator(), FilterKind.AND.name());
       List<Expression> operands = func.getOperands();
@@ -609,7 +641,7 @@ public class CalciteSqlCompilerTest {
       pinotQuery = CalciteSqlParser.compileToPinotQuery(
           "select concat(upper(playerName), lower(teamID), '-') playerTeam, "
               + "upper(league) leagueUpper, count(playerName) cnt from 
baseballStats group by playerTeam, lower"
-              + "(teamID), leagueUpper " + "having cnt > 1 order by cnt desc 
limit 10");
+              + "(teamID), leagueUpper having cnt > 1 order by cnt desc limit 
10");
     } catch (SqlCompilationException e) {
       throw e;
     }
@@ -724,8 +756,7 @@ public class CalciteSqlCompilerTest {
       Assert.assertTrue(e.getCause().getMessage().contains("OPTION"));
     }
     try {
-      CalciteSqlParser.compileToPinotQuery(
-          "select * from vegetables where name <> 'Brussels OPTION 
(delicious=yes)");
+      CalciteSqlParser.compileToPinotQuery("select * from vegetables where 
name <> 'Brussels OPTION (delicious=yes)");
     } catch (SqlCompilationException e) {
       Assert.assertTrue(e.getCause() instanceof ParseException);
     }
@@ -770,24 +801,25 @@ public class CalciteSqlCompilerTest {
 
     // test invalid options
     try {
-      CalciteSqlParser.compileToPinotQuery("select * from vegetables SET 
delicious='yes', foo='1234' "
-          + "where name <> 'Brussels sprouts'");
+      CalciteSqlParser.compileToPinotQuery(
+          "select * from vegetables SET delicious='yes', foo='1234' where name 
<> 'Brussels sprouts'");
       Assert.fail("SQL should not be compiled");
     } catch (SqlCompilationException sce) {
       // expected.
     }
 
     try {
-      CalciteSqlParser.compileToPinotQuery("select * from vegetables where 
name <> 'Brussels sprouts'; "
-          + "SET (delicious='yes', foo=1234)");
+      CalciteSqlParser.compileToPinotQuery(
+          "select * from vegetables where name <> 'Brussels sprouts'; SET 
(delicious='yes', foo=1234)");
       Assert.fail("SQL should not be compiled");
     } catch (SqlCompilationException sce) {
       // expected.
     }
 
     try {
-      CalciteSqlParser.compileToPinotQuery("select * from vegetables where 
name <> 'Brussels sprouts'; "
-          + "SET (delicious='yes', foo=1234); select * from meat");
+      CalciteSqlParser.compileToPinotQuery(
+          "select * from vegetables where name <> 'Brussels sprouts'; SET 
(delicious='yes', foo=1234); select * from "
+              + "meat");
       Assert.fail("SQL should not be compiled");
     } catch (SqlCompilationException sce) {
       // expected.
@@ -2618,7 +2650,7 @@ public class CalciteSqlCompilerTest {
 
     // Query having multiple SQL statements
     Assert.expectThrows(SqlCompilationException.class, () -> 
CalciteSqlParser.compileToPinotQuery(
-        "SELECT col1, count(*) FROM foo GROUP BY col1; SELECT col2," + 
"count(*) FROM foo GROUP BY col2"));
+        "SELECT col1, count(*) FROM foo GROUP BY col1; SELECT col2, count(*) 
FROM foo GROUP BY col2"));
 
     // Query having multiple SQL statements with trailing and leading 
whitespaces
     Assert.expectThrows(SqlCompilationException.class, () -> 
CalciteSqlParser.compileToPinotQuery(
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunction.java
new file mode 100644
index 0000000000..c5d2ce65f6
--- /dev/null
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunction.java
@@ -0,0 +1,111 @@
+/**
+ * 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.operator.transform.function;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.pinot.core.operator.blocks.ProjectionBlock;
+import org.apache.pinot.core.operator.transform.TransformResultMetadata;
+import org.apache.pinot.segment.spi.datasource.DataSource;
+import org.joda.time.Chronology;
+import org.joda.time.DateTimeField;
+import org.joda.time.chrono.ISOChronology;
+
+
+public class ExtractTransformFunction extends BaseTransformFunction {
+  public static final String FUNCTION_NAME = "extract";
+  private TransformFunction _mainTransformFunction;
+  protected Field _field;
+  protected Chronology _chronology = ISOChronology.getInstanceUTC();
+
+  private enum Field {
+    YEAR, MONTH, DAY, HOUR, MINUTE, SECOND
+  }
+
+  @Override
+  public String getName() {
+    return FUNCTION_NAME;
+  }
+
+  @Override
+  public void init(List<TransformFunction> arguments, Map<String, DataSource> 
dataSourceMap) {
+    if (arguments.size() != 2) {
+      throw new IllegalArgumentException("Exactly 2 arguments are required for 
EXTRACT transform function");
+    }
+
+    _field = Field.valueOf(((LiteralTransformFunction) 
arguments.get(0)).getLiteral());
+
+    _mainTransformFunction = arguments.get(1);
+  }
+
+  @Override
+  public TransformResultMetadata getResultMetadata() {
+    return INT_SV_NO_DICTIONARY_METADATA;
+  }
+
+  @Override
+  public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) {
+    int numDocs = projectionBlock.getNumDocs();
+
+    if (_intValuesSV == null || _intValuesSV.length < numDocs) {
+      _intValuesSV = new int[numDocs];
+    }
+
+    long[] timestamps = 
_mainTransformFunction.transformToLongValuesSV(projectionBlock);
+
+    convert(timestamps, numDocs, _intValuesSV);
+
+    return _intValuesSV;
+  }
+
+  private void convert(long[] timestamps, int numDocs, int[] output) {
+    for (int i = 0; i < numDocs; i++) {
+      DateTimeField accessor;
+
+      switch (_field) {
+        case YEAR:
+          accessor = _chronology.year();
+          output[i] = accessor.get(timestamps[i]);
+          break;
+        case MONTH:
+          accessor = _chronology.monthOfYear();
+          output[i] = accessor.get(timestamps[i]);
+          break;
+        case DAY:
+          accessor = _chronology.dayOfMonth();
+          output[i] = accessor.get(timestamps[i]);
+          break;
+        case HOUR:
+          accessor = _chronology.hourOfDay();
+          output[i] = accessor.get(timestamps[i]);
+          break;
+        case MINUTE:
+          accessor = _chronology.minuteOfHour();
+          output[i] = accessor.get(timestamps[i]);
+          break;
+        case SECOND:
+          accessor = _chronology.secondOfMinute();
+          output[i] = accessor.get(timestamps[i]);
+          break;
+        default:
+          throw new IllegalArgumentException("Unsupported FIELD type");
+      }
+    }
+  }
+}
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java
index aeb5f5d187..672dd4f4a4 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java
@@ -131,6 +131,8 @@ public class TransformFunctionFactory {
     typeToImplementation.put(TransformFunctionType.INIDSET, 
InIdSetTransformFunction.class);
     typeToImplementation.put(TransformFunctionType.LOOKUP, 
LookupTransformFunction.class);
 
+    typeToImplementation.put(TransformFunctionType.EXTRACT, 
ExtractTransformFunction.class);
+
     // Regexp functions
     typeToImplementation.put(TransformFunctionType.REGEXP_EXTRACT,
         RegexpExtractTransformFunction.class);
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunctionTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunctionTest.java
new file mode 100644
index 0000000000..61d580c9a1
--- /dev/null
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunctionTest.java
@@ -0,0 +1,66 @@
+/**
+ * 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.operator.transform.function;
+
+import java.util.function.LongToIntFunction;
+import org.apache.pinot.common.function.scalar.DateTimeFunctions;
+import org.apache.pinot.common.request.context.ExpressionContext;
+import org.apache.pinot.common.request.context.RequestContextUtils;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+
+public class ExtractTransformFunctionTest extends BaseTransformFunctionTest {
+
+  @DataProvider
+  public static Object[][] testCases() {
+    return new Object[][]{
+        //@formatter:off
+        {"year", (LongToIntFunction) DateTimeFunctions::year},
+        {"month", (LongToIntFunction) DateTimeFunctions::month},
+        {"day", (LongToIntFunction) DateTimeFunctions::dayOfMonth},
+        {"hour", (LongToIntFunction) DateTimeFunctions::hour},
+        {"minute", (LongToIntFunction) DateTimeFunctions::minute},
+        {"second", (LongToIntFunction) DateTimeFunctions::second},
+        // TODO: Need to add timezone_hour and timezone_minute
+//      "timezone_hour",
+//      "timezone_minute",
+      //@formatter:on
+    };
+  }
+
+  @Test(dataProvider = "testCases")
+  public void testExtractTransformFunction(String field, LongToIntFunction 
expected) {
+    // NOTE: functionality of ExtractTransformFunction is covered in 
ExtractTransformFunctionTest
+    // SELECT EXTRACT(YEAR FROM '2017-10-10')
+
+    ExpressionContext expression =
+        RequestContextUtils.getExpression(String.format("extract(%s FROM %s)", 
field, TIMESTAMP_COLUMN));
+
+    TransformFunction transformFunction = 
TransformFunctionFactory.get(expression, _dataSourceMap);
+    Assert.assertTrue(transformFunction instanceof ExtractTransformFunction);
+    int[] value = transformFunction.transformToIntValuesSV(_projectionBlock);
+    for (int i = 0; i < _projectionBlock.getNumDocs(); i++) {
+      assertEquals(value[i], expected.applyAsInt(_timeValues[i]));
+    }
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to