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:
+ *
+ * - {@link org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction}
+ * - {@link org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.AbstractMultivalueFunction}
+ * - {@link org.elasticsearch.xpack.esql.expression.function.scalar.UnaryScalarFunction}
+ * or any subclass like {@code AbstractTrigonometricFunction}
+ * - {@link org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction}
+ * - {@link org.elasticsearch.xpack.esql.expression.function.scalar.math.DoubleConstantFunction}
+ *
*
*
* 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:
+ *
+ * - {@link org.elasticsearch.xpack.esql.expression.function.scalar.convert.AbstractConvertFunction}: {@code factories}
+ * - {@link org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.AbstractMultivalueFunction}:
+ * {@code evaluator}
+ * - {@code AbstractTrigonometricFunction}: {@code doubleEvaluator}
+ * - {@link org.elasticsearch.xpack.esql.expression.function.scalar.math.DoubleConstantFunction}: nothing!
+ *
*
*
* 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