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
This commit is contained in:
Andrei Stefan 2018-10-09 00:20:18 +03:00 committed by GitHub
parent 9e522d5d97
commit d7a94fb6aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 454 additions and 253 deletions

View File

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

View File

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

View File

@ -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,"

View File

@ -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) {

View File

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

View File

@ -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;
}
}

View File

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

View File

@ -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 ucase(String s) {
return (String) StringOperation.UCASE.apply(s);
}
public static Integer length(String s) {
return (Integer) StringOperation.LENGTH.apply(s);
}
public static String rtrim(String s) {
return (String) StringOperation.RTRIM.apply(s);
}
public static String ltrim(String s) {
return (String) StringOperation.LTRIM.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 String right(String s, int count) {
return BinaryStringNumericOperation.RIGHT.apply(s, count);
}
public static String concat(String s1, String s2) {
return ConcatFunctionProcessor.doProcessInScripts(s1, s2).toString();
}
public static String repeat(String s, int count) {
return BinaryStringNumericOperation.REPEAT.apply(s, count);
}
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 lcase(String s) {
return (String) StringOperation.LCASE.apply(s);
}
public static String replace(String s1, String s2, String s3) {
return ReplaceFunctionProcessor.doProcess(s1, s2, s3).toString();
public static String left(String s, int count) {
return BinaryStringNumericOperation.LEFT.apply(s, count);
}
public static Integer locate(String s1, String s2, Integer pos) {
return (Integer) LocateFunctionProcessor.doProcess(s1, s2, pos);
public static Integer length(String s) {
return (Integer) StringOperation.LENGTH.apply(s);
}
public static Integer locate(String s1, String s2) {
return locate(s1, s2, null);
}
public static Number round(Number v, Number s) {
return BinaryMathOperation.ROUND.apply(v, s);
public static Integer locate(String s1, String s2, Integer pos) {
return (Integer) LocateFunctionProcessor.doProcess(s1, s2, pos);
}
public static Number truncate(Number v, Number s) {
return BinaryMathOperation.TRUNCATE.apply(v, s);
public static String ltrim(String s) {
return (String) StringOperation.LTRIM.apply(s);
}
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 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 String right(String s, int count) {
return BinaryStringNumericOperation.RIGHT.apply(s, count);
}
public static String rtrim(String s) {
return (String) StringOperation.RTRIM.apply(s);
}
public static String space(Number n) {
return (String) StringOperation.SPACE.apply(n);
}
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);
}
}

View File

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

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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*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*DAYNAME\\s*\\|\\s*SCALAR\\s*"));
assertThat(readLine(), RegexMatcher.matches("\\s*DAYOFMONTH\\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());

View File

@ -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
DAY_NAME |SCALAR
DAYNAME |SCALAR
DAY_OF_MONTH |SCALAR
DAYOFMONTH |SCALAR
STDDEV_POP |AGGREGATE
SUM_OF_SQUARES |AGGREGATE
VAR_POP |AGGREGATE
DAY |SCALAR
DOM |SCALAR
DAY_OF_WEEK |SCALAR
DAYNAME |SCALAR
DAYOFMONTH |SCALAR
DAYOFWEEK |SCALAR
DOW |SCALAR
DAY_OF_YEAR |SCALAR
DAYOFYEAR |SCALAR
DAY_NAME |SCALAR
DAY_OF_MONTH |SCALAR
DAY_OF_WEEK |SCALAR
DAY_OF_YEAR |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
MONTHNAME |SCALAR
MONTH_OF_YEAR |SCALAR
MONTH |SCALAR
SECOND_OF_MINUTE|SCALAR
SECOND |SCALAR
MONTHNAME |SCALAR
MONTH_NAME |SCALAR
MONTH_OF_YEAR |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
;

View File

@ -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
DAY_NAME |SCALAR
DAYNAME |SCALAR
DAY_OF_MONTH |SCALAR
DAYOFMONTH |SCALAR
STDDEV_POP |AGGREGATE
SUM_OF_SQUARES |AGGREGATE
VAR_POP |AGGREGATE
DAY |SCALAR
DOM |SCALAR
DAY_OF_WEEK |SCALAR
DAYNAME |SCALAR
DAYOFMONTH |SCALAR
DAYOFWEEK |SCALAR
DOW |SCALAR
DAY_OF_YEAR |SCALAR
DAYOFYEAR |SCALAR
DAY_NAME |SCALAR
DAY_OF_MONTH |SCALAR
DAY_OF_WEEK |SCALAR
DAY_OF_YEAR |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
MONTHNAME |SCALAR
MONTH_OF_YEAR |SCALAR
MONTH |SCALAR
SECOND_OF_MINUTE|SCALAR
SECOND |SCALAR
MONTHNAME |SCALAR
MONTH_NAME |SCALAR
MONTH_OF_YEAR |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;

View File

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