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

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


The following commit(s) were added to refs/heads/master by this push:
     new 154b6fb  SQL: Add "POSITION" function. (#6596)
154b6fb is described below

commit 154b6fbcefe1d0b28248a6e5bbfe2a42b08a57ee
Author: Gian Merlino <gianmerl...@gmail.com>
AuthorDate: Tue Nov 13 13:39:00 2018 -0800

    SQL: Add "POSITION" function. (#6596)
    
    Also add a "fromIndex" argument to the strpos expression function. There
    are some -1 and +1 adjustment terms due to the fact that the strpos
    expression behaves like Java indexOf (0-indexed), but the POSITION SQL
    function is 1-indexed.
---
 .../java/org/apache/druid/math/expr/Function.java  | 15 ++++-
 .../org/apache/druid/math/expr/FunctionTest.java   |  4 ++
 docs/content/misc/math-expr.md                     |  2 +-
 docs/content/querying/sql.md                       |  3 +-
 .../builtin/PositionOperatorConversion.java        | 76 ++++++++++++++++++++++
 .../sql/calcite/planner/DruidOperatorTable.java    |  2 +
 .../sql/calcite/expression/ExpressionsTest.java    | 36 ++++++++++
 7 files changed, 133 insertions(+), 5 deletions(-)

diff --git a/core/src/main/java/org/apache/druid/math/expr/Function.java 
b/core/src/main/java/org/apache/druid/math/expr/Function.java
index 5ed460a..3521708 100644
--- a/core/src/main/java/org/apache/druid/math/expr/Function.java
+++ b/core/src/main/java/org/apache/druid/math/expr/Function.java
@@ -955,8 +955,8 @@ interface Function
     @Override
     public ExprEval apply(List<Expr> args, Expr.ObjectBinding bindings)
     {
-      if (args.size() != 2) {
-        throw new IAE("Function[%s] needs 2 arguments", name());
+      if (args.size() < 2 || args.size() > 3) {
+        throw new IAE("Function[%s] needs 2 or 3 arguments", name());
       }
 
       final String haystack = 
NullHandling.nullToEmptyIfNeeded(args.get(0).eval(bindings).asString());
@@ -965,7 +965,16 @@ interface Function
       if (haystack == null || needle == null) {
         return ExprEval.of(null);
       }
-      return ExprEval.of(haystack.indexOf(needle));
+
+      final int fromIndex;
+
+      if (args.size() >= 3) {
+        fromIndex = args.get(2).eval(bindings).asInt();
+      } else {
+        fromIndex = 0;
+      }
+
+      return ExprEval.of(haystack.indexOf(needle, fromIndex));
     }
   }
 
diff --git a/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java 
b/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
index 8f2d3bf..bc04283 100644
--- a/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
+++ b/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
@@ -95,6 +95,10 @@ public class FunctionTest
   public void testStrpos()
   {
     assertExpr("strpos(x, 'o')", 1L);
+    assertExpr("strpos(x, 'o', 0)", 1L);
+    assertExpr("strpos(x, 'o', 1)", 1L);
+    assertExpr("strpos(x, 'o', 2)", 2L);
+    assertExpr("strpos(x, 'o', 3)", -1L);
     assertExpr("strpos(x, '')", 0L);
     assertExpr("strpos(x, 'x')", -1L);
   }
diff --git a/docs/content/misc/math-expr.md b/docs/content/misc/math-expr.md
index 321f8fb..a798d0b 100644
--- a/docs/content/misc/math-expr.md
+++ b/docs/content/misc/math-expr.md
@@ -71,7 +71,7 @@ The following built-in functions are available.
 |replace|replace(expr, pattern, replacement) replaces pattern with replacement|
 |substring|substring(expr, index, length) behaves like java.lang.String's 
substring|
 |strlen|strlen(expr) returns length of a string in UTF-16 code units|
-|strpos|strpos(haystack, needle) returns the position of the needle within the 
haystack, with indexes starting from 0. If the needle is not found then the 
function returns -1.|
+|strpos|strpos(haystack, needle[, fromIndex]) returns the position of the 
needle within the haystack, with indexes starting from 0. The search will begin 
at fromIndex, or 0 if fromIndex is not specified. If the needle is not found 
then the function returns -1.|
 |trim|trim(expr[, chars]) remove leading and trailing characters from `expr` 
if they are present in `chars`. `chars` defaults to ' ' (space) if not 
provided.|
 |ltrim|ltrim(expr[, chars]) remove leading characters from `expr` if they are 
present in `chars`. `chars` defaults to ' ' (space) if not provided.|
 |rtrim|rtrim(expr[, chars]) remove trailing characters from `expr` if they are 
present in `chars`. `chars` defaults to ' ' (space) if not provided.|
diff --git a/docs/content/querying/sql.md b/docs/content/querying/sql.md
index e1fb2e4..f996c62 100644
--- a/docs/content/querying/sql.md
+++ b/docs/content/querying/sql.md
@@ -156,9 +156,10 @@ String functions accept strings, and return a type 
appropriate to the function.
 |`STRLEN(expr)`|Synonym for `LENGTH`.|
 |`LOOKUP(expr, lookupName)`|Look up expr in a registered [query-time lookup 
table](lookups.html).|
 |`LOWER(expr)`|Returns expr in all lowercase.|
+|`POSITION(needle IN haystack [FROM fromIndex])`|Returns the index of needle 
within haystack, with indexes starting from 1. The search will begin at 
fromIndex, or 1 if fromIndex is not specified. If the needle is not found, 
returns 0.|
 |`REGEXP_EXTRACT(expr, pattern, [index])`|Apply regular expression pattern and 
extract a capture group, or null if there is no match. If index is unspecified 
or zero, returns the substring that matched the pattern.|
 |`REPLACE(expr, pattern, replacement)`|Replaces pattern with replacement in 
expr, and returns the result.|
-|`STRPOS(haystack, needle)`|Returns the index of needle within haystack, 
starting from 1. If the needle is not found, returns 0.|
+|`STRPOS(haystack, needle)`|Returns the index of needle within haystack, with 
indexes starting from 1. If the needle is not found, returns 0.|
 |`SUBSTRING(expr, index, [length])`|Returns a substring of expr starting at 
index, with a max length, both measured in UTF-16 code units.|
 |`SUBSTR(expr, index, [length])`|Synonym for SUBSTRING.|
 |`TRIM([BOTH \| LEADING \| TRAILING] [<chars> FROM] expr)`|Returns expr with 
characters removed from the leading, trailing, or both ends of "expr" if they 
are in "chars". If "chars" is not provided, it defaults to " " (a space). If 
the directional argument is not provided, it defaults to "BOTH".|
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/PositionOperatorConversion.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/PositionOperatorConversion.java
new file mode 100644
index 0000000..3b4baf6
--- /dev/null
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/PositionOperatorConversion.java
@@ -0,0 +1,76 @@
+/*
+ * 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.druid.sql.calcite.expression.builtin;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.sql.calcite.expression.DruidExpression;
+import org.apache.druid.sql.calcite.expression.OperatorConversions;
+import org.apache.druid.sql.calcite.expression.SqlOperatorConversion;
+import org.apache.druid.sql.calcite.planner.PlannerContext;
+import org.apache.druid.sql.calcite.table.RowSignature;
+
+public class PositionOperatorConversion implements SqlOperatorConversion
+{
+  private static final DruidExpression ZERO = 
DruidExpression.fromExpression("0");
+
+  @Override
+  public SqlOperator calciteOperator()
+  {
+    return SqlStdOperatorTable.POSITION;
+  }
+
+  @Override
+  public DruidExpression toDruidExpression(
+      final PlannerContext plannerContext,
+      final RowSignature rowSignature,
+      final RexNode rexNode
+  )
+  {
+    return OperatorConversions.convertCall(
+        plannerContext,
+        rowSignature,
+        rexNode,
+        druidExpressions -> {
+          final DruidExpression fromIndexExpression;
+          if (druidExpressions.size() > 2) {
+            fromIndexExpression = DruidExpression.fromExpression(
+                StringUtils.format("(%s - 1)", 
druidExpressions.get(2).getExpression())
+            );
+          } else {
+            fromIndexExpression = ZERO;
+          }
+
+          return DruidExpression.fromExpression(
+              StringUtils.format(
+                  "(%s + 1)",
+                  DruidExpression.functionCall(
+                      "strpos",
+                      ImmutableList.of(druidExpressions.get(1), 
druidExpressions.get(0), fromIndexExpression)
+                  )
+              )
+          );
+        }
+    );
+  }
+}
diff --git 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
index 13790a9..9d8570f 100644
--- 
a/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
+++ 
b/sql/src/main/java/org/apache/druid/sql/calcite/planner/DruidOperatorTable.java
@@ -55,6 +55,7 @@ import 
org.apache.druid.sql.calcite.expression.builtin.ExtractOperatorConversion
 import org.apache.druid.sql.calcite.expression.builtin.FloorOperatorConversion;
 import org.apache.druid.sql.calcite.expression.builtin.LTrimOperatorConversion;
 import 
org.apache.druid.sql.calcite.expression.builtin.MillisToTimestampOperatorConversion;
+import 
org.apache.druid.sql.calcite.expression.builtin.PositionOperatorConversion;
 import org.apache.druid.sql.calcite.expression.builtin.RTrimOperatorConversion;
 import 
org.apache.druid.sql.calcite.expression.builtin.RegexpExtractOperatorConversion;
 import 
org.apache.druid.sql.calcite.expression.builtin.ReinterpretOperatorConversion;
@@ -147,6 +148,7 @@ public class DruidOperatorTable implements SqlOperatorTable
           .add(new MillisToTimestampOperatorConversion())
           .add(new ReinterpretOperatorConversion())
           .add(new RegexpExtractOperatorConversion())
+          .add(new PositionOperatorConversion())
           .add(new StrposOperatorConversion())
           .add(new SubstringOperatorConversion())
           .add(new ConcatOperatorConversion())
diff --git 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java
 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java
index 8ef1d32..d64b9a0 100644
--- 
a/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java
+++ 
b/sql/src/test/java/org/apache/druid/sql/calcite/expression/ExpressionsTest.java
@@ -199,6 +199,42 @@ public class ExpressionsTest extends CalciteTestBase
   }
 
   @Test
+  public void testPosition()
+  {
+    testExpression(
+        rexBuilder.makeCall(
+            SqlStdOperatorTable.POSITION,
+            rexBuilder.makeLiteral("oo"),
+            inputRef("s")
+        ),
+        DruidExpression.fromExpression("(strpos(\"s\",'oo',0) + 1)"),
+        2L
+    );
+
+    testExpression(
+        rexBuilder.makeCall(
+            SqlStdOperatorTable.POSITION,
+            rexBuilder.makeLiteral("oo"),
+            inputRef("s"),
+            rexBuilder.makeExactLiteral(BigDecimal.valueOf(2))
+        ),
+        DruidExpression.fromExpression("(strpos(\"s\",'oo',(2 - 1)) + 1)"),
+        2L
+    );
+
+    testExpression(
+        rexBuilder.makeCall(
+            SqlStdOperatorTable.POSITION,
+            rexBuilder.makeLiteral("oo"),
+            inputRef("s"),
+            rexBuilder.makeExactLiteral(BigDecimal.valueOf(3))
+        ),
+        DruidExpression.fromExpression("(strpos(\"s\",'oo',(3 - 1)) + 1)"),
+        0L
+    );
+  }
+
+  @Test
   public void testPower()
   {
     testExpression(


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

Reply via email to