Selaa lähdekoodia

SQL: Functions enhancements (OCTET_LENGTH function, order functions alphabetically, RANDOM function docs) (#34101)

* New OCTET_LENGTH function
* Changed the way the FunctionRegistry stores functions, considering the alphabetic ordering by name
* Added documentation for the RANDOM function
Andrei Stefan 7 vuotta sitten
vanhempi
commit
d7a94fb6aa
15 muutettua tiedostoa jossa 435 lisäystä ja 234 poistoa
  1. 24 0
      docs/reference/sql/functions/math.asciidoc
  2. 23 0
      docs/reference/sql/functions/string.asciidoc
  3. 1 2
      x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcDatabaseMetaData.java
  4. 114 94
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java
  5. 1 1
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/BitLength.java
  6. 42 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/OctetLength.java
  7. 1 0
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/StringProcessor.java
  8. 40 36
      x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java
  9. 2 1
      x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt
  10. 47 26
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistryTests.java
  11. 13 0
      x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/StringFunctionProcessorTests.java
  12. 5 5
      x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/cli/ShowTestCase.java
  13. 35 34
      x-pack/qa/sql/src/main/resources/command.csv-spec
  14. 56 35
      x-pack/qa/sql/src/main/resources/docs.csv-spec
  15. 31 0
      x-pack/qa/sql/src/main/resources/functions.csv-spec

+ 24 - 0
docs/reference/sql/functions/math.asciidoc

@@ -276,6 +276,30 @@ include-tagged::{sql-specs}/docs.csv-spec[mathInlinePowerPositive]
 include-tagged::{sql-specs}/docs.csv-spec[mathInlinePowerNegative]
 --------------------------------------------------
 
+[[sql-functions-math-random]]
+===== `RANDOM`
+
+.Synopsis:
+[source, sql]
+--------------------------------------------------
+RANDOM(seed<1>)
+--------------------------------------------------
+
+*Input*:
+
+<1> numeric expression
+
+*Output*: double numeric value
+
+.Description:
+
+Returns a random double using the given seed.
+
+["source","sql",subs="attributes,macros"]
+--------------------------------------------------
+include-tagged::{sql-specs}/docs.csv-spec[mathRandom]
+--------------------------------------------------
+
 [[sql-functions-math-round]]
 ===== `ROUND`
 

+ 23 - 0
docs/reference/sql/functions/string.asciidoc

@@ -271,6 +271,29 @@ Returns the characters of `string_exp`, with leading blanks removed.
 include-tagged::{sql-specs}/docs.csv-spec[stringLTrim]
 --------------------------------------------------
 
+[[sql-functions-string-octet-length]]
+==== `OCTET_LENGTH`
+
+.Synopsis:
+[source, sql]
+--------------------------------------------------
+OCTET_LENGTH(string_exp<1>)
+--------------------------------------------------
+*Input*:
+
+<1> string expression
+
+*Output*: integer
+
+.Description:
+
+Returns the length in bytes of the `string_exp` input expression.
+
+["source","sql",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{sql-specs}/docs.csv-spec[stringOctetLength]
+--------------------------------------------------
+
 [[sql-functions-string-position]]
 ==== `POSITION`
 

+ 1 - 2
x-pack/plugin/sql/jdbc/src/main/java/org/elasticsearch/xpack/sql/jdbc/jdbc/JdbcDatabaseMetaData.java

@@ -202,8 +202,7 @@ class JdbcDatabaseMetaData implements DatabaseMetaData, JdbcWrapper {
                 + "CHAR,CHAR_LENGTH,CHARACTER_LENGTH,CONCAT,"
                 + "INSERT,"
                 + "LCASE,LEFT,LENGTH,LOCATE,LTRIM,"
-                // waiting on https://github.com/elastic/elasticsearch/issues/33477
-                //+ "OCTET_LENGTH,"
+                + "OCTET_LENGTH,"
                 + "POSITION,"
                 + "REPEAT,REPLACE,RIGHT,RTRIM,"
                 + "SPACE,SUBSTRING,"

+ 114 - 94
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java

@@ -72,6 +72,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.string.LTrim;
 import org.elasticsearch.xpack.sql.expression.function.scalar.string.Left;
 import org.elasticsearch.xpack.sql.expression.function.scalar.string.Length;
 import org.elasticsearch.xpack.sql.expression.function.scalar.string.Locate;
+import org.elasticsearch.xpack.sql.expression.function.scalar.string.OctetLength;
 import org.elasticsearch.xpack.sql.expression.function.scalar.string.Position;
 import org.elasticsearch.xpack.sql.expression.function.scalar.string.RTrim;
 import org.elasticsearch.xpack.sql.expression.function.scalar.string.Repeat;
@@ -92,124 +93,143 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.TimeZone;
 import java.util.function.BiFunction;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import static java.util.Collections.emptyList;
 import static java.util.Collections.unmodifiableList;
 import static java.util.stream.Collectors.toList;
 
 public class FunctionRegistry {
-    private static final List<FunctionDefinition> DEFAULT_FUNCTIONS = unmodifiableList(Arrays.asList(
-        // Aggregate functions
-            def(Avg.class, Avg::new),
-            def(Count.class, Count::new),
-            def(Max.class, Max::new),
-            def(Min.class, Min::new),
-            def(Sum.class, Sum::new),
-            // Statistics
-            def(StddevPop.class, StddevPop::new),
-            def(VarPop.class, VarPop::new),
-            def(Percentile.class, Percentile::new),
-            def(PercentileRank.class, PercentileRank::new),
-            def(SumOfSquares.class, SumOfSquares::new),
-            def(Skewness.class, Skewness::new),
-            def(Kurtosis.class, Kurtosis::new),
-            // Scalar functions
-            // Date
-            def(DayName.class, DayName::new, "DAYNAME"),
-            def(DayOfMonth.class, DayOfMonth::new, "DAYOFMONTH", "DAY", "DOM"),
-            def(DayOfWeek.class, DayOfWeek::new, "DAYOFWEEK", "DOW"),
-            def(DayOfYear.class, DayOfYear::new, "DAYOFYEAR", "DOY"),
-            def(HourOfDay.class, HourOfDay::new, "HOUR"),
-            def(MinuteOfDay.class, MinuteOfDay::new),
-            def(MinuteOfHour.class, MinuteOfHour::new, "MINUTE"),
-            def(MonthName.class, MonthName::new, "MONTHNAME"),
-            def(MonthOfYear.class, MonthOfYear::new, "MONTH"),
-            def(SecondOfMinute.class, SecondOfMinute::new, "SECOND"),
-            def(Quarter.class, Quarter::new),
-            def(Year.class, Year::new),
-            def(WeekOfYear.class, WeekOfYear::new, "WEEK"),
-            // Math
-            def(Abs.class, Abs::new),
-            def(ACos.class, ACos::new),
-            def(ASin.class, ASin::new),
-            def(ATan.class, ATan::new),
-            def(ATan2.class, ATan2::new),
-            def(Cbrt.class, Cbrt::new),
-            def(Ceil.class, Ceil::new, "CEILING"),
-            def(Cos.class, Cos::new),
-            def(Cosh.class, Cosh::new),
-            def(Cot.class, Cot::new),
-            def(Degrees.class, Degrees::new),
-            def(E.class, E::new),
-            def(Exp.class, Exp::new),
-            def(Expm1.class, Expm1::new),
-            def(Floor.class, Floor::new),
-            def(Log.class, Log::new),
-            def(Log10.class, Log10::new),
-            // SQL and ODBC require MOD as a _function_
-            def(Mod.class, Mod::new),
-            def(Pi.class, Pi::new),
-            def(Power.class, Power::new),
-            def(Radians.class, Radians::new),
-            def(Random.class, Random::new, "RAND"),
-            def(Round.class, Round::new),
-            def(Sign.class, Sign::new, "SIGNUM"),
-            def(Sin.class, Sin::new),
-            def(Sinh.class, Sinh::new),
-            def(Sqrt.class, Sqrt::new),
-            def(Tan.class, Tan::new),
-            def(Truncate.class, Truncate::new),
-            // String
-            def(Ascii.class, Ascii::new),
-            def(BitLength.class, BitLength::new),
-            def(Char.class, Char::new),
-            def(CharLength.class, CharLength::new, "CHARACTER_LENGTH"),
-            def(Concat.class, Concat::new),
-            def(Insert.class, Insert::new),
-            def(LCase.class, LCase::new),
-            def(Left.class, Left::new),
-            def(Length.class, Length::new),
-            def(Locate.class, Locate::new),
-            def(LTrim.class, LTrim::new),
-            def(Position.class, Position::new),
-            def(Repeat.class, Repeat::new),
-            def(Replace.class, Replace::new),
-            def(Right.class, Right::new),
-            def(RTrim.class, RTrim::new),
-            def(Space.class, Space::new),
-            def(Substring.class, Substring::new),
-            def(UCase.class, UCase::new),
-            // Special
-            def(Score.class, Score::new)));
-
+    // list of functions grouped by type of functions (aggregate, statistics, math etc) and ordered alphabetically inside each group
+    // a single function will have one entry for itself with its name associated to its instance and, also, one entry for each alias
+    // it has with the alias name associated to the FunctionDefinition instance
     private final Map<String, FunctionDefinition> defs = new LinkedHashMap<>();
-    private final Map<String, String> aliases;
+    private final Map<String, String> aliases = new HashMap<>();
 
     /**
      * Constructor to build with the default list of functions.
      */
     public FunctionRegistry() {
-        this(DEFAULT_FUNCTIONS);
+        defineDefaultFunctions();
     }
-
+    
     /**
      * Constructor specifying alternate functions for testing.
      */
-    FunctionRegistry(List<FunctionDefinition> functions) {
-        this.aliases = new HashMap<>();
+    FunctionRegistry(FunctionDefinition... functions) {
+        addToMap(functions);
+    }
+    
+    private void defineDefaultFunctions() {
+        // Aggregate functions
+        addToMap(def(Avg.class, Avg::new),
+                def(Count.class, Count::new),
+                def(Max.class, Max::new),
+                def(Min.class, Min::new),
+                def(Sum.class, Sum::new));
+        // Statistics
+        addToMap(def(StddevPop.class, StddevPop::new),
+                def(VarPop.class, VarPop::new),
+                def(Percentile.class, Percentile::new),
+                def(PercentileRank.class, PercentileRank::new),
+                def(SumOfSquares.class, SumOfSquares::new),
+                def(Skewness.class, Skewness::new),
+                def(Kurtosis.class, Kurtosis::new));
+        // Scalar functions
+        // Date
+        addToMap(def(DayName.class, DayName::new, "DAYNAME"),
+                def(DayOfMonth.class, DayOfMonth::new, "DAYOFMONTH", "DAY", "DOM"),
+                def(DayOfWeek.class, DayOfWeek::new, "DAYOFWEEK", "DOW"),
+                def(DayOfYear.class, DayOfYear::new, "DAYOFYEAR", "DOY"),
+                def(HourOfDay.class, HourOfDay::new, "HOUR"),
+                def(MinuteOfDay.class, MinuteOfDay::new),
+                def(MinuteOfHour.class, MinuteOfHour::new, "MINUTE"),
+                def(MonthName.class, MonthName::new, "MONTHNAME"),
+                def(MonthOfYear.class, MonthOfYear::new, "MONTH"),
+                def(SecondOfMinute.class, SecondOfMinute::new, "SECOND"),
+                def(Quarter.class, Quarter::new),
+                def(Year.class, Year::new),
+                def(WeekOfYear.class, WeekOfYear::new, "WEEK"));
+        // Math
+        addToMap(def(Abs.class, Abs::new),
+                def(ACos.class, ACos::new),
+                def(ASin.class, ASin::new),
+                def(ATan.class, ATan::new),
+                def(ATan2.class, ATan2::new),
+                def(Cbrt.class, Cbrt::new),
+                def(Ceil.class, Ceil::new, "CEILING"),
+                def(Cos.class, Cos::new),
+                def(Cosh.class, Cosh::new),
+                def(Cot.class, Cot::new),
+                def(Degrees.class, Degrees::new),
+                def(E.class, E::new),
+                def(Exp.class, Exp::new),
+                def(Expm1.class, Expm1::new),
+                def(Floor.class, Floor::new),
+                def(Log.class, Log::new),
+                def(Log10.class, Log10::new),
+                // SQL and ODBC require MOD as a _function_
+                def(Mod.class, Mod::new),
+                def(Pi.class, Pi::new),
+                def(Power.class, Power::new),
+                def(Radians.class, Radians::new),
+                def(Random.class, Random::new, "RAND"),
+                def(Round.class, Round::new),
+                def(Sign.class, Sign::new, "SIGNUM"),
+                def(Sin.class, Sin::new),
+                def(Sinh.class, Sinh::new),
+                def(Sqrt.class, Sqrt::new),
+                def(Tan.class, Tan::new),
+                def(Truncate.class, Truncate::new));
+        // String
+        addToMap(def(Ascii.class, Ascii::new),
+                def(BitLength.class, BitLength::new),
+                def(Char.class, Char::new),
+                def(CharLength.class, CharLength::new, "CHARACTER_LENGTH"),
+                def(Concat.class, Concat::new),
+                def(Insert.class, Insert::new),
+                def(LCase.class, LCase::new),
+                def(Left.class, Left::new),
+                def(Length.class, Length::new),
+                def(Locate.class, Locate::new),
+                def(LTrim.class, LTrim::new),
+                def(OctetLength.class, OctetLength::new),
+                def(Position.class, Position::new),
+                def(Repeat.class, Repeat::new),
+                def(Replace.class, Replace::new),
+                def(Right.class, Right::new),
+                def(RTrim.class, RTrim::new),
+                def(Space.class, Space::new),
+                def(Substring.class, Substring::new),
+                def(UCase.class, UCase::new));
+        // Special
+        addToMap(def(Score.class, Score::new));
+    }
+    
+    protected void addToMap(FunctionDefinition...functions) {
+        // temporary map to hold [function_name/alias_name : function instance]
+        Map<String, FunctionDefinition> batchMap = new HashMap<>();
         for (FunctionDefinition f : functions) {
-            defs.put(f.name(), f);
+            batchMap.put(f.name(), f);
             for (String alias : f.aliases()) {
-                Object old = aliases.put(alias, f.name());
-                if (old != null) {
-                    throw new IllegalArgumentException("alias [" + alias + "] is used by [" + old + "] and [" + f.name() + "]");
+                Object old = batchMap.put(alias, f);
+                if (old != null || defs.containsKey(alias)) {
+                    throw new IllegalArgumentException("alias [" + alias + "] is used by "
+                            + "[" + (old != null ? old : defs.get(alias).name()) + "] and [" + f.name() + "]");
                 }
-                defs.put(alias, f);
+                aliases.put(alias, f.name());
             }
         }
+        // sort the temporary map by key name and add it to the global map of functions
+        defs.putAll(batchMap.entrySet().stream()
+                .sorted(Map.Entry.comparingByKey())
+                .collect(Collectors.<Entry<String, FunctionDefinition>, String, 
+                        FunctionDefinition, LinkedHashMap<String, FunctionDefinition>> toMap(Map.Entry::getKey, Map.Entry::getValue,
+                (oldValue, newValue) -> oldValue, LinkedHashMap::new)));
     }
 
     public FunctionDefinition resolveFunction(String functionName) {

+ 1 - 1
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/BitLength.java

@@ -12,7 +12,7 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo;
 import org.elasticsearch.xpack.sql.type.DataType;
 
 /**
- * Returns returns the number of bits contained within the value expression.
+ * Returns the number of bits contained within the value expression.
  */
 public class BitLength extends UnaryStringFunction {
 

+ 42 - 0
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/OctetLength.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+package org.elasticsearch.xpack.sql.expression.function.scalar.string;
+
+import org.elasticsearch.xpack.sql.expression.Expression;
+import org.elasticsearch.xpack.sql.expression.function.scalar.string.StringProcessor.StringOperation;
+import org.elasticsearch.xpack.sql.tree.Location;
+import org.elasticsearch.xpack.sql.tree.NodeInfo;
+import org.elasticsearch.xpack.sql.type.DataType;
+
+/**
+ * Returns the number of bytes contained within the value expression.
+ */
+public class OctetLength extends UnaryStringFunction {
+
+    public OctetLength(Location location, Expression field) {
+        super(location, field);
+    }
+
+    @Override
+    protected NodeInfo<OctetLength> info() {
+        return NodeInfo.create(this, OctetLength::new, field());
+    }
+
+    @Override
+    protected OctetLength replaceChild(Expression newChild) {
+        return new OctetLength(location(), newChild);
+    }
+
+    @Override
+    protected StringOperation operation() {
+        return StringOperation.OCTET_LENGTH;
+    }
+
+    @Override
+    public DataType dataType() {
+        return DataType.INTEGER;
+    }
+}

+ 1 - 0
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/StringProcessor.java

@@ -65,6 +65,7 @@ public class StringProcessor implements Processor {
             return new String(spaces);
         }),
         BIT_LENGTH((String s) -> UnicodeUtil.calcUTF16toUTF8Length(s, 0, s.length()) * 8),
+        OCTET_LENGTH((String s) -> UnicodeUtil.calcUTF16toUTF8Length(s, 0, s.length())),
         CHAR_LENGTH(String::length);
 
         private final Function<Object, Object> apply;

+ 40 - 36
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java

@@ -43,6 +43,14 @@ public final class InternalSqlScriptUtils {
         return QuarterProcessor.quarter(millis, tzId);
     }
     
+    public static Number round(Number v, Number s) {
+        return BinaryMathOperation.ROUND.apply(v, s);
+    }
+    
+    public static Number truncate(Number v, Number s) {
+        return BinaryMathOperation.TRUNCATE.apply(v, s);
+    }
+    
     public static Integer ascii(String s) {
         return (Integer) StringOperation.ASCII.apply(s);
     }
@@ -59,75 +67,71 @@ public final class InternalSqlScriptUtils {
         return (Integer) StringOperation.CHAR_LENGTH.apply(s);
     }
     
-    public static String lcase(String s) {
-        return (String) StringOperation.LCASE.apply(s);
+    public static String concat(String s1, String s2) {
+        return ConcatFunctionProcessor.doProcessInScripts(s1, s2).toString();
     }
     
-    public static String ucase(String s) {
-        return (String) StringOperation.UCASE.apply(s);
+    public static String insert(String s, int start, int length, String r) {
+        return InsertFunctionProcessor.doProcess(s, start, length, r).toString();
     }
     
-    public static Integer length(String s) {
-        return (Integer) StringOperation.LENGTH.apply(s);
+    public static String lcase(String s) {
+        return (String) StringOperation.LCASE.apply(s);
     }
     
-    public static String rtrim(String s) {
-        return (String) StringOperation.RTRIM.apply(s);
+    public static String left(String s, int count) {
+        return BinaryStringNumericOperation.LEFT.apply(s, count);
     }
     
-    public static String ltrim(String s) {
-        return (String) StringOperation.LTRIM.apply(s);
+    public static Integer length(String s) {
+        return (Integer) StringOperation.LENGTH.apply(s);
     }
     
-    public static String space(Number n) {
-        return (String) StringOperation.SPACE.apply(n);
-    }
-
-    public static String left(String s, int count) {
-        return BinaryStringNumericOperation.LEFT.apply(s, count);
+    public static Integer locate(String s1, String s2) {
+        return locate(s1, s2, null);
     }
     
-    public static String right(String s, int count) {
-        return BinaryStringNumericOperation.RIGHT.apply(s, count);
+    public static Integer locate(String s1, String s2, Integer pos) {
+        return (Integer) LocateFunctionProcessor.doProcess(s1, s2, pos);
     }
     
-    public static String concat(String s1, String s2) {
-        return ConcatFunctionProcessor.doProcessInScripts(s1, s2).toString();
+    public static String ltrim(String s) {
+        return (String) StringOperation.LTRIM.apply(s);
     }
     
-    public static String repeat(String s, int count) {
-        return BinaryStringNumericOperation.REPEAT.apply(s, count);
+    public static Integer octetLength(String s) {
+        return (Integer) StringOperation.OCTET_LENGTH.apply(s);
     }
     
     public static Integer position(String s1, String s2) {
         return (Integer) BinaryStringStringOperation.POSITION.apply(s1, s2);
     }
     
-    public static String insert(String s, int start, int length, String r) {
-        return InsertFunctionProcessor.doProcess(s, start, length, r).toString();
-    }
-    
-    public static String substring(String s, int start, int length) {
-        return SubstringFunctionProcessor.doProcess(s, start, length).toString();
+    public static String repeat(String s, int count) {
+        return BinaryStringNumericOperation.REPEAT.apply(s, count);
     }
     
     public static String replace(String s1, String s2, String s3) {
         return ReplaceFunctionProcessor.doProcess(s1, s2, s3).toString();
     }
     
-    public static Integer locate(String s1, String s2, Integer pos) {
-        return (Integer) LocateFunctionProcessor.doProcess(s1, s2, pos);
+    public static String right(String s, int count) {
+        return BinaryStringNumericOperation.RIGHT.apply(s, count);
     }
     
-    public static Integer locate(String s1, String s2) {
-        return locate(s1, s2, null);
+    public static String rtrim(String s) {
+        return (String) StringOperation.RTRIM.apply(s);
     }
     
-    public static Number round(Number v, Number s) {
-        return BinaryMathOperation.ROUND.apply(v, s);
+    public static String space(Number n) {
+        return (String) StringOperation.SPACE.apply(n);
     }
     
-    public static Number truncate(Number v, Number s) {
-        return BinaryMathOperation.TRUNCATE.apply(v, s);
+    public static String substring(String s, int start, int length) {
+        return SubstringFunctionProcessor.doProcess(s, start, length).toString();
+    }
+    
+    public static String ucase(String s) {
+        return (String) StringOperation.UCASE.apply(s);
     }
 }

+ 2 - 1
x-pack/plugin/sql/src/main/resources/org/elasticsearch/xpack/sql/plugin/sql_whitelist.txt

@@ -16,8 +16,8 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS
   Number truncate(Number, Number)
   Integer ascii(String)
   Integer bitLength(String)
-  Integer charLength(String)
   String character(Number)
+  Integer charLength(String)
   String concat(String, String)
   String insert(String, int, int, String)
   String lcase(String)
@@ -26,6 +26,7 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS
   Integer locate(String, String)
   Integer locate(String, String, Integer)
   String ltrim(String)
+  Integer octetLength(String)
   Integer position(String, String)
   String repeat(String, int)
   String replace(String, String, String)

+ 47 - 26
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistryTests.java

@@ -18,7 +18,6 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo;
 import org.elasticsearch.xpack.sql.type.DataType;
 
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.TimeZone;
 
@@ -34,7 +33,7 @@ import static org.mockito.Mockito.mock;
 public class FunctionRegistryTests extends ESTestCase {
     public void testNoArgFunction() {
         UnresolvedFunction ur = uf(STANDARD);
-        FunctionRegistry r = new FunctionRegistry(Collections.singletonList(def(DummyFunction.class, DummyFunction::new)));
+        FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, DummyFunction::new));
         FunctionDefinition def = r.resolveFunction(ur.name());
         assertEquals(ur.location(), ur.buildResolved(randomTimeZone(), def).location());
 
@@ -51,11 +50,10 @@ public class FunctionRegistryTests extends ESTestCase {
 
     public void testUnaryFunction() {
         UnresolvedFunction ur = uf(STANDARD, mock(Expression.class));
-        FunctionRegistry r = new FunctionRegistry(Collections.singletonList(
-            def(DummyFunction.class, (Location l, Expression e) -> {
+        FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, (Location l, Expression e) -> {
             assertSame(e, ur.children().get(0));
             return new DummyFunction(l);
-        })));
+        }));
         FunctionDefinition def = r.resolveFunction(ur.name());
         assertFalse(def.datetime());
         assertEquals(ur.location(), ur.buildResolved(randomTimeZone(), def).location());
@@ -79,12 +77,11 @@ public class FunctionRegistryTests extends ESTestCase {
     public void testUnaryDistinctAwareFunction() {
         boolean urIsDistinct = randomBoolean();
         UnresolvedFunction ur = uf(urIsDistinct ? DISTINCT : STANDARD, mock(Expression.class));
-        FunctionRegistry r = new FunctionRegistry(Collections.singletonList(
-            def(DummyFunction.class, (Location l, Expression e, boolean distinct) -> {
-                assertEquals(urIsDistinct, distinct);
-                assertSame(e, ur.children().get(0));
-                return new DummyFunction(l);
-            })));
+        FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, (Location l, Expression e, boolean distinct) -> {
+                    assertEquals(urIsDistinct, distinct);
+                    assertSame(e, ur.children().get(0));
+                    return new DummyFunction(l);
+        }));
         FunctionDefinition def = r.resolveFunction(ur.name());
         assertEquals(ur.location(), ur.buildResolved(randomTimeZone(), def).location());
         assertFalse(def.datetime());
@@ -104,12 +101,11 @@ public class FunctionRegistryTests extends ESTestCase {
         boolean urIsExtract = randomBoolean();
         UnresolvedFunction ur = uf(urIsExtract ? EXTRACT : STANDARD, mock(Expression.class));
         TimeZone providedTimeZone = randomTimeZone();
-        FunctionRegistry r = new FunctionRegistry(Collections.singletonList(
-            def(DummyFunction.class, (Location l, Expression e, TimeZone tz) -> {
-                assertEquals(providedTimeZone, tz);
-                assertSame(e, ur.children().get(0));
-                return new DummyFunction(l);
-            })));
+        FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, (Location l, Expression e, TimeZone tz) -> {
+                    assertEquals(providedTimeZone, tz);
+                    assertSame(e, ur.children().get(0));
+                    return new DummyFunction(l);
+        }));
         FunctionDefinition def = r.resolveFunction(ur.name());
         assertEquals(ur.location(), ur.buildResolved(providedTimeZone, def).location());
         assertTrue(def.datetime());
@@ -132,12 +128,11 @@ public class FunctionRegistryTests extends ESTestCase {
 
     public void testBinaryFunction() {
         UnresolvedFunction ur = uf(STANDARD, mock(Expression.class), mock(Expression.class));
-        FunctionRegistry r = new FunctionRegistry(Collections.singletonList(
-            def(DummyFunction.class, (Location l, Expression lhs, Expression rhs) -> {
-                assertSame(lhs, ur.children().get(0));
-                assertSame(rhs, ur.children().get(1));
-                return new DummyFunction(l);
-            })));
+        FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, (Location l, Expression lhs, Expression rhs) -> {
+                    assertSame(lhs, ur.children().get(0));
+                    assertSame(rhs, ur.children().get(1));
+                    return new DummyFunction(l);
+        }));
         FunctionDefinition def = r.resolveFunction(ur.name());
         assertEquals(ur.location(), ur.buildResolved(randomTimeZone(), def).location());
         assertFalse(def.datetime());
@@ -163,14 +158,34 @@ public class FunctionRegistryTests extends ESTestCase {
                     .buildResolved(randomTimeZone(), def));
         assertThat(e.getMessage(), endsWith("expects exactly two arguments"));
     }
+    
+    public void testAliasNameIsTheSameAsAFunctionName() {
+        FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, DummyFunction::new, "ALIAS"));
+        IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
+                r.addToMap(def(DummyFunction2.class, DummyFunction2::new, "DUMMY_FUNCTION")));
+        assertEquals(iae.getMessage(), "alias [DUMMY_FUNCTION] is used by [DUMMY_FUNCTION] and [DUMMY_FUNCTION2]");
+    }
+    
+    public void testDuplicateAliasInTwoDifferentFunctionsFromTheSameBatch() {
+        IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
+                new FunctionRegistry(def(DummyFunction.class, DummyFunction::new, "ALIAS"),
+                        def(DummyFunction2.class, DummyFunction2::new, "ALIAS")));
+        assertEquals(iae.getMessage(), "alias [ALIAS] is used by [DUMMY_FUNCTION(ALIAS)] and [DUMMY_FUNCTION2]");
+    }
+    
+    public void testDuplicateAliasInTwoDifferentFunctionsFromTwoDifferentBatches() {
+        FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, DummyFunction::new, "ALIAS"));
+        IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
+                r.addToMap(def(DummyFunction2.class, DummyFunction2::new, "ALIAS")));
+        assertEquals(iae.getMessage(), "alias [ALIAS] is used by [DUMMY_FUNCTION] and [DUMMY_FUNCTION2]");
+    }
 
     public void testFunctionResolving() {
         UnresolvedFunction ur = uf(STANDARD, mock(Expression.class));
-        FunctionRegistry r = new FunctionRegistry(
-            Collections.singletonList(def(DummyFunction.class, (Location l, Expression e) -> {
+        FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, (Location l, Expression e) -> {
             assertSame(e, ur.children().get(0));
             return new DummyFunction(l);
-        }, "DUMMY_FUNC")));
+        }, "DUMMY_FUNC"));
 
         // Resolve by primary name
         FunctionDefinition def = r.resolveFunction(r.resolveAlias("DuMMy_FuncTIon"));
@@ -241,4 +256,10 @@ public class FunctionRegistryTests extends ESTestCase {
             return null;
         }
     }
+    
+    public static class DummyFunction2 extends DummyFunction {
+        public DummyFunction2(Location location) {
+            super(location);
+        }
+    }
 }

+ 13 - 0
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/StringFunctionProcessorTests.java

@@ -202,4 +202,17 @@ public class StringFunctionProcessorTests extends AbstractWireSerializingTestCas
 
         stringCharInputValidation(proc);
     }
+    
+    public void testOctetLength() {
+        StringProcessor proc = new StringProcessor(StringOperation.OCTET_LENGTH);
+        assertNull(proc.process(null));
+        assertEquals(7, proc.process("foo bar"));
+        assertEquals(0, proc.process(""));
+        assertEquals(1, proc.process('f'));
+        assertEquals(3, proc.process('\u20ac')); // euro symbol
+        // euro (3), lamda (2), theta (2), 'white sun with rays' (3), math 'A' (4) symbols
+        assertEquals(14, proc.process("\u20ac\u039B\u03F4\u263C\u1D400"));
+
+        stringCharInputValidation(proc);
+    }
 }

+ 5 - 5
x-pack/qa/sql/src/main/java/org/elasticsearch/xpack/qa/sql/cli/ShowTestCase.java

@@ -59,15 +59,15 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
     public void testShowFunctionsLikeInfix() throws IOException {
         assertThat(command("SHOW FUNCTIONS LIKE '%DAY%'"), RegexMatcher.matches("\\s*name\\s*\\|\\s*type\\s*"));
         assertThat(readLine(), containsString("----------"));
-        assertThat(readLine(), RegexMatcher.matches("\\s*DAY_NAME\\s*\\|\\s*SCALAR\\s*"));
+        assertThat(readLine(), RegexMatcher.matches("\\s*DAY\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*DAYNAME\\s*\\|\\s*SCALAR\\s*"));
-        assertThat(readLine(), RegexMatcher.matches("\\s*DAY_OF_MONTH\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*DAYOFMONTH\\s*\\|\\s*SCALAR\\s*"));
-        assertThat(readLine(), RegexMatcher.matches("\\s*DAY\\s*\\|\\s*SCALAR\\s*"));
-        assertThat(readLine(), RegexMatcher.matches("\\s*DAY_OF_WEEK\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*DAYOFWEEK\\s*\\|\\s*SCALAR\\s*"));
-        assertThat(readLine(), RegexMatcher.matches("\\s*DAY_OF_YEAR\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*DAYOFYEAR\\s*\\|\\s*SCALAR\\s*"));
+        assertThat(readLine(), RegexMatcher.matches("\\s*DAY_NAME\\s*\\|\\s*SCALAR\\s*"));
+        assertThat(readLine(), RegexMatcher.matches("\\s*DAY_OF_MONTH\\s*\\|\\s*SCALAR\\s*"));
+        assertThat(readLine(), RegexMatcher.matches("\\s*DAY_OF_WEEK\\s*\\|\\s*SCALAR\\s*"));
+        assertThat(readLine(), RegexMatcher.matches("\\s*DAY_OF_YEAR\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*HOUR_OF_DAY\\s*\\|\\s*SCALAR\\s*"));
         assertThat(readLine(), RegexMatcher.matches("\\s*MINUTE_OF_DAY\\s*\\|\\s*SCALAR\\s*"));
         assertEquals("", readLine());

+ 35 - 34
x-pack/qa/sql/src/main/resources/command.csv-spec

@@ -12,40 +12,40 @@ COUNT           |AGGREGATE
 MAX             |AGGREGATE      
 MIN             |AGGREGATE      
 SUM             |AGGREGATE      
-STDDEV_POP      |AGGREGATE      
-VAR_POP         |AGGREGATE      
+KURTOSIS        |AGGREGATE      
 PERCENTILE      |AGGREGATE      
 PERCENTILE_RANK |AGGREGATE      
-SUM_OF_SQUARES  |AGGREGATE      
 SKEWNESS        |AGGREGATE      
-KURTOSIS        |AGGREGATE      
+STDDEV_POP      |AGGREGATE      
+SUM_OF_SQUARES  |AGGREGATE      
+VAR_POP         |AGGREGATE      
+DAY             |SCALAR         
+DAYNAME         |SCALAR         
+DAYOFMONTH      |SCALAR         
+DAYOFWEEK       |SCALAR         
+DAYOFYEAR       |SCALAR         
 DAY_NAME        |SCALAR         
-DAYNAME         |SCALAR
 DAY_OF_MONTH    |SCALAR         
-DAYOFMONTH      |SCALAR         
-DAY             |SCALAR         
-DOM             |SCALAR         
 DAY_OF_WEEK     |SCALAR         
-DAYOFWEEK       |SCALAR         
-DOW             |SCALAR         
 DAY_OF_YEAR     |SCALAR         
-DAYOFYEAR       |SCALAR         
+DOM             |SCALAR         
+DOW             |SCALAR         
 DOY             |SCALAR         
-HOUR_OF_DAY     |SCALAR         
 HOUR            |SCALAR         
+HOUR_OF_DAY     |SCALAR         
+MINUTE          |SCALAR         
 MINUTE_OF_DAY   |SCALAR         
 MINUTE_OF_HOUR  |SCALAR         
-MINUTE          |SCALAR         
-MONTH_NAME      |SCALAR         
+MONTH           |SCALAR         
 MONTHNAME       |SCALAR         
+MONTH_NAME      |SCALAR         
 MONTH_OF_YEAR   |SCALAR         
-MONTH           |SCALAR         
-SECOND_OF_MINUTE|SCALAR         
-SECOND          |SCALAR         
 QUARTER         |SCALAR         
-YEAR            |SCALAR         
+SECOND          |SCALAR         
+SECOND_OF_MINUTE|SCALAR         
+WEEK            |SCALAR         
 WEEK_OF_YEAR    |SCALAR         
-WEEK            |SCALAR                  
+YEAR            |SCALAR         
 ABS             |SCALAR         
 ACOS            |SCALAR         
 ASIN            |SCALAR         
@@ -68,8 +68,8 @@ MOD             |SCALAR
 PI              |SCALAR         
 POWER           |SCALAR         
 RADIANS         |SCALAR         
-RANDOM          |SCALAR         
 RAND            |SCALAR         
+RANDOM          |SCALAR         
 ROUND           |SCALAR         
 SIGN            |SCALAR         
 SIGNUM          |SCALAR         
@@ -81,21 +81,22 @@ TRUNCATE        |SCALAR
 ASCII           |SCALAR         
 BIT_LENGTH      |SCALAR         
 CHAR            |SCALAR         
-CHAR_LENGTH     |SCALAR         
 CHARACTER_LENGTH|SCALAR         
-CONCAT          |SCALAR
-INSERT          |SCALAR                  
+CHAR_LENGTH     |SCALAR         
+CONCAT          |SCALAR         
+INSERT          |SCALAR         
 LCASE           |SCALAR         
 LEFT            |SCALAR         
 LENGTH          |SCALAR         
 LOCATE          |SCALAR         
 LTRIM           |SCALAR         
+OCTET_LENGTH    |SCALAR         
 POSITION        |SCALAR         
 REPEAT          |SCALAR         
 REPLACE         |SCALAR         
-RIGHT           |SCALAR
+RIGHT           |SCALAR         
 RTRIM           |SCALAR         
-SPACE           |SCALAR                  
+SPACE           |SCALAR         
 SUBSTRING       |SCALAR         
 UCASE           |SCALAR         
 SCORE           |SCORE          
@@ -134,15 +135,15 @@ showFunctionsWithLeadingPattern
 SHOW FUNCTIONS LIKE '%DAY%';
 
     name:s     |    type:s
-DAY_NAME       |SCALAR         
-DAYNAME        |SCALAR 
-DAY_OF_MONTH   |SCALAR         
-DAYOFMONTH     |SCALAR         
-DAY            |SCALAR         
-DAY_OF_WEEK    |SCALAR         
-DAYOFWEEK      |SCALAR         
-DAY_OF_YEAR    |SCALAR         
-DAYOFYEAR      |SCALAR         
+DAY            |SCALAR
+DAYNAME        |SCALAR
+DAYOFMONTH     |SCALAR
+DAYOFWEEK      |SCALAR
+DAYOFYEAR      |SCALAR
+DAY_NAME       |SCALAR
+DAY_OF_MONTH   |SCALAR
+DAY_OF_WEEK    |SCALAR
+DAY_OF_YEAR    |SCALAR
 HOUR_OF_DAY    |SCALAR         
 MINUTE_OF_DAY  |SCALAR         
 ;

+ 56 - 35
x-pack/qa/sql/src/main/resources/docs.csv-spec

@@ -188,40 +188,40 @@ COUNT           |AGGREGATE
 MAX             |AGGREGATE      
 MIN             |AGGREGATE      
 SUM             |AGGREGATE      
-STDDEV_POP      |AGGREGATE      
-VAR_POP         |AGGREGATE      
+KURTOSIS        |AGGREGATE      
 PERCENTILE      |AGGREGATE      
 PERCENTILE_RANK |AGGREGATE      
-SUM_OF_SQUARES  |AGGREGATE      
 SKEWNESS        |AGGREGATE      
-KURTOSIS        |AGGREGATE      
+STDDEV_POP      |AGGREGATE      
+SUM_OF_SQUARES  |AGGREGATE      
+VAR_POP         |AGGREGATE      
+DAY             |SCALAR         
+DAYNAME         |SCALAR         
+DAYOFMONTH      |SCALAR         
+DAYOFWEEK       |SCALAR         
+DAYOFYEAR       |SCALAR         
 DAY_NAME        |SCALAR         
-DAYNAME         |SCALAR
 DAY_OF_MONTH    |SCALAR         
-DAYOFMONTH      |SCALAR         
-DAY             |SCALAR         
-DOM             |SCALAR         
 DAY_OF_WEEK     |SCALAR         
-DAYOFWEEK       |SCALAR         
-DOW             |SCALAR         
 DAY_OF_YEAR     |SCALAR         
-DAYOFYEAR       |SCALAR         
+DOM             |SCALAR         
+DOW             |SCALAR         
 DOY             |SCALAR         
-HOUR_OF_DAY     |SCALAR         
 HOUR            |SCALAR         
+HOUR_OF_DAY     |SCALAR         
+MINUTE          |SCALAR         
 MINUTE_OF_DAY   |SCALAR         
 MINUTE_OF_HOUR  |SCALAR         
-MINUTE          |SCALAR         
-MONTH_NAME      |SCALAR         
+MONTH           |SCALAR         
 MONTHNAME       |SCALAR         
+MONTH_NAME      |SCALAR         
 MONTH_OF_YEAR   |SCALAR         
-MONTH           |SCALAR         
-SECOND_OF_MINUTE|SCALAR         
-SECOND          |SCALAR         
 QUARTER         |SCALAR         
-YEAR            |SCALAR         
+SECOND          |SCALAR         
+SECOND_OF_MINUTE|SCALAR         
+WEEK            |SCALAR         
 WEEK_OF_YEAR    |SCALAR         
-WEEK            |SCALAR                  
+YEAR            |SCALAR         
 ABS             |SCALAR         
 ACOS            |SCALAR         
 ASIN            |SCALAR         
@@ -244,8 +244,8 @@ MOD             |SCALAR
 PI              |SCALAR         
 POWER           |SCALAR         
 RADIANS         |SCALAR         
-RANDOM          |SCALAR         
 RAND            |SCALAR         
+RANDOM          |SCALAR         
 ROUND           |SCALAR         
 SIGN            |SCALAR         
 SIGNUM          |SCALAR         
@@ -257,24 +257,25 @@ TRUNCATE        |SCALAR
 ASCII           |SCALAR         
 BIT_LENGTH      |SCALAR         
 CHAR            |SCALAR         
-CHAR_LENGTH     |SCALAR         
 CHARACTER_LENGTH|SCALAR         
-CONCAT          |SCALAR
-INSERT          |SCALAR                  
+CHAR_LENGTH     |SCALAR         
+CONCAT          |SCALAR         
+INSERT          |SCALAR         
 LCASE           |SCALAR         
 LEFT            |SCALAR         
 LENGTH          |SCALAR         
 LOCATE          |SCALAR         
 LTRIM           |SCALAR         
+OCTET_LENGTH    |SCALAR         
 POSITION        |SCALAR         
 REPEAT          |SCALAR         
 REPLACE         |SCALAR         
-RIGHT           |SCALAR
+RIGHT           |SCALAR         
 RTRIM           |SCALAR         
-SPACE           |SCALAR                  
+SPACE           |SCALAR         
 SUBSTRING       |SCALAR         
 UCASE           |SCALAR         
-SCORE           |SCORE          
+SCORE           |SCORE           
 // end::showFunctions
 ;
 
@@ -322,15 +323,15 @@ SHOW FUNCTIONS LIKE '%DAY%';
 
      name      |     type      
 ---------------+---------------
-DAY_NAME       |SCALAR         
-DAYNAME        |SCALAR 
-DAY_OF_MONTH   |SCALAR         
-DAYOFMONTH     |SCALAR         
-DAY            |SCALAR         
-DAY_OF_WEEK    |SCALAR         
-DAYOFWEEK      |SCALAR         
-DAY_OF_YEAR    |SCALAR         
-DAYOFYEAR      |SCALAR         
+DAY            |SCALAR
+DAYNAME        |SCALAR
+DAYOFMONTH     |SCALAR
+DAYOFWEEK      |SCALAR
+DAYOFYEAR      |SCALAR
+DAY_NAME       |SCALAR
+DAY_OF_MONTH   |SCALAR
+DAY_OF_WEEK    |SCALAR
+DAY_OF_YEAR    |SCALAR
 HOUR_OF_DAY    |SCALAR         
 MINUTE_OF_DAY  |SCALAR         
 
@@ -1007,6 +1008,16 @@ Elastic
 // end::stringLTrim
 ;
 
+stringOctetLength
+// tag::stringOctetLength
+SELECT OCTET_LENGTH('Elastic');
+
+OCTET_LENGTH(Elastic)
+-------------------
+7  
+// end::stringOctetLength
+;
+
 stringPosition
 // tag::stringPosition
 SELECT POSITION('Elastic', 'Elasticsearch');
@@ -1342,6 +1353,16 @@ SELECT RADIANS(90), PI()/2;
 // end::mathInlineRadians
 ;
 
+mathRandom
+// tag::mathRandom
+SELECT RANDOM(123);
+
+   RANDOM(123)
+------------------
+0.7231742029971469
+// end::mathRandom
+;
+
 mathRoundWithNegativeParameter
 // tag::mathRoundWithNegativeParameter
 SELECT ROUND(-345.153, -1) AS rounded;

+ 31 - 0
x-pack/qa/sql/src/main/resources/functions.csv-spec

@@ -366,6 +366,37 @@ bu             |1
 by             |1
 ;
 
+octetLengthGroupByAndOrderBy
+SELECT OCTET_LENGTH(first_name), COUNT(*) count FROM "test_emp" GROUP BY OCTET_LENGTH(first_name) ORDER BY OCTET_LENGTH(first_name) LIMIT 10;
+
+OCTET_LENGTH(first_name):i|     count:l
+3                       |4
+4                       |11
+5                       |16
+6                       |24
+7                       |19
+8                       |14
+9                       |10
+10                      |1
+11                      |1
+;
+
+octetLengthOrderByFieldWithWhere
+SELECT OCTET_LENGTH(first_name) len, first_name FROM "test_emp" WHERE OCTET_LENGTH(first_name) > 8 ORDER BY first_name LIMIT 10;
+
+len:i          |  first_name:s
+10             |Adamantios
+9              |Alejandro
+9              |Alejandro
+9              |Chirstian
+9              |Cristinel
+9              |Duangkaew
+9              |Eberhardt
+9              |Margareta
+9              |Prasadram
+11             |Sreekrishna
+;
+
 upperCasingTheSecondLetterFromTheRightFromFirstName
 SELECT CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) f FROM "test_emp" ORDER BY "first_name" LIMIT 10;