SQL: Simplify function registration and resolution (#36417)

Previously, we used a CamelCase to CAMEL_CASE transformation to get the
primary name of a function from its class name which led to some issues
since there are functions that we don't want to be registered this way
(e.g.: IFNULL). To simplify the logic and avoid and "magic"
transformations in the FunctionRegistry a primary name must be provided
explicitely for each function.

The same change is applied for the function resolution (when a function
is used in an SQL statement). There is no CamelCase to CAMEL_CASE
transformation but only upper-casing is applied (fuNcTiOn -> FUNCTION).
This commit is contained in:
Marios Trivyzas 2018-12-10 18:52:09 +02:00 committed by GitHub
parent 10938e5135
commit c3dd0d393d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 131 additions and 142 deletions

View File

@ -94,18 +94,16 @@ import org.elasticsearch.xpack.sql.parser.ParsingException;
import org.elasticsearch.xpack.sql.session.Configuration;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.util.StringUtils;
import org.elasticsearch.xpack.sql.util.Check;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.BiFunction;
import java.util.regex.Pattern;
@ -117,14 +115,6 @@ import static java.util.stream.Collectors.toList;
public class FunctionRegistry {
private static final Set<String> EXCLUDE_FROM_NAME_NORMALIZATION = new HashSet<>();
static {
EXCLUDE_FROM_NAME_NORMALIZATION.add(IfNull.class.getSimpleName());
EXCLUDE_FROM_NAME_NORMALIZATION.add(NullIf.class.getSimpleName());
}
// 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
@ -147,99 +137,99 @@ public class FunctionRegistry {
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));
addToMap(def(Avg.class, Avg::new, "AVG"),
def(Count.class, Count::new, "COUNT"),
def(Max.class, Max::new, "MAX"),
def(Min.class, Min::new, "MIN"),
def(Sum.class, Sum::new, "SUM"));
// 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));
addToMap(def(StddevPop.class, StddevPop::new, "STDDEV_POP"),
def(VarPop.class, VarPop::new,"VAR_POP"),
def(Percentile.class, Percentile::new, "PERCENTILE"),
def(PercentileRank.class, PercentileRank::new, "PERCENTILE_RANK"),
def(SumOfSquares.class, SumOfSquares::new, "SUM_OF_SQUARES"),
def(Skewness.class, Skewness::new, "SKEWNESS"),
def(Kurtosis.class, Kurtosis::new, "KURTOSIS"));
// Scalar functions
// conditional
addToMap(def(Coalesce.class, Coalesce::new),
def(IfNull.class, IfNull::new, "ISNULL", "NVL"),
def(NullIf.class, NullIf::new),
def(Greatest.class, Greatest::new),
def(Least.class, Least::new));
addToMap(def(Coalesce.class, Coalesce::new, "COALESCE"),
def(IfNull.class, IfNull::new, "IFNULL", "ISNULL", "NVL"),
def(NullIf.class, NullIf::new, "NULLIF"),
def(Greatest.class, Greatest::new, "GREATEST"),
def(Least.class, Least::new, "LEAST"));
// 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"));
addToMap(def(DayName.class, DayName::new, "DAY_NAME", "DAYNAME"),
def(DayOfMonth.class, DayOfMonth::new, "DAY_OF_MONTH", "DAYOFMONTH", "DAY", "DOM"),
def(DayOfWeek.class, DayOfWeek::new, "DAY_OF_WEEK", "DAYOFWEEK", "DOW"),
def(DayOfYear.class, DayOfYear::new, "DAY_OF_YEAR", "DAYOFYEAR", "DOY"),
def(HourOfDay.class, HourOfDay::new, "HOUR_OF_DAY", "HOUR"),
def(MinuteOfDay.class, MinuteOfDay::new, "MINUTE_OF_DAY"),
def(MinuteOfHour.class, MinuteOfHour::new, "MINUTE_OF_HOUR", "MINUTE"),
def(MonthName.class, MonthName::new, "MONTH_NAME", "MONTHNAME"),
def(MonthOfYear.class, MonthOfYear::new, "MONTH_OF_YEAR", "MONTH"),
def(SecondOfMinute.class, SecondOfMinute::new, "SECOND_OF_MINUTE", "SECOND"),
def(Quarter.class, Quarter::new, "QUARTER"),
def(Year.class, Year::new, "YEAR"),
def(WeekOfYear.class, WeekOfYear::new, "WEEK_OF_YEAR", "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),
addToMap(def(Abs.class, Abs::new, "ABS"),
def(ACos.class, ACos::new, "ACOS"),
def(ASin.class, ASin::new, "ASIN"),
def(ATan.class, ATan::new, "ATAN"),
def(ATan2.class, ATan2::new, "ATAN2"),
def(Cbrt.class, Cbrt::new, "CBRT"),
def(Ceil.class, Ceil::new, "CEIL", "CEILING"),
def(Cos.class, Cos::new, "COS"),
def(Cosh.class, Cosh::new, "COSH"),
def(Cot.class, Cot::new, "COT"),
def(Degrees.class, Degrees::new, "DEGREES"),
def(E.class, E::new, "E"),
def(Exp.class, Exp::new, "EXP"),
def(Expm1.class, Expm1::new, "EXPM1"),
def(Floor.class, Floor::new, "FLOOR"),
def(Log.class, Log::new, "LOG"),
def(Log10.class, Log10::new, "LOG10"),
// 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));
def(Mod.class, Mod::new, "MOD"),
def(Pi.class, Pi::new, "PI"),
def(Power.class, Power::new, "POWER"),
def(Radians.class, Radians::new, "RADIANS"),
def(Random.class, Random::new, "RANDOM", "RAND"),
def(Round.class, Round::new, "ROUND"),
def(Sign.class, Sign::new, "SIGN", "SIGNUM"),
def(Sin.class, Sin::new, "SIN"),
def(Sinh.class, Sinh::new, "SINH"),
def(Sqrt.class, Sqrt::new, "SQRT"),
def(Tan.class, Tan::new, "TAN"),
def(Truncate.class, Truncate::new, "TRUNCATE"));
// 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));
addToMap(def(Ascii.class, Ascii::new, "ASCII"),
def(BitLength.class, BitLength::new, "BIT_LENGTH"),
def(Char.class, Char::new, "CHAR"),
def(CharLength.class, CharLength::new, "CHAR_LENGTH", "CHARACTER_LENGTH"),
def(Concat.class, Concat::new, "CONCAT"),
def(Insert.class, Insert::new, "INSERT"),
def(LCase.class, LCase::new, "LCASE"),
def(Left.class, Left::new, "LEFT"),
def(Length.class, Length::new, "LENGTH"),
def(Locate.class, Locate::new, "LOCATE"),
def(LTrim.class, LTrim::new, "LTRIM"),
def(OctetLength.class, OctetLength::new, "OCTET_LENGTH"),
def(Position.class, Position::new, "POSITION"),
def(Repeat.class, Repeat::new, "REPEAT"),
def(Replace.class, Replace::new, "REPLACE"),
def(Right.class, Right::new, "RIGHT"),
def(RTrim.class, RTrim::new, "RTRIM"),
def(Space.class, Space::new, "SPACE"),
def(Substring.class, Substring::new, "SUBSTRING"),
def(UCase.class, UCase::new, "UCASE"));
// DataType conversion
addToMap(def(Cast.class, Cast::new, "CONVERT"));
addToMap(def(Cast.class, Cast::new, "CAST", "CONVERT"));
// Scalar "meta" functions
addToMap(def(Database.class, Database::new),
def(User.class, User::new));
addToMap(def(Database.class, Database::new, "DATABASE"),
def(User.class, User::new, "USER"));
// Special
addToMap(def(Score.class, Score::new));
addToMap(def(Score.class, Score::new, "SCORE"));
}
void addToMap(FunctionDefinition...functions) {
@ -293,7 +283,7 @@ public class FunctionRegistry {
public Collection<FunctionDefinition> listFunctions(String pattern) {
// It is worth double checking if we need this copy. These are immutable anyway.
Pattern p = Strings.hasText(pattern) ? Pattern.compile(normalize(pattern)) : null;
Pattern p = Strings.hasText(pattern) ? Pattern.compile(pattern.toUpperCase(Locale.ROOT)) : null;
return defs.entrySet().stream()
.filter(e -> p == null || p.matcher(e.getKey()).matches())
.map(e -> new FunctionDefinition(e.getKey(), emptyList(),
@ -306,7 +296,7 @@ public class FunctionRegistry {
* is not aware of time zone and does not support {@code DISTINCT}.
*/
static <T extends Function> FunctionDefinition def(Class<T> function,
java.util.function.Function<Location, T> ctorRef, String... aliases) {
java.util.function.Function<Location, T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
if (false == children.isEmpty()) {
throw new IllegalArgumentException("expects no arguments");
@ -316,7 +306,7 @@ public class FunctionRegistry {
}
return ctorRef.apply(location);
};
return def(function, builder, false, aliases);
return def(function, builder, false, names);
}
/**
@ -326,7 +316,7 @@ public class FunctionRegistry {
*/
@SuppressWarnings("overloads")
static <T extends Function> FunctionDefinition def(Class<T> function,
ConfigurationAwareFunctionBuilder<T> ctorRef, String... aliases) {
ConfigurationAwareFunctionBuilder<T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
if (false == children.isEmpty()) {
throw new IllegalArgumentException("expects no arguments");
@ -336,7 +326,7 @@ public class FunctionRegistry {
}
return ctorRef.build(location, cfg);
};
return def(function, builder, false, aliases);
return def(function, builder, false, names);
}
interface ConfigurationAwareFunctionBuilder<T> {
@ -349,7 +339,7 @@ public class FunctionRegistry {
*/
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
static <T extends Function> FunctionDefinition def(Class<T> function,
BiFunction<Location, Expression, T> ctorRef, String... aliases) {
BiFunction<Location, Expression, T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
if (children.size() != 1) {
throw new IllegalArgumentException("expects exactly one argument");
@ -359,7 +349,7 @@ public class FunctionRegistry {
}
return ctorRef.apply(location, children.get(0));
};
return def(function, builder, false, aliases);
return def(function, builder, false, names);
}
/**
@ -368,14 +358,14 @@ public class FunctionRegistry {
*/
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
static <T extends Function> FunctionDefinition def(Class<T> function,
MultiFunctionBuilder<T> ctorRef, String... aliases) {
MultiFunctionBuilder<T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
if (distinct) {
throw new IllegalArgumentException("does not support DISTINCT yet it was specified");
}
return ctorRef.build(location, children);
};
return def(function, builder, false, aliases);
return def(function, builder, false, names);
}
interface MultiFunctionBuilder<T> {
@ -388,14 +378,14 @@ public class FunctionRegistry {
*/
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
static <T extends Function> FunctionDefinition def(Class<T> function,
DistinctAwareUnaryFunctionBuilder<T> ctorRef, String... aliases) {
DistinctAwareUnaryFunctionBuilder<T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
if (children.size() != 1) {
throw new IllegalArgumentException("expects exactly one argument");
}
return ctorRef.build(location, children.get(0), distinct);
};
return def(function, builder, false, aliases);
return def(function, builder, false, names);
}
interface DistinctAwareUnaryFunctionBuilder<T> {
@ -408,7 +398,7 @@ public class FunctionRegistry {
*/
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
static <T extends Function> FunctionDefinition def(Class<T> function,
DatetimeUnaryFunctionBuilder<T> ctorRef, String... aliases) {
DatetimeUnaryFunctionBuilder<T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
if (children.size() != 1) {
throw new IllegalArgumentException("expects exactly one argument");
@ -418,7 +408,7 @@ public class FunctionRegistry {
}
return ctorRef.build(location, children.get(0), cfg.timeZone());
};
return def(function, builder, true, aliases);
return def(function, builder, true, names);
}
interface DatetimeUnaryFunctionBuilder<T> {
@ -431,7 +421,7 @@ public class FunctionRegistry {
*/
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
static <T extends Function> FunctionDefinition def(Class<T> function,
BinaryFunctionBuilder<T> ctorRef, String... aliases) {
BinaryFunctionBuilder<T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
boolean isBinaryOptionalParamFunction = function.isAssignableFrom(Round.class) || function.isAssignableFrom(Truncate.class);
if (isBinaryOptionalParamFunction && (children.size() > 2 || children.size() < 1)) {
@ -445,17 +435,24 @@ public class FunctionRegistry {
}
return ctorRef.build(location, children.get(0), children.size() == 2 ? children.get(1) : null);
};
return def(function, builder, false, aliases);
return def(function, builder, false, names);
}
interface BinaryFunctionBuilder<T> {
T build(Location location, Expression lhs, Expression rhs);
}
/**
* Main method to register a function/
* @param names Must always have at least one entry which is the method's primary name
*
*/
@SuppressWarnings("overloads")
private static FunctionDefinition def(Class<? extends Function> function, FunctionBuilder builder,
boolean datetime, String... aliases) {
String primaryName = normalize(function.getSimpleName());
boolean datetime, String... names) {
Check.isTrue(names.length > 0, "At least one name must be provided for the function");
String primaryName = names[0];
List<String> aliases = Arrays.asList(names).subList(1, names.length);
FunctionDefinition.Builder realBuilder = (uf, distinct, cfg) -> {
try {
return builder.build(uf.location(), uf.children(), distinct, cfg);
@ -464,7 +461,7 @@ public class FunctionRegistry {
uf.location().getLineNumber(), uf.location().getColumnNumber());
}
};
return new FunctionDefinition(primaryName, unmodifiableList(Arrays.asList(aliases)), function, datetime, realBuilder);
return new FunctionDefinition(primaryName, unmodifiableList(aliases), function, datetime, realBuilder);
}
private interface FunctionBuilder {
@ -473,7 +470,7 @@ public class FunctionRegistry {
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
static <T extends Function> FunctionDefinition def(Class<T> function,
ThreeParametersFunctionBuilder<T> ctorRef, String... aliases) {
ThreeParametersFunctionBuilder<T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
boolean isLocateFunction = function.isAssignableFrom(Locate.class);
if (isLocateFunction && (children.size() > 3 || children.size() < 2)) {
@ -486,7 +483,7 @@ public class FunctionRegistry {
}
return ctorRef.build(location, children.get(0), children.get(1), children.size() == 3 ? children.get(2) : null);
};
return def(function, builder, false, aliases);
return def(function, builder, false, names);
}
interface ThreeParametersFunctionBuilder<T> {
@ -495,7 +492,7 @@ public class FunctionRegistry {
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
static <T extends Function> FunctionDefinition def(Class<T> function,
FourParametersFunctionBuilder<T> ctorRef, String... aliases) {
FourParametersFunctionBuilder<T> ctorRef, String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) -> {
if (children.size() != 4) {
throw new IllegalArgumentException("expects exactly four arguments");
@ -505,7 +502,7 @@ public class FunctionRegistry {
}
return ctorRef.build(location, children.get(0), children.get(1), children.get(2), children.get(3));
};
return def(function, builder, false, aliases);
return def(function, builder, false, names);
}
interface FourParametersFunctionBuilder<T> {
@ -521,22 +518,13 @@ public class FunctionRegistry {
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
private static <T extends Function> FunctionDefinition def(Class<T> function,
CastFunctionBuilder<T> ctorRef,
String... aliases) {
String... names) {
FunctionBuilder builder = (location, children, distinct, cfg) ->
ctorRef.build(location, children.get(0), children.get(0).dataType());
return def(function, builder, false, aliases);
return def(function, builder, false, names);
}
private interface CastFunctionBuilder<T> {
T build(Location location, Expression expression, DataType dataType);
}
private static String normalize(String name) {
if (EXCLUDE_FROM_NAME_NORMALIZATION.contains(name)) {
return name.toUpperCase(Locale.ROOT);
}
// translate CamelCase to CAMEL_CASE
return StringUtils.camelCaseToUnderscore(name);
}
}

View File

@ -34,9 +34,10 @@ import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
public class FunctionRegistryTests extends ESTestCase {
public void testNoArgFunction() {
UnresolvedFunction ur = uf(STANDARD);
FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, DummyFunction::new));
FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, DummyFunction::new, "DUMMY_FUNCTION"));
FunctionDefinition def = r.resolveFunction(ur.name());
assertEquals(ur.location(), ur.buildResolved(randomConfiguration(), def).location());
@ -56,7 +57,7 @@ public class FunctionRegistryTests extends ESTestCase {
FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, (Location l, Expression e) -> {
assertSame(e, ur.children().get(0));
return new DummyFunction(l);
}));
}, "DUMMY_FUNCTION"));
FunctionDefinition def = r.resolveFunction(ur.name());
assertFalse(def.datetime());
assertEquals(ur.location(), ur.buildResolved(randomConfiguration(), def).location());
@ -84,7 +85,7 @@ public class FunctionRegistryTests extends ESTestCase {
assertEquals(urIsDistinct, distinct);
assertSame(e, ur.children().get(0));
return new DummyFunction(l);
}));
}, "DUMMY_FUNCTION"));
FunctionDefinition def = r.resolveFunction(ur.name());
assertEquals(ur.location(), ur.buildResolved(randomConfiguration(), def).location());
assertFalse(def.datetime());
@ -109,7 +110,7 @@ public class FunctionRegistryTests extends ESTestCase {
assertEquals(providedTimeZone, tz);
assertSame(e, ur.children().get(0));
return new DummyFunction(l);
}));
}, "DUMMY_FUNCTION"));
FunctionDefinition def = r.resolveFunction(ur.name());
assertEquals(ur.location(), ur.buildResolved(providedConfiguration, def).location());
assertTrue(def.datetime());
@ -136,7 +137,7 @@ public class FunctionRegistryTests extends ESTestCase {
assertSame(lhs, ur.children().get(0));
assertSame(rhs, ur.children().get(1));
return new DummyFunction(l);
}));
}, "DUMMY_FUNCTION"));
FunctionDefinition def = r.resolveFunction(ur.name());
assertEquals(ur.location(), ur.buildResolved(randomConfiguration(), def).location());
assertFalse(def.datetime());
@ -164,24 +165,24 @@ public class FunctionRegistryTests extends ESTestCase {
}
public void testAliasNameIsTheSameAsAFunctionName() {
FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, DummyFunction::new, "ALIAS"));
FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, DummyFunction::new, "DUMMY_FUNCTION", "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]");
r.addToMap(def(DummyFunction2.class, DummyFunction2::new, "DUMMY_FUNCTION2", "DUMMY_FUNCTION")));
assertEquals("alias [DUMMY_FUNCTION] is used by [DUMMY_FUNCTION] and [DUMMY_FUNCTION2]", iae.getMessage());
}
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]");
new FunctionRegistry(def(DummyFunction.class, DummyFunction::new, "DUMMY_FUNCTION", "ALIAS"),
def(DummyFunction2.class, DummyFunction2::new, "DUMMY_FUNCTION2", "ALIAS")));
assertEquals("alias [ALIAS] is used by [DUMMY_FUNCTION(ALIAS)] and [DUMMY_FUNCTION2]", iae.getMessage());
}
public void testDuplicateAliasInTwoDifferentFunctionsFromTwoDifferentBatches() {
FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, DummyFunction::new, "ALIAS"));
FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, DummyFunction::new, "DUMMY_FUNCTION", "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]");
r.addToMap(def(DummyFunction2.class, DummyFunction2::new, "DUMMY_FUNCTION2", "ALIAS")));
assertEquals("alias [ALIAS] is used by [DUMMY_FUNCTION] and [DUMMY_FUNCTION2]", iae.getMessage());
}
public void testFunctionResolving() {
@ -189,7 +190,7 @@ public class FunctionRegistryTests extends ESTestCase {
FunctionRegistry r = new FunctionRegistry(def(DummyFunction.class, (Location l, Expression e) -> {
assertSame(e, ur.children().get(0));
return new DummyFunction(l);
}, "DUMMY_FUNC"));
}, "DUMMY_FUNCTION", "DUMMY_FUNC"));
// Resolve by primary name
FunctionDefinition def = r.resolveFunction(r.resolveAlias("DuMMy_FuncTIon"));