From bc2ef139a89dfbcd7c7e8f07a55e28ccd5464642 Mon Sep 17 00:00:00 2001 From: Costin Leau Date: Fri, 20 Apr 2018 12:23:42 +0300 Subject: [PATCH] SQL: Add Atan2 and Power functions (elastic/x-pack-elasticsearch#4412) Add missing Atan2 & Power(and introduce BinaryMath operations), similar to MathOperation. Also align arithmetic package with binary math for code reuse. Original commit: elastic/x-pack-elasticsearch@311961815e5f7d0f754c8b6f47856acb062c0b2d --- .../expression/function/FunctionRegistry.java | 7 ++ .../scalar/arithmetic/ArithmeticFunction.java | 58 ++----------- .../arithmetic/BinaryArithmeticProcessor.java | 72 +++------------- .../expression/function/scalar/math/ACos.java | 2 +- .../expression/function/scalar/math/ASin.java | 2 +- .../expression/function/scalar/math/ATan.java | 2 +- .../function/scalar/math/ATan2.java | 49 +++++++++++ .../scalar/math/BinaryMathProcessor.java | 58 +++++++++++++ .../math/BinaryMathProcessorDefinition.java | 69 +++++++++++++++ .../scalar/math/BinaryNumericFunction.java | 86 +++++++++++++++++++ .../scalar/math/BinaryNumericProcessor.java | 76 ++++++++++++++++ .../expression/function/scalar/math/Ceil.java | 5 ++ .../function/scalar/math/Floor.java | 5 ++ .../function/scalar/math/MathFunction.java | 12 ++- .../function/scalar/math/MathProcessor.java | 5 ++ .../function/scalar/math/Power.java | 50 +++++++++++ .../expression/function/scalar/math/Tan.java | 2 +- .../xpack/sql/type/DataTypeConversion.java | 16 ++++ .../analyzer/VerifierErrorMessagesTests.java | 4 +- .../BinaryArithmeticProcessorTests.java | 10 +-- .../math/MathFunctionProcessorTests.java | 9 +- qa/sql/src/main/resources/command.csv-spec | 4 + qa/sql/src/main/resources/math.sql-spec | 12 +++ 23 files changed, 494 insertions(+), 121 deletions(-) create mode 100644 plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ATan2.java create mode 100644 plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessor.java create mode 100644 plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessorDefinition.java create mode 100644 plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryNumericFunction.java create mode 100644 plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryNumericProcessor.java create mode 100644 plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Power.java diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java index 0315b5c17c6..e54027a3313 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java @@ -20,6 +20,7 @@ import org.elasticsearch.xpack.sql.expression.function.aggregate.StddevPop; import org.elasticsearch.xpack.sql.expression.function.aggregate.Sum; import org.elasticsearch.xpack.sql.expression.function.aggregate.SumOfSquares; import org.elasticsearch.xpack.sql.expression.function.aggregate.VarPop; +import org.elasticsearch.xpack.sql.expression.function.scalar.arithmetic.Mod; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfMonth; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfWeek; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfYear; @@ -33,6 +34,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.Year; import org.elasticsearch.xpack.sql.expression.function.scalar.math.ACos; import org.elasticsearch.xpack.sql.expression.function.scalar.math.ASin; import org.elasticsearch.xpack.sql.expression.function.scalar.math.ATan; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.ATan2; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Abs; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cbrt; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Ceil; @@ -46,6 +48,7 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.math.Floor; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Log; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Log10; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Pi; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.Power; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Radians; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Round; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sin; @@ -104,6 +107,7 @@ public class FunctionRegistry { 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), def(Cos.class, Cos::new), @@ -115,7 +119,10 @@ public class FunctionRegistry { 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(Round.class, Round::new), def(Sin.class, Sin::new), diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/ArithmeticFunction.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/ArithmeticFunction.java index 8caee094d79..5715e19963c 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/ArithmeticFunction.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/ArithmeticFunction.java @@ -7,8 +7,9 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.arithmetic; import org.elasticsearch.xpack.sql.expression.Expression; import org.elasticsearch.xpack.sql.expression.Literal; -import org.elasticsearch.xpack.sql.expression.function.scalar.BinaryScalarFunction; import org.elasticsearch.xpack.sql.expression.function.scalar.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryNumericFunction; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition; import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions; import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate; import org.elasticsearch.xpack.sql.tree.Location; @@ -16,20 +17,20 @@ import org.elasticsearch.xpack.sql.type.DataType; import org.elasticsearch.xpack.sql.type.DataTypeConversion; import java.util.Locale; -import java.util.Objects; import static java.lang.String.format; import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder; -public abstract class ArithmeticFunction extends BinaryScalarFunction { +public abstract class ArithmeticFunction extends BinaryNumericFunction { - private BinaryArithmeticOperation operation; + private final BinaryArithmeticOperation operation; ArithmeticFunction(Location location, Expression left, Expression right, BinaryArithmeticOperation operation) { super(location, left, right); this.operation = operation; } + @Override public BinaryArithmeticOperation operation() { return operation; } @@ -39,32 +40,6 @@ public abstract class ArithmeticFunction extends BinaryScalarFunction { return DataTypeConversion.commonType(left().dataType(), right().dataType()); } - @Override - protected TypeResolution resolveType() { - if (!childrenResolved()) { - return new TypeResolution("Unresolved children"); - } - DataType l = left().dataType(); - DataType r = right().dataType(); - - TypeResolution resolution = resolveInputType(l); - - if (resolution == TypeResolution.TYPE_RESOLVED) { - return resolveInputType(r); - } - return resolution; - } - - protected TypeResolution resolveInputType(DataType inputType) { - return inputType.isNumeric() ? TypeResolution.TYPE_RESOLVED - : new TypeResolution("'%s' requires a numeric type, not %s", operation, inputType.sqlName()); - } - - @Override - public Object fold() { - return operation.apply((Number) left().fold(), (Number) right().fold()); - } - @Override protected ScriptTemplate asScriptFrom(ScriptTemplate leftScript, ScriptTemplate rightScript) { String op = operation.symbol(); @@ -79,10 +54,11 @@ public abstract class ArithmeticFunction extends BinaryScalarFunction { } @Override - protected final BinaryArithmeticProcessorDefinition makeProcessorDefinition() { + protected ProcessorDefinition makeProcessorDefinition() { return new BinaryArithmeticProcessorDefinition(location(), this, ProcessorDefinitions.toProcessorDefinition(left()), - ProcessorDefinitions.toProcessorDefinition(right()), operation); + ProcessorDefinitions.toProcessorDefinition(right()), + operation); } @Override @@ -115,20 +91,4 @@ public abstract class ArithmeticFunction extends BinaryScalarFunction { protected boolean useParanthesis() { return !(left() instanceof Literal) || !(right() instanceof Literal); } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != getClass()) { - return false; - } - ArithmeticFunction other = (ArithmeticFunction) obj; - return Objects.equals(other.left(), left()) - && Objects.equals(other.right(), right()) - && Objects.equals(other.operation, operation); - } - - @Override - public int hashCode() { - return Objects.hash(left(), right(), operation); - } -} +} \ No newline at end of file diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/BinaryArithmeticProcessor.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/BinaryArithmeticProcessor.java index 706b0d6664f..3f54004c1b0 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/BinaryArithmeticProcessor.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/BinaryArithmeticProcessor.java @@ -7,18 +7,16 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.arithmetic; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; -import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.BinaryProcessor; +import org.elasticsearch.xpack.sql.expression.function.scalar.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryNumericProcessor; import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor; import java.io.IOException; -import java.util.Locale; -import java.util.Objects; import java.util.function.BiFunction; -public class BinaryArithmeticProcessor extends BinaryProcessor { +public class BinaryArithmeticProcessor extends BinaryNumericProcessor { - public enum BinaryArithmeticOperation { + public enum BinaryArithmeticOperation implements BiFunction { ADD(Arithmetics::add, "+"), SUB(Arithmetics::sub, "-"), @@ -38,6 +36,7 @@ public class BinaryArithmeticProcessor extends BinaryProcessor { return symbol; } + @Override public final Number apply(Number left, Number right) { return process.apply(left, right); } @@ -50,66 +49,21 @@ public class BinaryArithmeticProcessor extends BinaryProcessor { public static final String NAME = "ab"; - private final BinaryArithmeticOperation operation; - public BinaryArithmeticProcessor(Processor left, Processor right, BinaryArithmeticOperation operation) { - super(left, right); - this.operation = operation; + super(left, right, operation); } public BinaryArithmeticProcessor(StreamInput in) throws IOException { - super(in); - operation = in.readEnum(BinaryArithmeticOperation.class); + super(in, i -> i.readEnum(BinaryArithmeticOperation.class)); + } + + @Override + protected void doWrite(StreamOutput out) throws IOException { + out.writeEnum(operation()); } @Override public String getWriteableName() { return NAME; } - - @Override - protected void doWrite(StreamOutput out) throws IOException { - out.writeEnum(operation); - } - - @Override - protected Object doProcess(Object left, Object right) { - if (left == null || right == null) { - return null; - } - if (!(left instanceof Number)) { - throw new SqlIllegalArgumentException("A number is required; received {}", left); - } - if (!(right instanceof Number)) { - throw new SqlIllegalArgumentException("A number is required; received {}", right); - } - - return operation.apply((Number) left, (Number) right); - } - - @Override - public int hashCode() { - return operation.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - BinaryArithmeticProcessor other = (BinaryArithmeticProcessor) obj; - return Objects.equals(operation, other.operation) - && Objects.equals(left(), other.left()) - && Objects.equals(right(), other.right()); - } - - @Override - public String toString() { - return String.format(Locale.ROOT, "(%s %s %s)", left(), operation, right()); - } -} +} \ No newline at end of file diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ACos.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ACos.java index 108556c91c5..d4e01329cd3 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ACos.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ACos.java @@ -13,7 +13,7 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo; /** * Arc cosine - * fuction. + * function. */ public class ACos extends MathFunction { public ACos(Location location, Expression field) { diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ASin.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ASin.java index 83fb5db97d9..26362af968f 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ASin.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ASin.java @@ -12,7 +12,7 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo; /** * Arc sine - * fuction. + * function. */ public class ASin extends MathFunction { public ASin(Location location, Expression field) { diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ATan.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ATan.java index 7b046da97d3..ae64ab2e364 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ATan.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ATan.java @@ -12,7 +12,7 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo; /** * Arc tangent - * fuction. + * function. */ public class ATan extends MathFunction { public ATan(Location location, Expression field) { diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ATan2.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ATan2.java new file mode 100644 index 00000000000..1a86a44d32b --- /dev/null +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/ATan2.java @@ -0,0 +1,49 @@ +/* + * 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.math; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; + +import java.util.function.BiFunction; + +/** + * Multi-valued inverse tangent + * function. + */ +public class ATan2 extends BinaryNumericFunction { + + public ATan2(Location location, Expression left, Expression right) { + super(location, left, right); + } + + @Override + protected BiFunction operation() { + return BinaryMathOperation.ATAN2; + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, ATan2::new, left(), right()); + } + + @Override + protected ATan2 replaceChildren(Expression newLeft, Expression newRight) { + return new ATan2(location(), newLeft, newRight); + } + + @Override + protected ProcessorDefinition makeProcessorDefinition() { + return new BinaryMathProcessorDefinition(location(), this, + ProcessorDefinitions.toProcessorDefinition(left()), + ProcessorDefinitions.toProcessorDefinition(right()), + BinaryMathOperation.ATAN2); + } +} diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessor.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessor.java new file mode 100644 index 00000000000..fca6aa5023d --- /dev/null +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessor.java @@ -0,0 +1,58 @@ +/* + * 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.math; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor; + +import java.io.IOException; +import java.util.function.BiFunction; + +/** + * Binary math operations. Sister class to {@link MathOperation}. + */ +public class BinaryMathProcessor extends BinaryNumericProcessor { + + public enum BinaryMathOperation implements BiFunction { + + ATAN2((l, r) -> Math.atan2(l.doubleValue(), r.doubleValue())), + POWER((l, r) -> Math.pow(l.doubleValue(), r.doubleValue())); + + private final BiFunction process; + + BinaryMathOperation(BiFunction process) { + this.process = process; + } + + @Override + public final Number apply(Number left, Number right) { + return process.apply(left, right); + } + } + + public static final String NAME = "mb"; + + public BinaryMathProcessor(Processor left, Processor right, BinaryMathOperation operation) { + super(left, right, operation); + } + + public BinaryMathProcessor(StreamInput in) throws IOException { + super(in, i -> i.readEnum(BinaryMathOperation.class)); + } + + @Override + protected void doWrite(StreamOutput out) throws IOException { + out.writeEnum(operation()); + } + + @Override + public String getWriteableName() { + return NAME; + } +} diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessorDefinition.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessorDefinition.java new file mode 100644 index 00000000000..482029a8c16 --- /dev/null +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryMathProcessorDefinition.java @@ -0,0 +1,69 @@ +/* + * 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.math; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.BinaryProcessorDefinition; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; + +import java.util.Objects; + +/** + * Processor definition for math operations requiring two arguments. + */ +public class BinaryMathProcessorDefinition extends BinaryProcessorDefinition { + + private final BinaryMathOperation operation; + + public BinaryMathProcessorDefinition(Location location, Expression expression, ProcessorDefinition left, + ProcessorDefinition right, BinaryMathOperation operation) { + super(location, expression, left, right); + this.operation = operation; + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, BinaryMathProcessorDefinition::new, expression(), left(), right(), operation); + } + + public BinaryMathOperation operation() { + return operation; + } + + @Override + protected BinaryProcessorDefinition replaceChildren(ProcessorDefinition left, ProcessorDefinition right) { + return new BinaryMathProcessorDefinition(location(), expression(), left, right, operation); + } + + @Override + public BinaryMathProcessor asProcessor() { + return new BinaryMathProcessor(left().asProcessor(), right().asProcessor(), operation); + } + + @Override + public int hashCode() { + return Objects.hash(left(), right(), operation); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + BinaryMathProcessorDefinition other = (BinaryMathProcessorDefinition) obj; + return Objects.equals(operation, other.operation) + && Objects.equals(left(), other.left()) + && Objects.equals(right(), other.right()); + } +} diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryNumericFunction.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryNumericFunction.java new file mode 100644 index 00000000000..14675270f9f --- /dev/null +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryNumericFunction.java @@ -0,0 +1,86 @@ +/* + * 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.math; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.BinaryScalarFunction; +import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.type.DataType; + +import java.util.Locale; +import java.util.Objects; +import java.util.function.BiFunction; + +import static java.lang.String.format; +import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder; + +public abstract class BinaryNumericFunction extends BinaryScalarFunction { + + protected BinaryNumericFunction(Location location, Expression left, Expression right) { + super(location, left, right); + } + + protected abstract BiFunction operation(); + + @Override + public DataType dataType() { + return DataType.DOUBLE; + } + + @Override + protected TypeResolution resolveType() { + if (!childrenResolved()) { + return new TypeResolution("Unresolved children"); + } + + TypeResolution resolution = resolveInputType(left().dataType()); + + if (resolution == TypeResolution.TYPE_RESOLVED) { + return resolveInputType(right().dataType()); + } + return resolution; + } + + protected TypeResolution resolveInputType(DataType inputType) { + return inputType.isNumeric() ? + TypeResolution.TYPE_RESOLVED : + new TypeResolution("'%s' requires a numeric type, received %s", mathFunction(), inputType.esType); + } + + @Override + public Object fold() { + return operation().apply((Number) left().fold(), (Number) right().fold()); + } + + @Override + protected ScriptTemplate asScriptFrom(ScriptTemplate leftScript, ScriptTemplate rightScript) { + return new ScriptTemplate(format(Locale.ROOT, "Math.%s(%s,%s)", mathFunction(), leftScript.template(), rightScript.template()), + paramsBuilder() + .script(leftScript.params()).script(rightScript.params()) + .build(), dataType()); + } + + protected String mathFunction() { + return getClass().getSimpleName().toLowerCase(Locale.ROOT); + } + + @Override + public int hashCode() { + return Objects.hash(left(), right(), operation()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) { + return false; + } + BinaryNumericFunction other = (BinaryNumericFunction) obj; + return Objects.equals(other.left(), left()) + && Objects.equals(other.right(), right()) + && Objects.equals(other.operation(), operation()); + } +} \ No newline at end of file diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryNumericProcessor.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryNumericProcessor.java new file mode 100644 index 00000000000..3acc1cabf2b --- /dev/null +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/BinaryNumericProcessor.java @@ -0,0 +1,76 @@ +/* + * 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.math; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.BinaryProcessor; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor; + +import java.io.IOException; +import java.util.Locale; +import java.util.Objects; +import java.util.function.BiFunction; + +public abstract class BinaryNumericProcessor & BiFunction> extends BinaryProcessor { + + private final O operation; + + protected BinaryNumericProcessor(Processor left, Processor right, O operation) { + super(left, right); + this.operation = operation; + } + + protected BinaryNumericProcessor(StreamInput in, Reader reader) throws IOException { + super(in); + operation = reader.read(in); + } + + protected O operation() { + return operation; + } + + @Override + protected Object doProcess(Object left, Object right) { + if (left == null || right == null) { + return null; + } + if (!(left instanceof Number)) { + throw new SqlIllegalArgumentException("A number is required; received {}", left); + } + if (!(right instanceof Number)) { + throw new SqlIllegalArgumentException("A number is required; received {}", right); + } + + return operation.apply((Number) left, (Number) right); + } + + @Override + public int hashCode() { + return Objects.hash(operation); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + BinaryNumericProcessor other = (BinaryNumericProcessor) obj; + return Objects.equals(operation, other.operation) + && Objects.equals(left(), other.left()) + && Objects.equals(right(), other.right()); + } + + @Override + public String toString() { + return String.format(Locale.ROOT, "(%s %s %s)", left(), operation, right()); + } +} \ No newline at end of file diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Ceil.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Ceil.java index adbcd08ce42..0203a137466 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Ceil.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Ceil.java @@ -31,6 +31,11 @@ public class Ceil extends MathFunction { return new Ceil(location(), newChild); } + @Override + public Number fold() { + return DataTypeConversion.toInteger((double) super.fold(), dataType()); + } + @Override protected MathOperation operation() { return MathOperation.CEIL; diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Floor.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Floor.java index 66a5d8ef85e..7548b03f784 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Floor.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Floor.java @@ -31,6 +31,11 @@ public class Floor extends MathFunction { return new Floor(location(), newChild); } + @Override + public Object fold() { + return DataTypeConversion.toInteger((double) super.fold(), dataType()); + } + @Override protected MathOperation operation() { return MathOperation.FLOOR; diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunction.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunction.java index 05d333bee1f..c50b7243f10 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunction.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunction.java @@ -52,6 +52,16 @@ public abstract class MathFunction extends UnaryScalarFunction { public DataType dataType() { return DataType.DOUBLE; } + + @Override + protected TypeResolution resolveType() { + if (!childrenResolved()) { + return new TypeResolution("Unresolved children"); + } + + return field().dataType().isNumeric() ? TypeResolution.TYPE_RESOLVED + : new TypeResolution("'%s' requires a numeric type, received %s", operation(), field().dataType().esType); + } @Override protected final ProcessorDefinition makeProcessorDefinition() { @@ -74,4 +84,4 @@ public abstract class MathFunction extends UnaryScalarFunction { public int hashCode() { return Objects.hash(field()); } -} +} \ No newline at end of file diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java index 5336ff66afc..38889ecb43b 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathProcessor.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.math; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor; import java.io.IOException; @@ -93,6 +94,10 @@ public class MathProcessor implements Processor { @Override public Object process(Object input) { + if (input != null && !(input instanceof Number)) { + throw new SqlIllegalArgumentException("A number is required; received [{}]", input); + } + return processor.apply(input); } diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Power.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Power.java new file mode 100644 index 00000000000..d38d7067caf --- /dev/null +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Power.java @@ -0,0 +1,50 @@ +/* + * 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.math; + +import org.elasticsearch.xpack.sql.expression.Expression; +import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition; +import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions; +import org.elasticsearch.xpack.sql.tree.Location; +import org.elasticsearch.xpack.sql.tree.NodeInfo; + +import java.util.function.BiFunction; + +public class Power extends BinaryNumericFunction { + + public Power(Location location, Expression left, Expression right) { + super(location, left, right); + } + + @Override + protected BiFunction operation() { + return BinaryMathOperation.POWER; + } + + @Override + protected NodeInfo info() { + return NodeInfo.create(this, Power::new, left(), right()); + } + + @Override + protected Power replaceChildren(Expression newLeft, Expression newRight) { + return new Power(location(), newLeft, newRight); + } + + @Override + protected ProcessorDefinition makeProcessorDefinition() { + return new BinaryMathProcessorDefinition(location(), this, + ProcessorDefinitions.toProcessorDefinition(left()), + ProcessorDefinitions.toProcessorDefinition(right()), + BinaryMathOperation.POWER); + } + + @Override + protected String mathFunction() { + return "pow"; + } +} diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Tan.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Tan.java index 4728a7947e7..25409c84ff3 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Tan.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/Tan.java @@ -12,7 +12,7 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo; /** * Tangent - * fuction. + * function. */ public class Tan extends MathFunction { public Tan(Location location, Expression field) { diff --git a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypeConversion.java b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypeConversion.java index 889ba0a8c5e..c0f4947bb88 100644 --- a/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypeConversion.java +++ b/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypeConversion.java @@ -22,6 +22,7 @@ import static org.elasticsearch.xpack.sql.type.DataType.BOOLEAN; import static org.elasticsearch.xpack.sql.type.DataType.DATE; import static org.elasticsearch.xpack.sql.type.DataType.LONG; import static org.elasticsearch.xpack.sql.type.DataType.NULL; + /** * Conversions from one Elasticsearch data type to another Elasticsearch data types. *

@@ -316,6 +317,21 @@ public abstract class DataTypeConversion { return Math.round(x); } + public static Number toInteger(double x, DataType dataType) { + long l = safeToLong(x); + + switch (dataType) { + case BYTE: + return safeToByte(l); + case SHORT: + return safeToShort(l); + case INTEGER: + return safeToInt(l); + default: + return l; + } + } + public static boolean convertToBoolean(String val) { String lowVal = val.toLowerCase(Locale.ROOT); if (Booleans.isBoolean(lowVal) == false) { diff --git a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java index 24108f35465..433cece90d6 100644 --- a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java +++ b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/analyzer/VerifierErrorMessagesTests.java @@ -111,8 +111,8 @@ public class VerifierErrorMessagesTests extends ESTestCase { } public void testGroupByOrderByScalarOverNonGrouped() { - assertEquals("1:50: Cannot order by non-grouped column [bool], expected [text]", - verify("SELECT MAX(int) FROM test GROUP BY text ORDER BY ABS(bool)")); + assertEquals("1:50: Cannot order by non-grouped column [date], expected [text]", + verify("SELECT MAX(int) FROM test GROUP BY text ORDER BY YEAR(date)")); } public void testGroupByHavingNonGrouped() { diff --git a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/BinaryArithmeticProcessorTests.java b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/BinaryArithmeticProcessorTests.java index 08736caef66..7baba683d74 100644 --- a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/BinaryArithmeticProcessorTests.java +++ b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/arithmetic/BinaryArithmeticProcessorTests.java @@ -40,29 +40,29 @@ public class BinaryArithmeticProcessorTests extends AbstractWireSerializingTestC } public void testAdd() { - BinaryArithmeticProcessor ba = new Add(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); + Processor ba = new Add(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); assertEquals(10, ba.process(null)); } public void testSub() { - BinaryArithmeticProcessor ba = new Sub(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); + Processor ba = new Sub(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); assertEquals(4, ba.process(null)); } public void testMul() { - BinaryArithmeticProcessor ba = new Mul(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); + Processor ba = new Mul(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); assertEquals(21, ba.process(null)); } public void testDiv() { - BinaryArithmeticProcessor ba = new Div(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); + Processor ba = new Div(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); assertEquals(2, ((Number) ba.process(null)).longValue()); ba = new Div(EMPTY, l((double) 7), l(3)).makeProcessorDefinition().asProcessor(); assertEquals(2.33, ((Number) ba.process(null)).doubleValue(), 0.01d); } public void testMod() { - BinaryArithmeticProcessor ba = new Mod(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); + Processor ba = new Mod(EMPTY, l(7), l(3)).makeProcessorDefinition().asProcessor(); assertEquals(1, ba.process(null)); } diff --git a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunctionProcessorTests.java b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunctionProcessorTests.java index 886531f6f94..d64bd057a26 100644 --- a/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunctionProcessorTests.java +++ b/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/expression/function/scalar/math/MathFunctionProcessorTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.math; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation; import java.io.IOException; @@ -34,7 +35,6 @@ public class MathFunctionProcessorTests extends AbstractWireSerializingTestCase< public void testApply() { MathProcessor proc = new MathProcessor(MathOperation.E); assertEquals(Math.E, proc.process(null)); - assertEquals(Math.E, proc.process("cat")); assertEquals(Math.E, proc.process(Math.PI)); proc = new MathProcessor(MathOperation.SQRT); @@ -42,4 +42,11 @@ public class MathFunctionProcessorTests extends AbstractWireSerializingTestCase< assertEquals(3.0, (double) proc.process(9d), 0); assertEquals(1.77, (double) proc.process(3.14), 0.01); } + + public void testNumberCheck() { + MathProcessor proc = new MathProcessor(MathOperation.E); + SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class, () -> proc.process("string")); + assertEquals("A number is required; received [string]", siae.getMessage()); + + } } diff --git a/qa/sql/src/main/resources/command.csv-spec b/qa/sql/src/main/resources/command.csv-spec index e9fbd507fcc..b9e505e60f8 100644 --- a/qa/sql/src/main/resources/command.csv-spec +++ b/qa/sql/src/main/resources/command.csv-spec @@ -42,6 +42,7 @@ ABS |SCALAR ACOS |SCALAR ASIN |SCALAR ATAN |SCALAR +ATAN2 |SCALAR CBRT |SCALAR CEIL |SCALAR COS |SCALAR @@ -53,7 +54,9 @@ EXPM1 |SCALAR FLOOR |SCALAR LOG |SCALAR LOG10 |SCALAR +MOD |SCALAR PI |SCALAR +POWER |SCALAR RADIANS |SCALAR ROUND |SCALAR SIN |SCALAR @@ -80,6 +83,7 @@ ABS |SCALAR ACOS |SCALAR ASIN |SCALAR ATAN |SCALAR +ATAN2 |SCALAR ; showFunctionsWithPatternChar diff --git a/qa/sql/src/main/resources/math.sql-spec b/qa/sql/src/main/resources/math.sql-spec index d31c26226ce..cdfa1ee35eb 100644 --- a/qa/sql/src/main/resources/math.sql-spec +++ b/qa/sql/src/main/resources/math.sql-spec @@ -112,3 +112,15 @@ mathConstantPI SELECT ABS(emp_no) m, PI() as pi, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; mathConstant SELECT 5 + 2 * 3 / 2 % 2 AS c, PI() as e, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; + +// +// binary functions +// +mathATan2 +// tag::atan2 +SELECT ATAN2(emp_no, emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; +// end::atan2 +mathPower +// tag::power +SELECT POWER(emp_no, 2) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no; +// end::power