diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/AbstractConvertFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/AbstractConvertFunction.java index f04f41813d57..54d9fcb59f47 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/AbstractConvertFunction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/AbstractConvertFunction.java @@ -58,7 +58,7 @@ protected AbstractConvertFunction(Source source, Expression field) { /** * Build the evaluator given the evaluator a multivalued field. */ - protected ExpressionEvaluator.Factory evaluator(ExpressionEvaluator.Factory fieldEval) { + protected final ExpressionEvaluator.Factory evaluator(ExpressionEvaluator.Factory fieldEval) { DataType sourceType = field().dataType(); var factory = factories().get(sourceType); if (factory == null) { @@ -98,6 +98,21 @@ interface BuildFactory { ExpressionEvaluator.Factory build(ExpressionEvaluator.Factory field, Source source); } + /** + * A map from input type to {@link ExpressionEvaluator} ctor. Usually implemented like: + *
{@code
+     *     private static final Map EVALUATORS = Map.ofEntries(
+     *         Map.entry(BOOLEAN, (field, source) -> field),
+     *         Map.entry(KEYWORD, ToBooleanFromStringEvaluator.Factory::new),
+     *         ...
+     *     );
+     *
+     *     @Override
+     *     protected Map factories() {
+     *         return EVALUATORS;
+     *     }
+     * }
+ */ protected abstract Map factories(); @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AbstractTrigonometricFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AbstractTrigonometricFunction.java index 7ec618b621c5..1643d1b21ca5 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AbstractTrigonometricFunction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AbstractTrigonometricFunction.java @@ -28,6 +28,9 @@ abstract class AbstractTrigonometricFunction extends UnaryScalarFunction { super(source, field); } + /** + * Build an evaluator for this function given the evaluator for it's input. + */ protected abstract EvalOperator.ExpressionEvaluator.Factory doubleEvaluator(EvalOperator.ExpressionEvaluator.Factory field); @Override diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java index 3f6ad8ca3ab0..bf0e10f817e3 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java @@ -8,7 +8,7 @@ /** * Functions that take a row of data and produce a row of data without holding * any state between rows. This includes both the {@link org.elasticsearch.xpack.esql.core.expression.function.scalar.ScalarFunction} - * subclass to link into the QL infrastucture and the {@link org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator} + * subclass to link into the ESQL core infrastructure and the {@link org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator} * implementation to run the actual function. * *

Guide to adding new function

@@ -47,7 +47,7 @@ *
  • * Pick one of the csv-spec files in {@code x-pack/plugin/esql/qa/testFixtures/src/main/resources/} * and add a test for the function you want to write. These files are roughly themed but there - * isn't a strong guiding principle in the theme. + * isn't a strong guiding principle in the organization. *
  • *
  • * Rerun the {@code CsvTests} and watch your new test fail. Yay, TDD doing it's job. @@ -56,20 +56,51 @@ * Find a function in this package similar to the one you are working on and copy it to build * yours. There's some ceremony required in each function class to make it constant foldable * and return the right types. Take a stab at these, but don't worry too much about getting - * it right. + * it right. Your function might extend from one of several abstract base classes, all of + * those are fine for this guide, but might have special instructions called out later. + * Known good base classes: + * *
  • *
  • * There are also methods annotated with {@link org.elasticsearch.compute.ann.Evaluator} - * that contain the actual inner implementation of the function. Modify those to look right - * and click {@code Build->Recompile 'FunctionName.java'} in IntelliJ or run the - * {@code CsvTests} again. This should generate an - * {@link org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator} implementation - * calling the method annotated with {@link org.elasticsearch.compute.ann.Evaluator}. Please commit the - * generated evaluator before submitting your PR. - *
  • - * Once your evaluator is generated you can implement - * {@link org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper#toEvaluator}, - * having it return the generated evaluator. + * that contain the actual inner implementation of the function. They are usually named + * "process" or "processInts" or "processBar". Modify those to look right and run the {@code CsvTests} + * again. This should generate an {@link org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator} + * implementation calling the method annotated with {@link org.elasticsearch.compute.ann.Evaluator}. + *. To make it work with IntelliJ, also click {@code Build->Recompile 'FunctionName.java'}. + * Please commit the generated evaluator before submitting your PR. + *

    + * NOTE: The function you copied may have a method annotated with + * {@link org.elasticsearch.compute.ann.ConvertEvaluator} or + * {@link org.elasticsearch.compute.ann.MvEvaluator} instead of + * {@link org.elasticsearch.compute.ann.Evaluator}. Those do similar things and the + * instructions should still work for you regardless. If your function contains an implementation + * of {@link org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator} written by + * hand then please stop and ask for help. This is not a good first function. + *

    + *

    + * NOTE 2: Regardless of which annotation is on your "process" method you can learn more + * about the options for generating code from the javadocs on those annotations. + *

    + *
  • + * Once your evaluator is generated you can have your function return it, + * generally by implementing {@link org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper#toEvaluator}. + * It's possible that your abstract base class implements that function and + * will need you to implement something else: + * *
  • *
  • * Add your function to {@link org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry}. @@ -100,24 +131,32 @@ * {@code ./gradlew -p x-pack/plugin/esql/ test} *
  • *
  • - * Now it's time to write some docs! - * Generate the docs, a syntax diagram and a table with supported types by running the tests via - * gradle: {@code ./gradlew x-pack:plugin:esql:test} + * Now it's time to generate some docs! + * Actually, running the tests in the example above should have done it for you. * The generated files are - *
      + *
        *
      • {@code docs/reference/esql/functions/description/myfunction.asciidoc}
      • *
      • {@code docs/reference/esql/functions/examples/myfunction.asciidoc}
      • *
      • {@code docs/reference/esql/functions/layout/myfunction.asciidoc}
      • *
      • {@code docs/reference/esql/functions/parameters/myfunction.asciidoc}
      • *
      • {@code docs/reference/esql/functions/signature/myfunction.svg}
      • *
      • {@code docs/reference/esql/functions/types/myfunction.asciidoc}
      • - *
    + * * * Make sure to commit them. Add a reference to the * {@code docs/reference/esql/functions/layout/myfunction.asciidoc} in the function list * docs. There are plenty of examples on how * to reference those files e.g. if you are writing a Math function, you will want to * list it in {@code docs/reference/esql/functions/math-functions.asciidoc}. + *

    + * You can generate the docs for just your function by running + * {@code ./gradlew :x-pack:plugin:esql:test -Dtests.class='*SinTests'}. It's just + * running your new unit test. You should see something like: + *

    + *
    {@code
    + *              > Task :x-pack:plugin:esql:test
    + *              ESQL Docs: Only files related to [sin.asciidoc], patching them into place
    + *         }
    *
  • *
  • * Build the docs by cloning the docs repo