SQL: implement COT, RANDOM, SIGN math functions (elastic/x-pack-elasticsearch#4394)

Add a number of missing math functions

Original commit: elastic/x-pack-elasticsearch@a26d9d2454
This commit is contained in:
Costin Leau 2018-04-20 15:07:14 +03:00 committed by GitHub
parent 043a877a31
commit 03ecbc0ccb
9 changed files with 193 additions and 3 deletions

View File

@ -40,6 +40,7 @@ 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.Cos;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cosh;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Cot;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Degrees;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.E;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Exp;
@ -50,7 +51,9 @@ 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.Random;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Round;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sign;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sin;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sinh;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.Sqrt;
@ -109,9 +112,10 @@ public class FunctionRegistry {
def(ATan.class, ATan::new),
def(ATan2.class, ATan2::new),
def(Cbrt.class, Cbrt::new),
def(Ceil.class, Ceil::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),
@ -124,7 +128,9 @@ public class FunctionRegistry {
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),

View File

@ -0,0 +1,45 @@
/*
* 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.MathProcessor.MathOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Locale;
import static java.lang.String.format;
/**
* <a href="https://en.wikipedia.org/wiki/Trigonometric_functions#Cosecant,_secant,_and_cotangent">Cotangent</a>
* function.
*/
public class Cot extends MathFunction {
public Cot(Location location, Expression field) {
super(location, field);
}
@Override
protected NodeInfo<Cot> info() {
return NodeInfo.create(this, Cot::new, field());
}
@Override
protected Cot replaceChild(Expression newChild) {
return new Cot(location(), newChild);
}
@Override
protected String formatScript(String template) {
return super.formatScript(format(Locale.ROOT, "1.0 / Math.tan(%s)", template));
}
@Override
protected MathOperation operation() {
return MathOperation.COT;
}
}

View File

@ -5,12 +5,14 @@
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.math;
import org.elasticsearch.common.Randomness;
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;
import java.util.Random;
import java.util.function.DoubleFunction;
import java.util.function.Function;
import java.util.function.Supplier;
@ -26,6 +28,7 @@ public class MathProcessor implements Processor {
return Math.abs(((Double) l).doubleValue());
}
long lo = ((Number) l).longValue();
//handles the corner-case of Long.MIN_VALUE
return lo >= 0 ? lo : lo == Long.MIN_VALUE ? Long.MAX_VALUE : -lo;
}),
@ -36,6 +39,7 @@ public class MathProcessor implements Processor {
CEIL(Math::ceil),
COS(Math::cos),
COSH(Math::cosh),
COT((Object l) -> 1.0d / Math.tan(((Number) l).doubleValue())),
DEGREES(Math::toDegrees),
E(() -> Math.E),
EXP(Math::exp),
@ -45,7 +49,11 @@ public class MathProcessor implements Processor {
LOG10(Math::log10),
PI(() -> Math.PI),
RADIANS(Math::toRadians),
RANDOM((Object l) -> l != null ?
new Random(((Number) l).longValue()).nextDouble() :
Randomness.get().nextDouble(), true),
ROUND((DoubleFunction<Object>) Math::round),
SIGN((DoubleFunction<Object>) Math::signum),
SIN(Math::sin),
SINH(Math::sinh),
SQRT(Math::sqrt),
@ -54,8 +62,21 @@ public class MathProcessor implements Processor {
private final Function<Object, Object> apply;
MathOperation(Function<Object, Object> apply) {
this(apply, false);
}
/**
* Wrapper for nulls around the given function.
* If true, nulls are passed through, otherwise the function is short-circuited
* and null returned.
*/
MathOperation(Function<Object, Object> apply, boolean nullAware) {
if (nullAware) {
this.apply = apply;
} else {
this.apply = l -> l == null ? null : apply.apply(l);
}
}
MathOperation(DoubleFunction<Object> apply) {
this.apply = (Object l) -> l == null ? null : apply.apply(((Number) l).doubleValue());

View File

@ -0,0 +1,47 @@
/*
* 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.MathProcessor.MathOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Locale;
import static java.lang.String.format;
/**
* Returns a random double (using the given seed).
*/
public class Random extends MathFunction {
public Random(Location location, Expression field) {
super(location, field);
}
@Override
protected NodeInfo<Random> info() {
return NodeInfo.create(this, Random::new, field());
}
@Override
protected Random replaceChild(Expression newChild) {
return new Random(location(), newChild);
}
@Override
protected String formatScript(String template) {
//TODO: Painless script uses Random since Randomness is not whitelisted
return super.formatScript(
format(Locale.ROOT, "%s != null ? new Random((long) %s).nextDouble() : Math.random()", template, template));
}
@Override
protected MathOperation operation() {
return MathOperation.RANDOM;
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.MathProcessor.MathOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
/**
* Returns the sign of the given expression:
* <ul>
* <li>-1 if it is negative</li>
* <li> 0 if it is zero</li>
* <li>+1 if it is positive</li>
* </ul>
*/
public class Sign extends MathFunction {
public Sign(Location location, Expression field) {
super(location, field);
}
@Override
protected NodeInfo<Sign> info() {
return NodeInfo.create(this, Sign::new, field());
}
@Override
protected Sign replaceChild(Expression newChild) {
return new Sign(location(), newChild);
}
@Override
protected String mathFunction() {
return "signum";
}
@Override
protected MathOperation operation() {
return MathOperation.SIGN;
}
@Override
public DataType dataType() {
return DataType.INTEGER;
}
}

View File

@ -55,7 +55,7 @@ public class VerifierErrorMessagesTests extends ESTestCase {
}
public void testMisspelledFunction() {
assertEquals("1:8: Unknown function [COONT], did you mean [COUNT]?", verify("SELECT COONT(bool) FROM test"));
assertEquals("1:8: Unknown function [COONT], did you mean any of [COUNT, COT]?", verify("SELECT COONT(bool) FROM test"));
}
public void testMissingColumnInGroupBy() {

View File

@ -49,4 +49,10 @@ public class MathFunctionProcessorTests extends AbstractWireSerializingTestCase<
assertEquals("A number is required; received [string]", siae.getMessage());
}
public void testRandom() {
MathProcessor proc = new MathProcessor(MathOperation.RANDOM);
assertNotNull(proc.process(null));
assertNotNull(proc.process(randomLong()));
}
}

View File

@ -45,8 +45,10 @@ ATAN |SCALAR
ATAN2 |SCALAR
CBRT |SCALAR
CEIL |SCALAR
CEILING |SCALAR
COS |SCALAR
COSH |SCALAR
COT |SCALAR
DEGREES |SCALAR
E |SCALAR
EXP |SCALAR
@ -58,7 +60,11 @@ MOD |SCALAR
PI |SCALAR
POWER |SCALAR
RADIANS |SCALAR
RANDOM |SCALAR
RAND |SCALAR
ROUND |SCALAR
SIGN |SCALAR
SIGNUM |SCALAR
SIN |SCALAR
SINH |SCALAR
SQRT |SCALAR

View File

@ -31,6 +31,10 @@ mathCosh
// tag::cosh
SELECT COSH(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no;
// end::cosh
mathCot
// tag::cot
SELECT COT(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no;
// end::cot
mathDegrees
// tag::degrees
SELECT DEGREES(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no;
@ -59,6 +63,10 @@ SELECT RADIANS(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER
// end::radians
mathRound
SELECT CAST(ROUND(emp_no) AS INT) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no;
mathSign
// tag::sign
SELECT SIGN(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no;
// end::sign
mathSin
// tag::sin
SELECT SIN(emp_no) m, first_name FROM "test_emp" WHERE emp_no < 10010 ORDER BY emp_no;