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@311961815e
This commit is contained in:
Costin Leau 2018-04-20 12:23:42 +03:00 committed by GitHub
parent e6f69ee269
commit bc2ef139a8
23 changed files with 494 additions and 121 deletions

View File

@ -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.Sum;
import org.elasticsearch.xpack.sql.expression.function.aggregate.SumOfSquares; 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.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.DayOfMonth;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfWeek; import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfWeek;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DayOfYear; 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.ACos;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.ASin; 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.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.Abs;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cbrt; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cbrt;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Ceil; 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.Log;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Log10; 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.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.Radians;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Round; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Round;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sin; import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sin;
@ -104,6 +107,7 @@ public class FunctionRegistry {
def(ACos.class, ACos::new), def(ACos.class, ACos::new),
def(ASin.class, ASin::new), def(ASin.class, ASin::new),
def(ATan.class, ATan::new), def(ATan.class, ATan::new),
def(ATan2.class, ATan2::new),
def(Cbrt.class, Cbrt::new), def(Cbrt.class, Cbrt::new),
def(Ceil.class, Ceil::new), def(Ceil.class, Ceil::new),
def(Cos.class, Cos::new), def(Cos.class, Cos::new),
@ -115,7 +119,10 @@ public class FunctionRegistry {
def(Floor.class, Floor::new), def(Floor.class, Floor::new),
def(Log.class, Log::new), def(Log.class, Log::new),
def(Log10.class, Log10::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(Pi.class, Pi::new),
def(Power.class, Power::new),
def(Radians.class, Radians::new), def(Radians.class, Radians::new),
def(Round.class, Round::new), def(Round.class, Round::new),
def(Sin.class, Sin::new), def(Sin.class, Sin::new),

View File

@ -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.Expression;
import org.elasticsearch.xpack.sql.expression.Literal; 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.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.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate; import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Location; 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 org.elasticsearch.xpack.sql.type.DataTypeConversion;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import static java.lang.String.format; import static java.lang.String.format;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder; 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) { ArithmeticFunction(Location location, Expression left, Expression right, BinaryArithmeticOperation operation) {
super(location, left, right); super(location, left, right);
this.operation = operation; this.operation = operation;
} }
@Override
public BinaryArithmeticOperation operation() { public BinaryArithmeticOperation operation() {
return operation; return operation;
} }
@ -39,32 +40,6 @@ public abstract class ArithmeticFunction extends BinaryScalarFunction {
return DataTypeConversion.commonType(left().dataType(), right().dataType()); 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 @Override
protected ScriptTemplate asScriptFrom(ScriptTemplate leftScript, ScriptTemplate rightScript) { protected ScriptTemplate asScriptFrom(ScriptTemplate leftScript, ScriptTemplate rightScript) {
String op = operation.symbol(); String op = operation.symbol();
@ -79,10 +54,11 @@ public abstract class ArithmeticFunction extends BinaryScalarFunction {
} }
@Override @Override
protected final BinaryArithmeticProcessorDefinition makeProcessorDefinition() { protected ProcessorDefinition makeProcessorDefinition() {
return new BinaryArithmeticProcessorDefinition(location(), this, return new BinaryArithmeticProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(left()), ProcessorDefinitions.toProcessorDefinition(left()),
ProcessorDefinitions.toProcessorDefinition(right()), operation); ProcessorDefinitions.toProcessorDefinition(right()),
operation);
} }
@Override @Override
@ -115,20 +91,4 @@ public abstract class ArithmeticFunction extends BinaryScalarFunction {
protected boolean useParanthesis() { protected boolean useParanthesis() {
return !(left() instanceof Literal) || !(right() instanceof Literal); 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);
}
} }

View File

@ -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.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException; import org.elasticsearch.xpack.sql.expression.function.scalar.arithmetic.BinaryArithmeticProcessor.BinaryArithmeticOperation;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.BinaryProcessor; import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryNumericProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor; import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
import java.util.function.BiFunction; import java.util.function.BiFunction;
public class BinaryArithmeticProcessor extends BinaryProcessor { public class BinaryArithmeticProcessor extends BinaryNumericProcessor<BinaryArithmeticOperation> {
public enum BinaryArithmeticOperation { public enum BinaryArithmeticOperation implements BiFunction<Number, Number, Number> {
ADD(Arithmetics::add, "+"), ADD(Arithmetics::add, "+"),
SUB(Arithmetics::sub, "-"), SUB(Arithmetics::sub, "-"),
@ -38,6 +36,7 @@ public class BinaryArithmeticProcessor extends BinaryProcessor {
return symbol; return symbol;
} }
@Override
public final Number apply(Number left, Number right) { public final Number apply(Number left, Number right) {
return process.apply(left, right); return process.apply(left, right);
} }
@ -50,66 +49,21 @@ public class BinaryArithmeticProcessor extends BinaryProcessor {
public static final String NAME = "ab"; public static final String NAME = "ab";
private final BinaryArithmeticOperation operation;
public BinaryArithmeticProcessor(Processor left, Processor right, BinaryArithmeticOperation operation) { public BinaryArithmeticProcessor(Processor left, Processor right, BinaryArithmeticOperation operation) {
super(left, right); super(left, right, operation);
this.operation = operation;
} }
public BinaryArithmeticProcessor(StreamInput in) throws IOException { public BinaryArithmeticProcessor(StreamInput in) throws IOException {
super(in); super(in, i -> i.readEnum(BinaryArithmeticOperation.class));
operation = in.readEnum(BinaryArithmeticOperation.class); }
@Override
protected void doWrite(StreamOutput out) throws IOException {
out.writeEnum(operation());
} }
@Override @Override
public String getWriteableName() { public String getWriteableName() {
return NAME; 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());
}
} }

View File

@ -13,7 +13,7 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo;
/** /**
* <a href="https://en.wikipedia.org/wiki/Inverse_trigonometric_functions">Arc cosine</a> * <a href="https://en.wikipedia.org/wiki/Inverse_trigonometric_functions">Arc cosine</a>
* fuction. * function.
*/ */
public class ACos extends MathFunction { public class ACos extends MathFunction {
public ACos(Location location, Expression field) { public ACos(Location location, Expression field) {

View File

@ -12,7 +12,7 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo;
/** /**
* <a href="https://en.wikipedia.org/wiki/Inverse_trigonometric_functions">Arc sine</a> * <a href="https://en.wikipedia.org/wiki/Inverse_trigonometric_functions">Arc sine</a>
* fuction. * function.
*/ */
public class ASin extends MathFunction { public class ASin extends MathFunction {
public ASin(Location location, Expression field) { public ASin(Location location, Expression field) {

View File

@ -12,7 +12,7 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo;
/** /**
* <a href="https://en.wikipedia.org/wiki/Inverse_trigonometric_functions">Arc tangent</a> * <a href="https://en.wikipedia.org/wiki/Inverse_trigonometric_functions">Arc tangent</a>
* fuction. * function.
*/ */
public class ATan extends MathFunction { public class ATan extends MathFunction {
public ATan(Location location, Expression field) { public ATan(Location location, Expression field) {

View File

@ -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;
/**
* <a href="https://en.wikipedia.org/wiki/Atan2">Multi-valued inverse tangent</a>
* function.
*/
public class ATan2 extends BinaryNumericFunction {
public ATan2(Location location, Expression left, Expression right) {
super(location, left, right);
}
@Override
protected BiFunction<Number, Number, Number> operation() {
return BinaryMathOperation.ATAN2;
}
@Override
protected NodeInfo<? extends Expression> 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);
}
}

View File

@ -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<BinaryMathOperation> {
public enum BinaryMathOperation implements BiFunction<Number, Number, Number> {
ATAN2((l, r) -> Math.atan2(l.doubleValue(), r.doubleValue())),
POWER((l, r) -> Math.pow(l.doubleValue(), r.doubleValue()));
private final BiFunction<Number, Number, Number> process;
BinaryMathOperation(BiFunction<Number, Number, Number> 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;
}
}

View File

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

View File

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

View File

@ -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<O extends Enum<?> & BiFunction<Number, Number, Number>> 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<O> 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());
}
}

View File

@ -31,6 +31,11 @@ public class Ceil extends MathFunction {
return new Ceil(location(), newChild); return new Ceil(location(), newChild);
} }
@Override
public Number fold() {
return DataTypeConversion.toInteger((double) super.fold(), dataType());
}
@Override @Override
protected MathOperation operation() { protected MathOperation operation() {
return MathOperation.CEIL; return MathOperation.CEIL;

View File

@ -31,6 +31,11 @@ public class Floor extends MathFunction {
return new Floor(location(), newChild); return new Floor(location(), newChild);
} }
@Override
public Object fold() {
return DataTypeConversion.toInteger((double) super.fold(), dataType());
}
@Override @Override
protected MathOperation operation() { protected MathOperation operation() {
return MathOperation.FLOOR; return MathOperation.FLOOR;

View File

@ -53,6 +53,16 @@ public abstract class MathFunction extends UnaryScalarFunction {
return DataType.DOUBLE; 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 @Override
protected final ProcessorDefinition makeProcessorDefinition() { protected final ProcessorDefinition makeProcessorDefinition() {
return new UnaryProcessorDefinition(location(), this, return new UnaryProcessorDefinition(location(), this,

View File

@ -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.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; 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 org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import java.io.IOException; import java.io.IOException;
@ -93,6 +94,10 @@ public class MathProcessor implements Processor {
@Override @Override
public Object process(Object input) { public Object process(Object input) {
if (input != null && !(input instanceof Number)) {
throw new SqlIllegalArgumentException("A number is required; received [{}]", input);
}
return processor.apply(input); return processor.apply(input);
} }

View File

@ -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<Number, Number, Number> operation() {
return BinaryMathOperation.POWER;
}
@Override
protected NodeInfo<? extends Expression> 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";
}
}

View File

@ -12,7 +12,7 @@ import org.elasticsearch.xpack.sql.tree.NodeInfo;
/** /**
* <a href="https://en.wikipedia.org/wiki/Trigonometric_functions#sine">Tangent</a> * <a href="https://en.wikipedia.org/wiki/Trigonometric_functions#sine">Tangent</a>
* fuction. * function.
*/ */
public class Tan extends MathFunction { public class Tan extends MathFunction {
public Tan(Location location, Expression field) { public Tan(Location location, Expression field) {

View File

@ -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.DATE;
import static org.elasticsearch.xpack.sql.type.DataType.LONG; import static org.elasticsearch.xpack.sql.type.DataType.LONG;
import static org.elasticsearch.xpack.sql.type.DataType.NULL; import static org.elasticsearch.xpack.sql.type.DataType.NULL;
/** /**
* Conversions from one Elasticsearch data type to another Elasticsearch data types. * Conversions from one Elasticsearch data type to another Elasticsearch data types.
* <p> * <p>
@ -316,6 +317,21 @@ public abstract class DataTypeConversion {
return Math.round(x); 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) { public static boolean convertToBoolean(String val) {
String lowVal = val.toLowerCase(Locale.ROOT); String lowVal = val.toLowerCase(Locale.ROOT);
if (Booleans.isBoolean(lowVal) == false) { if (Booleans.isBoolean(lowVal) == false) {

View File

@ -111,8 +111,8 @@ public class VerifierErrorMessagesTests extends ESTestCase {
} }
public void testGroupByOrderByScalarOverNonGrouped() { public void testGroupByOrderByScalarOverNonGrouped() {
assertEquals("1:50: Cannot order by non-grouped column [bool], expected [text]", assertEquals("1:50: Cannot order by non-grouped column [date], expected [text]",
verify("SELECT MAX(int) FROM test GROUP BY text ORDER BY ABS(bool)")); verify("SELECT MAX(int) FROM test GROUP BY text ORDER BY YEAR(date)"));
} }
public void testGroupByHavingNonGrouped() { public void testGroupByHavingNonGrouped() {

View File

@ -40,29 +40,29 @@ public class BinaryArithmeticProcessorTests extends AbstractWireSerializingTestC
} }
public void testAdd() { 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)); assertEquals(10, ba.process(null));
} }
public void testSub() { 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)); assertEquals(4, ba.process(null));
} }
public void testMul() { 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)); assertEquals(21, ba.process(null));
} }
public void testDiv() { 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()); assertEquals(2, ((Number) ba.process(null)).longValue());
ba = new Div(EMPTY, l((double) 7), l(3)).makeProcessorDefinition().asProcessor(); ba = new Div(EMPTY, l((double) 7), l(3)).makeProcessorDefinition().asProcessor();
assertEquals(2.33, ((Number) ba.process(null)).doubleValue(), 0.01d); assertEquals(2.33, ((Number) ba.process(null)).doubleValue(), 0.01d);
} }
public void testMod() { 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)); assertEquals(1, ba.process(null));
} }

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.sql.expression.function.scalar.math;
import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.test.AbstractWireSerializingTestCase; import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation; import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation;
import java.io.IOException; import java.io.IOException;
@ -34,7 +35,6 @@ public class MathFunctionProcessorTests extends AbstractWireSerializingTestCase<
public void testApply() { public void testApply() {
MathProcessor proc = new MathProcessor(MathOperation.E); MathProcessor proc = new MathProcessor(MathOperation.E);
assertEquals(Math.E, proc.process(null)); assertEquals(Math.E, proc.process(null));
assertEquals(Math.E, proc.process("cat"));
assertEquals(Math.E, proc.process(Math.PI)); assertEquals(Math.E, proc.process(Math.PI));
proc = new MathProcessor(MathOperation.SQRT); proc = new MathProcessor(MathOperation.SQRT);
@ -42,4 +42,11 @@ public class MathFunctionProcessorTests extends AbstractWireSerializingTestCase<
assertEquals(3.0, (double) proc.process(9d), 0); assertEquals(3.0, (double) proc.process(9d), 0);
assertEquals(1.77, (double) proc.process(3.14), 0.01); 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());
}
} }

View File

@ -42,6 +42,7 @@ ABS |SCALAR
ACOS |SCALAR ACOS |SCALAR
ASIN |SCALAR ASIN |SCALAR
ATAN |SCALAR ATAN |SCALAR
ATAN2 |SCALAR
CBRT |SCALAR CBRT |SCALAR
CEIL |SCALAR CEIL |SCALAR
COS |SCALAR COS |SCALAR
@ -53,7 +54,9 @@ EXPM1 |SCALAR
FLOOR |SCALAR FLOOR |SCALAR
LOG |SCALAR LOG |SCALAR
LOG10 |SCALAR LOG10 |SCALAR
MOD |SCALAR
PI |SCALAR PI |SCALAR
POWER |SCALAR
RADIANS |SCALAR RADIANS |SCALAR
ROUND |SCALAR ROUND |SCALAR
SIN |SCALAR SIN |SCALAR
@ -80,6 +83,7 @@ ABS |SCALAR
ACOS |SCALAR ACOS |SCALAR
ASIN |SCALAR ASIN |SCALAR
ATAN |SCALAR ATAN |SCALAR
ATAN2 |SCALAR
; ;
showFunctionsWithPatternChar showFunctionsWithPatternChar

View File

@ -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; SELECT ABS(emp_no) m, PI() as pi, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no;
mathConstant 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; 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