Browse Source

Create e() function (ESQL-1304)

Euler's number.
Nik Everett 2 years ago
parent
commit
35fddc2281

+ 2 - 0
docs/reference/esql/esql-functions.asciidoc

@@ -17,6 +17,7 @@ these functions:
 * <<esql-concat>>
 * <<esql-date_format>>
 * <<esql-date_trunc>>
+* <<esql-e>>
 * <<esql-is_finite>>
 * <<esql-is_infinite>>
 * <<esql-is_nan>>
@@ -50,6 +51,7 @@ include::functions/cidr_match.asciidoc[]
 include::functions/concat.asciidoc[]
 include::functions/date_format.asciidoc[]
 include::functions/date_trunc.asciidoc[]
+include::functions/e.asciidoc[]
 include::functions/is_finite.asciidoc[]
 include::functions/is_infinite.asciidoc[]
 include::functions/is_nan.asciidoc[]

+ 12 - 0
docs/reference/esql/functions/e.asciidoc

@@ -0,0 +1,12 @@
+[[esql-e]]
+=== `E`
+{wikipedia}/E_(mathematical_constant)[Euler's number].
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/math.csv-spec[tag=e]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/math.csv-spec[tag=e-result]
+|===

+ 19 - 0
x-pack/plugin/esql/qa/testFixtures/src/main/resources/math.csv-spec

@@ -339,3 +339,22 @@ a:integer | sum_a:integer
 [3, 5, 6] | 14
 // end::mv_sum-result[]
 ;
+
+e
+// tag::e[]
+ROW E()
+// end::e[]
+;
+
+// tag::e-result[]
+E():double
+2.718281828459045
+// end::e-result[]
+;
+
+eInside
+ROW a=2 | EVAL c = abs(a + e());
+
+a:integer | c:double
+        2 | 4.718281828459045
+;

+ 1 - 0
x-pack/plugin/esql/qa/testFixtures/src/main/resources/show.csv-spec

@@ -20,6 +20,7 @@ count_distinct           |count_distinct(arg1, arg2)
 date_format              |date_format(arg1, arg2)
 date_parse               |date_parse(arg1, arg2)
 date_trunc               |date_trunc(arg1, arg2)
+e                        |e()
 is_finite                |is_finite(arg1)
 is_infinite              |is_infinite(arg1)
 is_nan                   |is_nan(arg1)

+ 2 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java

@@ -32,6 +32,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateTrunc;
 import org.elasticsearch.xpack.esql.expression.function.scalar.ip.CIDRMatch;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Abs;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.AutoBucket;
+import org.elasticsearch.xpack.esql.expression.function.scalar.math.E;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.IsFinite;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.IsInfinite;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.IsNaN;
@@ -81,6 +82,7 @@ public class EsqlFunctionRegistry extends FunctionRegistry {
             new FunctionDefinition[] {
                 def(Abs.class, Abs::new, "abs"),
                 def(AutoBucket.class, AutoBucket::new, "auto_bucket"),
+                def(E.class, E::new, "e"),
                 def(IsFinite.class, IsFinite::new, "is_finite"),
                 def(IsInfinite.class, IsInfinite::new, "is_infinite"),
                 def(IsNaN.class, IsNaN::new, "is_nan"),

+ 57 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/E.java

@@ -0,0 +1,57 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.expression.function.scalar.math;
+
+import org.elasticsearch.xpack.ql.expression.Expression;
+import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
+import org.elasticsearch.xpack.ql.expression.gen.script.ScriptTemplate;
+import org.elasticsearch.xpack.ql.tree.NodeInfo;
+import org.elasticsearch.xpack.ql.tree.Source;
+import org.elasticsearch.xpack.ql.type.DataType;
+import org.elasticsearch.xpack.ql.type.DataTypes;
+
+import java.util.List;
+
+/**
+ * Function that emits Euler's number.
+ */
+public class E extends ScalarFunction {
+    public E(Source source) {
+        super(source);
+    }
+
+    @Override
+    public boolean foldable() {
+        return true;
+    }
+
+    @Override
+    public Object fold() {
+        return Math.E;
+    }
+
+    @Override
+    public DataType dataType() {
+        return DataTypes.DOUBLE;
+    }
+
+    @Override
+    public ScriptTemplate asScript() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Expression replaceChildren(List<Expression> newChildren) {
+        return new E(source());
+    }
+
+    @Override
+    protected NodeInfo<? extends Expression> info() {
+        return NodeInfo.create(this);
+    }
+}

+ 15 - 0
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java

@@ -42,6 +42,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateTrunc;
 import org.elasticsearch.xpack.esql.expression.function.scalar.ip.CIDRMatch;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.Abs;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.AutoBucket;
+import org.elasticsearch.xpack.esql.expression.function.scalar.math.E;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.IsFinite;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.IsInfinite;
 import org.elasticsearch.xpack.esql.expression.function.scalar.math.IsNaN;
@@ -145,6 +146,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.BiFunction;
+import java.util.function.Function;
 
 import static java.util.Map.entry;
 import static org.elasticsearch.xpack.esql.io.stream.PlanNameRegistry.Entry.of;
@@ -256,6 +258,7 @@ public final class PlanNamedTypes {
             of(QL_UNARY_SCLR_CLS, Not.class, PlanNamedTypes::writeQLUnaryScalar, PlanNamedTypes::readQLUnaryScalar),
             of(QL_UNARY_SCLR_CLS, Length.class, PlanNamedTypes::writeQLUnaryScalar, PlanNamedTypes::readQLUnaryScalar),
             of(ESQL_UNARY_SCLR_CLS, Abs.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
+            of(ScalarFunction.class, E.class, PlanNamedTypes::writeNoArgScalar, PlanNamedTypes::readNoArgScalar),
             of(ESQL_UNARY_SCLR_CLS, IsFinite.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
             of(ESQL_UNARY_SCLR_CLS, IsInfinite.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
             of(ESQL_UNARY_SCLR_CLS, IsNaN.class, PlanNamedTypes::writeESQLUnaryScalar, PlanNamedTypes::readESQLUnaryScalar),
@@ -931,6 +934,18 @@ public final class PlanNamedTypes {
         out.writeExpression(function.field());
     }
 
+    static final Map<String, Function<Source, ScalarFunction>> NO_ARG_SCALAR_CTRS = Map.ofEntries(entry(name(E.class), E::new));
+
+    static ScalarFunction readNoArgScalar(PlanStreamInput in, String name) throws IOException {
+        var ctr = NO_ARG_SCALAR_CTRS.get(name);
+        if (ctr == null) {
+            throw new IOException("Constructor not found:" + name);
+        }
+        return ctr.apply(Source.EMPTY);
+    }
+
+    static void writeNoArgScalar(PlanStreamOutput out, ScalarFunction function) {}
+
     static final Map<
         String,
         BiFunction<Source, Expression, org.elasticsearch.xpack.ql.expression.function.scalar.UnaryScalarFunction>> QL_UNARY_SCALAR_CTRS =

+ 3 - 2
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/show/ShowFunctions.java

@@ -53,13 +53,14 @@ public class ShowFunctions extends LeafPlan {
             if (constructors.length > 0) {
                 var params = constructors[0].getParameters(); // no multiple c'tors supported
                 for (int i = 1; i < params.length; i++) { // skipping 1st argument, the source
+                    if (i > 1) {
+                        sb.append(", ");
+                    }
                     sb.append(params[i].getName());
                     if (List.class.isAssignableFrom(params[i].getType())) {
                         sb.append("...");
                     }
-                    sb.append(", ");
                 }
-                sb.delete(sb.length() - 2, sb.length());
             }
             sb.append(')');
             row.add(asBytesRefOrNull(sb.toString()));

+ 13 - 4
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/EvalMapper.java

@@ -199,10 +199,19 @@ public final class EvalMapper {
                     BytesRef v = (BytesRef) lit.value();
                     yield positions -> BytesRefBlock.newConstantBlockWith(v, positions);
                 }
-                case DOUBLE -> {
-                    double v = (double) lit.value();
-                    yield positions -> DoubleBlock.newConstantBlockWith(v, positions);
-                }
+                case DOUBLE -> new IntFunction<>() { // TODO toString in the rest of these and tests for this
+                    private final double v = (double) lit.value();
+
+                    @Override
+                    public Block apply(int positions) {
+                        return DoubleBlock.newConstantBlockWith(v, positions);
+                    }
+
+                    @Override
+                    public String toString() {
+                        return Double.toString(v);
+                    }
+                };
                 case INT -> {
                     int v = (int) lit.value();
                     yield positions -> IntBlock.newConstantBlockWith(v, positions);

+ 3 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractFunctionTestCase.java

@@ -84,6 +84,9 @@ public abstract class AbstractFunctionTestCase extends ESTestCase {
     protected abstract Expression build(Source source, List<Literal> args);
 
     protected final Supplier<EvalOperator.ExpressionEvaluator> evaluator(Expression e) {
+        if (e.foldable()) {
+            e = new Literal(e.source(), e.fold(), e.dataType());
+        }
         Layout.Builder builder = new Layout.Builder();
         // Hack together a layout by scanning for Fields.
         // Those will show up in the layout in whatever order a depth first traversal finds them.

+ 69 - 0
x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ETests.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.esql.expression.function.scalar.math;
+
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.DoubleBlock;
+import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractScalarFunctionTestCase;
+import org.elasticsearch.xpack.ql.expression.Expression;
+import org.elasticsearch.xpack.ql.expression.Literal;
+import org.elasticsearch.xpack.ql.tree.Source;
+import org.elasticsearch.xpack.ql.type.DataType;
+import org.elasticsearch.xpack.ql.type.DataTypes;
+import org.hamcrest.Matcher;
+
+import java.util.List;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class ETests extends AbstractScalarFunctionTestCase {
+    @Override
+    protected List<Object> simpleData() {
+        return List.of(1); // Need to put some data in the input page or it'll fail to build
+    }
+
+    @Override
+    protected Expression expressionForSimpleData() {
+        return new E(Source.EMPTY);
+    }
+
+    @Override
+    protected Matcher<Object> resultMatcher(List<Object> data) {
+        return equalTo(Math.E);
+    }
+
+    @Override
+    protected String expectedEvaluatorSimpleToString() {
+        return "LiteralsEvaluator[block=2.718281828459045]";
+    }
+
+    @Override
+    protected Expression constantFoldable(List<Object> data) {
+        return expressionForSimpleData();
+    }
+
+    @Override
+    protected Expression build(Source source, List<Literal> args) {
+        return expressionForSimpleData();
+    }
+
+    @Override
+    protected List<ArgumentSpec> argSpec() {
+        return List.of();
+    }
+
+    @Override
+    protected DataType expectedType(List<DataType> argTypes) {
+        return DataTypes.DOUBLE;
+    }
+
+    @Override
+    protected void assertSimpleWithNulls(List<Object> data, Block value, int nullBlock) {
+        assertThat(((DoubleBlock) value).asVector().getDouble(0), equalTo(Math.E));
+    }
+}