diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/function/scalar/BinaryScalarFunction.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/function/scalar/BinaryScalarFunction.java
index f96aeb693b52a..4b462719a375b 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/function/scalar/BinaryScalarFunction.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/function/scalar/BinaryScalarFunction.java
@@ -6,9 +6,14 @@
*/
package org.elasticsearch.xpack.esql.core.expression.function.scalar;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.util.PlanStreamInput;
+import org.elasticsearch.xpack.esql.core.util.PlanStreamOutput;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
@@ -22,6 +27,21 @@ protected BinaryScalarFunction(Source source, Expression left, Expression right)
this.right = right;
}
+ protected BinaryScalarFunction(StreamInput in) throws IOException {
+ this(
+ Source.readFrom((StreamInput & PlanStreamInput) in),
+ ((PlanStreamInput) in).readExpression(),
+ ((PlanStreamInput) in).readExpression()
+ );
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ source().writeTo(out);
+ ((PlanStreamOutput) out).writeExpression(left());
+ ((PlanStreamOutput) out).writeExpression(right());
+ }
+
@Override
public final BinaryScalarFunction replaceChildren(List newChildren) {
Expression newLeft = newChildren.get(0);
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunction.java
index 5aa6dad7b2a5b..9b7e0b729cde9 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunction.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunction.java
@@ -7,6 +7,9 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.operator.DriverContext;
@@ -15,7 +18,12 @@
import org.elasticsearch.core.Releasables;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.util.PlanStreamOutput;
import org.elasticsearch.xpack.esql.expression.function.scalar.UnaryScalarFunction;
+import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
+
+import java.io.IOException;
+import java.util.List;
/**
* Base class for functions that reduce multivalued fields into single valued fields.
@@ -25,10 +33,39 @@
*
*/
public abstract class AbstractMultivalueFunction extends UnaryScalarFunction {
+ public static List getNamedWriteables() {
+ return List.of(
+ MvAppend.ENTRY,
+ MvAvg.ENTRY,
+ MvConcat.ENTRY,
+ MvCount.ENTRY,
+ MvDedupe.ENTRY,
+ MvFirst.ENTRY,
+ MvLast.ENTRY,
+ MvMax.ENTRY,
+ MvMedian.ENTRY,
+ MvMin.ENTRY,
+ MvSlice.ENTRY,
+ MvSort.ENTRY,
+ MvSum.ENTRY,
+ MvZip.ENTRY
+ );
+ }
+
protected AbstractMultivalueFunction(Source source, Expression field) {
super(source, field);
}
+ protected AbstractMultivalueFunction(StreamInput in) throws IOException {
+ this(Source.readFrom((PlanStreamInput) in), ((PlanStreamInput) in).readExpression());
+ }
+
+ @Override
+ public final void writeTo(StreamOutput out) throws IOException {
+ Source.EMPTY.writeTo(out);
+ ((PlanStreamOutput) out).writeExpression(field);
+ }
+
/**
* Build the evaluator given the evaluator a multivalued field.
*/
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java
index 1f37c15ecfc43..99844d40e0565 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppend.java
@@ -8,6 +8,9 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.compute.ann.Evaluator;
import org.elasticsearch.compute.data.BooleanBlock;
import org.elasticsearch.compute.data.BytesRefBlock;
@@ -25,9 +28,11 @@
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
+import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -41,6 +46,8 @@
* Appends values to a multi-value
*/
public class MvAppend extends EsqlScalarFunction implements EvaluatorMapper {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvAppend", MvAppend::new);
+
private final Expression field1, field2;
private DataType dataType;
@@ -103,6 +110,22 @@ public MvAppend(
this.field2 = field2;
}
+ private MvAppend(StreamInput in) throws IOException {
+ this(Source.readFrom((PlanStreamInput) in), ((PlanStreamInput) in).readExpression(), ((PlanStreamInput) in).readExpression());
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ Source.EMPTY.writeTo(out);
+ out.writeNamedWriteable(field1);
+ out.writeNamedWriteable(field2);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveType() {
if (childrenResolved() == false) {
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvg.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvg.java
index 787bf3e5efd1c..01f24365be225 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvg.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvg.java
@@ -7,6 +7,8 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.ann.MvEvaluator;
import org.elasticsearch.compute.operator.EvalOperator;
import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
@@ -21,6 +23,7 @@
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
+import java.io.IOException;
import java.util.List;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
@@ -31,6 +34,8 @@
* Reduce a multivalued field to a single valued field containing the average value.
*/
public class MvAvg extends AbstractMultivalueFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvAvg", MvAvg::new);
+
@FunctionInfo(
returnType = "double",
description = "Converts a multivalued field into a single valued field containing the average of all of the values.",
@@ -47,6 +52,15 @@ public MvAvg(
super(source, field);
}
+ private MvAvg(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveFieldType() {
return isType(field(), t -> t.isNumeric() && isRepresentable(t), sourceText(), null, "numeric");
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcat.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcat.java
index 3e37a739147cf..fa9475055515f 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcat.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcat.java
@@ -9,6 +9,8 @@
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.data.BytesRefBlock;
import org.elasticsearch.compute.data.Page;
@@ -25,6 +27,7 @@
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.Param;
+import java.io.IOException;
import java.util.function.Function;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
@@ -33,6 +36,8 @@
* Reduce a multivalued string field to a single valued field by concatenating all values.
*/
public class MvConcat extends BinaryScalarFunction implements EvaluatorMapper {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvConcat", MvConcat::new);
+
@FunctionInfo(
returnType = "keyword",
description = "Converts a multivalued string expression into a single valued column "
@@ -53,6 +58,15 @@ public MvConcat(
super(source, field, delim);
}
+ private MvConcat(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveType() {
if (childrenResolved() == false) {
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCount.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCount.java
index b2afef4f2235e..faf7d36e4a24c 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCount.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCount.java
@@ -7,6 +7,8 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.operator.DriverContext;
import org.elasticsearch.compute.operator.EvalOperator;
@@ -20,6 +22,7 @@
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
+import java.io.IOException;
import java.util.List;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
@@ -28,6 +31,8 @@
* Reduce a multivalued field to a single valued field containing the count of values.
*/
public class MvCount extends AbstractMultivalueFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvCount", MvCount::new);
+
@FunctionInfo(
returnType = "integer",
description = "Converts a multivalued expression into a single valued column containing a count of the number of values.",
@@ -58,6 +63,15 @@ public MvCount(
super(source, v);
}
+ private MvCount(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveFieldType() {
return isType(field(), EsqlDataTypes::isRepresentable, sourceText(), null, "representable");
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java
index 71cf759b3dbe5..d17bc26ab808b 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupe.java
@@ -7,6 +7,8 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
import org.elasticsearch.compute.operator.mvdedupe.MultivalueDedupe;
import org.elasticsearch.xpack.esql.core.expression.Expression;
@@ -18,6 +20,7 @@
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
+import java.io.IOException;
import java.util.List;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
@@ -26,6 +29,8 @@
* Removes duplicate values from a multivalued field.
*/
public class MvDedupe extends AbstractMultivalueFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvDedupe", MvDedupe::new);
+
// @TODO: add unsigned_long
@FunctionInfo(
returnType = {
@@ -70,6 +75,15 @@ public MvDedupe(
super(source, field);
}
+ private MvDedupe(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveFieldType() {
return isType(field(), EsqlDataTypes::isRepresentable, sourceText(), null, "representable");
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java
index a985c10824ae7..25e6a85a485c1 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirst.java
@@ -8,6 +8,8 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.ann.MvEvaluator;
import org.elasticsearch.compute.data.BooleanBlock;
import org.elasticsearch.compute.data.BytesRefBlock;
@@ -26,6 +28,7 @@
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
+import java.io.IOException;
import java.util.List;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
@@ -34,6 +37,8 @@
* Reduce a multivalued field to a single valued field containing the minimum value.
*/
public class MvFirst extends AbstractMultivalueFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvFirst", MvFirst::new);
+
@FunctionInfo(
returnType = {
"boolean",
@@ -87,6 +92,15 @@ public MvFirst(
super(source, field);
}
+ private MvFirst(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveFieldType() {
return isType(field(), EsqlDataTypes::isRepresentable, sourceText(), null, "representable");
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java
index 8dcc4c8b1222e..2a9a498ecf9d3 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLast.java
@@ -8,6 +8,8 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.ann.MvEvaluator;
import org.elasticsearch.compute.data.BooleanBlock;
import org.elasticsearch.compute.data.BytesRefBlock;
@@ -26,6 +28,7 @@
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
+import java.io.IOException;
import java.util.List;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
@@ -34,6 +37,8 @@
* Reduce a multivalued field to a single valued field containing the minimum value.
*/
public class MvLast extends AbstractMultivalueFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvLast", MvLast::new);
+
@FunctionInfo(
returnType = {
"boolean",
@@ -87,6 +92,15 @@ public MvLast(
super(source, field);
}
+ private MvLast(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveFieldType() {
return isType(field(), EsqlDataTypes::isRepresentable, sourceText(), null, "representable");
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMax.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMax.java
index 7cfc4a94b35d4..24873cc1da2e9 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMax.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMax.java
@@ -8,6 +8,8 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.ann.MvEvaluator;
import org.elasticsearch.compute.operator.EvalOperator;
import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
@@ -20,6 +22,7 @@
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
+import java.io.IOException;
import java.util.List;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
@@ -30,6 +33,8 @@
* Reduce a multivalued field to a single valued field containing the maximum value.
*/
public class MvMax extends AbstractMultivalueFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvMax", MvMax::new);
+
@FunctionInfo(
returnType = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "text", "unsigned_long", "version" },
description = "Converts a multivalued expression into a single valued column containing the maximum value.",
@@ -53,6 +58,15 @@ public MvMax(
super(source, v);
}
+ private MvMax(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveFieldType() {
return isType(field(), t -> isSpatial(t) == false && isRepresentable(t), sourceText(), null, "representableNonSpatial");
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedian.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedian.java
index 8d3177926f2e6..4e7d6dd4e29b2 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedian.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedian.java
@@ -8,6 +8,8 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
import org.apache.lucene.util.ArrayUtil;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.ann.MvEvaluator;
import org.elasticsearch.compute.data.DoubleBlock;
import org.elasticsearch.compute.data.IntBlock;
@@ -23,6 +25,7 @@
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
+import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
@@ -36,6 +39,8 @@
* Reduce a multivalued field to a single valued field containing the average value.
*/
public class MvMedian extends AbstractMultivalueFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvMedian", MvMedian::new);
+
@FunctionInfo(
returnType = { "double", "integer", "long", "unsigned_long" },
description = "Converts a multivalued field into a single valued field containing the median value.",
@@ -60,6 +65,15 @@ public MvMedian(
super(source, field);
}
+ private MvMedian(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveFieldType() {
return isType(field(), t -> t.isNumeric() && isRepresentable(t), sourceText(), null, "numeric");
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMin.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMin.java
index e52e72c766a3d..205a09953fde3 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMin.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMin.java
@@ -8,6 +8,8 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.ann.MvEvaluator;
import org.elasticsearch.compute.operator.EvalOperator;
import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
@@ -20,6 +22,7 @@
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
+import java.io.IOException;
import java.util.List;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
@@ -30,6 +33,8 @@
* Reduce a multivalued field to a single valued field containing the minimum value.
*/
public class MvMin extends AbstractMultivalueFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvMin", MvMin::new);
+
@FunctionInfo(
returnType = { "boolean", "date", "double", "integer", "ip", "keyword", "long", "text", "unsigned_long", "version" },
description = "Converts a multivalued expression into a single valued column containing the minimum value.",
@@ -53,6 +58,15 @@ public MvMin(
super(source, field);
}
+ private MvMin(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveFieldType() {
return isType(field(), t -> isSpatial(t) == false && isRepresentable(t), sourceText(), null, "representableNonSpatial");
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java
index 40e9f90df9dc6..f824d0821cfbf 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSlice.java
@@ -8,6 +8,9 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.compute.ann.Evaluator;
import org.elasticsearch.compute.data.BooleanBlock;
import org.elasticsearch.compute.data.BytesRefBlock;
@@ -23,14 +26,17 @@
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.core.util.PlanStreamOutput;
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
import org.elasticsearch.xpack.esql.expression.function.Example;
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
+import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@@ -46,6 +52,8 @@
* Returns a subset of the multivalued field using the start and end index values.
*/
public class MvSlice extends EsqlScalarFunction implements OptionalArgument, EvaluatorMapper {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvSlice", MvSlice::new);
+
private final Expression field, start, end;
@FunctionInfo(
@@ -103,7 +111,43 @@ public MvSlice(
super(source, end == null ? Arrays.asList(field, start, start) : Arrays.asList(field, start, end));
this.field = field;
this.start = start;
- this.end = end == null ? start : end;
+ this.end = end;
+ }
+
+ private MvSlice(StreamInput in) throws IOException {
+ this(
+ Source.readFrom((PlanStreamInput) in),
+ ((PlanStreamInput) in).readExpression(),
+ ((PlanStreamInput) in).readExpression(),
+ // TODO readOptionalNamedWriteable
+ in.readOptionalWriteable(i -> ((PlanStreamInput) i).readExpression())
+ );
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ Source.EMPTY.writeTo(out);
+ ((PlanStreamOutput) out).writeExpression(field);
+ ((PlanStreamOutput) out).writeExpression(start);
+ // TODO writeOptionalNamedWriteable
+ out.writeOptionalWriteable(end == null ? null : o -> ((PlanStreamOutput) o).writeExpression(end));
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
+ Expression field() {
+ return field;
+ }
+
+ Expression start() {
+ return start;
+ }
+
+ Expression end() {
+ return end;
}
@Override
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java
index 744491b30f702..fd5f493ae405e 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSort.java
@@ -9,6 +9,9 @@
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.TriFunction;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.data.BlockFactory;
import org.elasticsearch.compute.data.BooleanBlock;
@@ -33,13 +36,16 @@
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.core.util.PlanStreamOutput;
import org.elasticsearch.xpack.esql.expression.function.Example;
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
+import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@@ -54,6 +60,8 @@
* Sorts a multivalued field in lexicographical order.
*/
public class MvSort extends EsqlScalarFunction implements OptionalArgument, Validatable {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvSort", MvSort::new);
+
private final Expression field, order;
private static final Literal ASC = new Literal(Source.EMPTY, "ASC", DataType.KEYWORD);
@@ -79,7 +87,37 @@ public MvSort(
) {
super(source, order == null ? Arrays.asList(field, ASC) : Arrays.asList(field, order));
this.field = field;
- this.order = order == null ? ASC : order;
+ this.order = order;
+ }
+
+ private MvSort(StreamInput in) throws IOException {
+ this(
+ Source.readFrom((PlanStreamInput) in),
+ ((PlanStreamInput) in).readExpression(),
+ // TODO readOptionalNamedWriteable
+ in.readOptionalWriteable(i -> ((PlanStreamInput) i).readExpression())
+ );
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ source().writeTo(out);
+ ((PlanStreamOutput) out).writeExpression(field);
+ // TODO writeOptionalNamedWriteable
+ out.writeOptionalWriteable(order == null ? null : o -> ((PlanStreamOutput) o).writeExpression(order));
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
+ Expression field() {
+ return field;
+ }
+
+ Expression order() {
+ return order;
}
@Override
@@ -93,6 +131,9 @@ protected TypeResolution resolveType() {
if (resolution.unresolved()) {
return resolution;
}
+ if (order == null) {
+ return resolution;
+ }
return isString(order, sourceText(), SECOND);
}
@@ -106,7 +147,10 @@ public boolean foldable() {
public EvalOperator.ExpressionEvaluator.Factory toEvaluator(
Function toEvaluator
) {
- boolean ordering = order.foldable() && ((BytesRef) order.fold()).utf8ToString().equalsIgnoreCase("DESC") ? false : true;
+ Expression nonNullOrder = order == null ? ASC : order;
+ boolean ordering = nonNullOrder.foldable() && ((BytesRef) nonNullOrder.fold()).utf8ToString().equalsIgnoreCase("DESC")
+ ? false
+ : true;
return switch (PlannerUtils.toElementType(field.dataType())) {
case BOOLEAN -> new MvSort.EvaluatorFactory(
toEvaluator.apply(field),
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSum.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSum.java
index e14bc401a058a..eabf5e20ad1b0 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSum.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSum.java
@@ -7,6 +7,8 @@
package org.elasticsearch.xpack.esql.expression.function.scalar.multivalue;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.compute.ann.MvEvaluator;
import org.elasticsearch.compute.operator.EvalOperator;
import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
@@ -21,6 +23,7 @@
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.planner.PlannerUtils;
+import java.io.IOException;
import java.util.List;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
@@ -31,6 +34,8 @@
* Reduce a multivalued field to a single valued field containing the sum of all values.
*/
public class MvSum extends AbstractMultivalueFunction {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvSum", MvSum::new);
+
@FunctionInfo(
returnType = { "double", "integer", "long", "unsigned_long" },
description = "Converts a multivalued field into a single valued field containing the sum of all of the values.",
@@ -47,6 +52,15 @@ public MvSum(
super(source, field);
}
+ private MvSum(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
@Override
protected TypeResolution resolveFieldType() {
return isType(field(), t -> t.isNumeric() && isRepresentable(t), sourceText(), null, "numeric");
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZip.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZip.java
index 4f42858cbedba..15bd09a4089e6 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZip.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZip.java
@@ -9,6 +9,9 @@
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.compute.ann.Evaluator;
import org.elasticsearch.compute.data.BytesRefBlock;
import org.elasticsearch.compute.operator.EvalOperator;
@@ -19,12 +22,15 @@
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.core.util.PlanStreamOutput;
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
import org.elasticsearch.xpack.esql.expression.function.Example;
import org.elasticsearch.xpack.esql.expression.function.FunctionInfo;
import org.elasticsearch.xpack.esql.expression.function.Param;
import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction;
+import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@@ -38,6 +44,8 @@
* Combines the values from two multivalued fields with a delimiter that joins them together.
*/
public class MvZip extends EsqlScalarFunction implements OptionalArgument, EvaluatorMapper {
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "MvZip", MvZip::new);
+
private final Expression mvLeft, mvRight, delim;
private static final Literal COMMA = new Literal(Source.EMPTY, ",", DataType.TEXT);
@@ -60,7 +68,31 @@ public MvZip(
super(source, delim == null ? Arrays.asList(mvLeft, mvRight, COMMA) : Arrays.asList(mvLeft, mvRight, delim));
this.mvLeft = mvLeft;
this.mvRight = mvRight;
- this.delim = delim == null ? COMMA : delim;
+ this.delim = delim;
+ }
+
+ private MvZip(StreamInput in) throws IOException {
+ this(
+ Source.readFrom((PlanStreamInput) in),
+ ((PlanStreamInput) in).readExpression(),
+ ((PlanStreamInput) in).readExpression(),
+ // TODO readOptionalNamedWriteable
+ in.readOptionalWriteable(i -> ((PlanStreamInput) i).readExpression())
+ );
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ Source.EMPTY.writeTo(out);
+ ((PlanStreamOutput) out).writeExpression(mvLeft);
+ ((PlanStreamOutput) out).writeExpression(mvRight);
+ // TODO writeOptionalNamedWriteable
+ out.writeOptionalWriteable(delim == null ? null : o -> ((PlanStreamOutput) o).writeExpression(delim));
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
}
@Override
@@ -104,7 +136,12 @@ public Nullability nullable() {
public EvalOperator.ExpressionEvaluator.Factory toEvaluator(
Function toEvaluator
) {
- return new MvZipEvaluator.Factory(source(), toEvaluator.apply(mvLeft), toEvaluator.apply(mvRight), toEvaluator.apply(delim));
+ return new MvZipEvaluator.Factory(
+ source(),
+ toEvaluator.apply(mvLeft),
+ toEvaluator.apply(mvRight),
+ toEvaluator.apply(delim == null ? COMMA : delim)
+ );
}
@Override
@@ -195,4 +232,16 @@ static void process(BytesRefBlock.Builder builder, int position, BytesRefBlock l
}
builder.endPositionEntry();
}
+
+ Expression mvLeft() {
+ return mvLeft;
+ }
+
+ Expression mvRight() {
+ return mvRight;
+ }
+
+ Expression delim() {
+ return delim;
+ }
}
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/EsqlArithmeticOperation.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/EsqlArithmeticOperation.java
index 89931d7a6f4d1..7ab6d96181f53 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/EsqlArithmeticOperation.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/EsqlArithmeticOperation.java
@@ -20,7 +20,6 @@
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Cast;
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
-import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput;
import org.elasticsearch.xpack.esql.type.EsqlDataTypeRegistry;
import java.io.IOException;
@@ -127,13 +126,6 @@ public interface BinaryEvaluator {
);
}
- @Override
- public void writeTo(StreamOutput out) throws IOException {
- source().writeTo(out);
- ((PlanStreamOutput) out).writeExpression(left());
- ((PlanStreamOutput) out).writeExpression(right());
- }
-
@Override
public Object fold() {
return EvaluatorMapper.super.fold();
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InsensitiveBinaryComparison.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InsensitiveBinaryComparison.java
index 137723de24edd..1ce87094e50b3 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InsensitiveBinaryComparison.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/InsensitiveBinaryComparison.java
@@ -7,13 +7,10 @@
package org.elasticsearch.xpack.esql.expression.predicate.operator.comparison;
import org.elasticsearch.common.io.stream.StreamInput;
-import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.function.scalar.BinaryScalarFunction;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
-import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput;
import java.io.IOException;
@@ -24,14 +21,7 @@ protected InsensitiveBinaryComparison(Source source, Expression left, Expression
}
protected InsensitiveBinaryComparison(StreamInput in) throws IOException {
- this(Source.readFrom((PlanStreamInput) in), ((PlanStreamInput) in).readExpression(), ((PlanStreamInput) in).readExpression());
- }
-
- @Override
- public void writeTo(StreamOutput out) throws IOException {
- source().writeTo(out);
- ((PlanStreamOutput) out).writeExpression(left());
- ((PlanStreamOutput) out).writeExpression(right());
+ super(in);
}
@Override
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java
index 59cbfca89112f..be5e105c3398e 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/io/stream/PlanNamedTypes.java
@@ -81,20 +81,6 @@
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Round;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Tau;
import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.AbstractMultivalueFunction;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvAppend;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvAvg;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvConcat;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvCount;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvDedupe;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvFirst;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvLast;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvMax;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvMedian;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvMin;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvSlice;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvSort;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvSum;
-import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvZip;
import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce;
import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialContains;
import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialDisjoint;
@@ -312,27 +298,13 @@ public static List namedTypeEntries() {
of(AggregateFunction.class, Percentile.class, PlanNamedTypes::writePercentile, PlanNamedTypes::readPercentile),
of(AggregateFunction.class, SpatialCentroid.class, PlanNamedTypes::writeAggFunction, PlanNamedTypes::readAggFunction),
of(AggregateFunction.class, Sum.class, PlanNamedTypes::writeAggFunction, PlanNamedTypes::readAggFunction),
- of(AggregateFunction.class, Values.class, PlanNamedTypes::writeAggFunction, PlanNamedTypes::readAggFunction),
- // Multivalue functions
- of(ScalarFunction.class, MvAppend.class, PlanNamedTypes::writeMvAppend, PlanNamedTypes::readMvAppend),
- of(ScalarFunction.class, MvAvg.class, PlanNamedTypes::writeMvFunction, PlanNamedTypes::readMvFunction),
- of(ScalarFunction.class, MvCount.class, PlanNamedTypes::writeMvFunction, PlanNamedTypes::readMvFunction),
- of(ScalarFunction.class, MvConcat.class, PlanNamedTypes::writeMvConcat, PlanNamedTypes::readMvConcat),
- of(ScalarFunction.class, MvDedupe.class, PlanNamedTypes::writeMvFunction, PlanNamedTypes::readMvFunction),
- of(ScalarFunction.class, MvFirst.class, PlanNamedTypes::writeMvFunction, PlanNamedTypes::readMvFunction),
- of(ScalarFunction.class, MvLast.class, PlanNamedTypes::writeMvFunction, PlanNamedTypes::readMvFunction),
- of(ScalarFunction.class, MvMax.class, PlanNamedTypes::writeMvFunction, PlanNamedTypes::readMvFunction),
- of(ScalarFunction.class, MvMedian.class, PlanNamedTypes::writeMvFunction, PlanNamedTypes::readMvFunction),
- of(ScalarFunction.class, MvMin.class, PlanNamedTypes::writeMvFunction, PlanNamedTypes::readMvFunction),
- of(ScalarFunction.class, MvSort.class, PlanNamedTypes::writeMvSort, PlanNamedTypes::readMvSort),
- of(ScalarFunction.class, MvSlice.class, PlanNamedTypes::writeMvSlice, PlanNamedTypes::readMvSlice),
- of(ScalarFunction.class, MvSum.class, PlanNamedTypes::writeMvFunction, PlanNamedTypes::readMvFunction),
- of(ScalarFunction.class, MvZip.class, PlanNamedTypes::writeMvZip, PlanNamedTypes::readMvZip)
+ of(AggregateFunction.class, Values.class, PlanNamedTypes::writeAggFunction, PlanNamedTypes::readAggFunction)
);
List entries = new ArrayList<>(declared);
// From NamedWriteables
for (List ee : List.of(
+ AbstractMultivalueFunction.getNamedWriteables(),
EsqlArithmeticOperation.getNamedWriteables(),
EsqlBinaryComparison.getNamedWriteables(),
FullTextPredicate.getNamedWriteables(),
@@ -1429,38 +1401,6 @@ static void writeAggFunction(PlanStreamOutput out, AggregateFunction aggregateFu
out.writeExpression(aggregateFunction.field());
}
- // -- Multivalue functions
- static final Map> MV_CTRS = Map.ofEntries(
- entry(name(MvAvg.class), MvAvg::new),
- entry(name(MvCount.class), MvCount::new),
- entry(name(MvDedupe.class), MvDedupe::new),
- entry(name(MvFirst.class), MvFirst::new),
- entry(name(MvLast.class), MvLast::new),
- entry(name(MvMax.class), MvMax::new),
- entry(name(MvMedian.class), MvMedian::new),
- entry(name(MvMin.class), MvMin::new),
- entry(name(MvSum.class), MvSum::new)
- );
-
- static AbstractMultivalueFunction readMvFunction(PlanStreamInput in, String name) throws IOException {
- return MV_CTRS.get(name).apply(Source.readFrom(in), in.readExpression());
- }
-
- static void writeMvFunction(PlanStreamOutput out, AbstractMultivalueFunction fn) throws IOException {
- Source.EMPTY.writeTo(out);
- out.writeExpression(fn.field());
- }
-
- static MvConcat readMvConcat(PlanStreamInput in) throws IOException {
- return new MvConcat(Source.readFrom(in), in.readExpression(), in.readExpression());
- }
-
- static void writeMvConcat(PlanStreamOutput out, MvConcat fn) throws IOException {
- Source.EMPTY.writeTo(out);
- out.writeExpression(fn.left());
- out.writeExpression(fn.right());
- }
-
// -- ancillary supporting classes of plan nodes, etc
static EsQueryExec.FieldSort readFieldSort(PlanStreamInput in) throws IOException {
@@ -1514,54 +1454,4 @@ static void writeLog(PlanStreamOutput out, Log log) throws IOException {
out.writeExpression(fields.get(0));
out.writeOptionalWriteable(fields.size() == 2 ? o -> out.writeExpression(fields.get(1)) : null);
}
-
- static MvSort readMvSort(PlanStreamInput in) throws IOException {
- return new MvSort(Source.readFrom(in), in.readExpression(), in.readOptionalNamed(Expression.class));
- }
-
- static void writeMvSort(PlanStreamOutput out, MvSort mvSort) throws IOException {
- mvSort.source().writeTo(out);
- List fields = mvSort.children();
- assert fields.size() == 1 || fields.size() == 2;
- out.writeExpression(fields.get(0));
- out.writeOptionalWriteable(fields.size() == 2 ? o -> out.writeExpression(fields.get(1)) : null);
- }
-
- static MvSlice readMvSlice(PlanStreamInput in) throws IOException {
- return new MvSlice(Source.readFrom(in), in.readExpression(), in.readExpression(), in.readOptionalNamed(Expression.class));
- }
-
- static void writeMvSlice(PlanStreamOutput out, MvSlice fn) throws IOException {
- Source.EMPTY.writeTo(out);
- List fields = fn.children();
- assert fields.size() == 2 || fields.size() == 3;
- out.writeExpression(fields.get(0));
- out.writeExpression(fields.get(1));
- out.writeOptionalWriteable(fields.size() == 3 ? o -> out.writeExpression(fields.get(2)) : null);
- }
-
- static MvZip readMvZip(PlanStreamInput in) throws IOException {
- return new MvZip(Source.readFrom(in), in.readExpression(), in.readExpression(), in.readOptionalNamed(Expression.class));
- }
-
- static void writeMvZip(PlanStreamOutput out, MvZip fn) throws IOException {
- Source.EMPTY.writeTo(out);
- List fields = fn.children();
- assert fields.size() == 2 || fields.size() == 3;
- out.writeExpression(fields.get(0));
- out.writeExpression(fields.get(1));
- out.writeOptionalWriteable(fields.size() == 3 ? o -> out.writeExpression(fields.get(2)) : null);
- }
-
- static MvAppend readMvAppend(PlanStreamInput in) throws IOException {
- return new MvAppend(Source.readFrom(in), in.readExpression(), in.readExpression());
- }
-
- static void writeMvAppend(PlanStreamOutput out, MvAppend fn) throws IOException {
- Source.EMPTY.writeTo(out);
- List fields = fn.children();
- assert fields.size() == 2;
- out.writeExpression(fields.get(0));
- out.writeExpression(fields.get(1));
- }
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/AbstractExpressionSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/AbstractExpressionSerializationTests.java
index 5a794c3ff7730..9b33af9f0a2e0 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/AbstractExpressionSerializationTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/AbstractExpressionSerializationTests.java
@@ -22,6 +22,7 @@
import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput;
import org.elasticsearch.xpack.esql.session.EsqlConfiguration;
import org.elasticsearch.xpack.esql.session.EsqlConfigurationSerializationTests;
+import org.junit.Before;
import java.io.IOException;
import java.util.ArrayList;
@@ -32,6 +33,12 @@
import static org.hamcrest.Matchers.sameInstance;
public abstract class AbstractExpressionSerializationTests extends AbstractWireTestCase {
+ /**
+ * We use a single random config for all serialization because it's pretty
+ * heavy to build, especially in {@link #testConcurrentSerialization()}.
+ */
+ private EsqlConfiguration config;
+
public static Source randomSource() {
int lineNumber = between(0, EXAMPLE_QUERY.length - 1);
int offset = between(0, EXAMPLE_QUERY[lineNumber].length() - 2);
@@ -46,7 +53,6 @@ public static Expression randomChild() {
@Override
protected final T copyInstance(T instance, TransportVersion version) throws IOException {
- EsqlConfiguration config = EsqlConfigurationSerializationTests.randomConfiguration(String.join("\n", EXAMPLE_QUERY), Map.of());
return copyInstance(
instance,
getNamedWriteableRegistry(),
@@ -91,4 +97,9 @@ protected final NamedWriteableRegistry getNamedWriteableRegistry() {
"I understand equations, both the simple and quadratical,",
"About binomial theorem I'm teeming with a lot o' news,",
"With many cheerful facts about the square of the hypotenuse." };
+
+ @Before
+ public void initConfig() {
+ config = EsqlConfigurationSerializationTests.randomConfiguration(String.join("\n", EXAMPLE_QUERY), Map.of());
+ }
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMvSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMvSerializationTests.java
new file mode 100644
index 0000000000000..fba33c9ea1c03
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMvSerializationTests.java
@@ -0,0 +1,21 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.util.List;
+
+public abstract class AbstractMvSerializationTests extends AbstractExpressionSerializationTests {
+ @Override
+ protected List getNamedWriteables() {
+ return AbstractMultivalueFunction.getNamedWriteables();
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendSerializationTests.java
new file mode 100644
index 0000000000000..8afd1b44dc3f3
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendSerializationTests.java
@@ -0,0 +1,42 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvAppendSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvAppend createTestInstance() {
+ Source source = randomSource();
+ Expression field1 = randomChild();
+ Expression field2 = randomChild();
+ return new MvAppend(source, field1, field2);
+ }
+
+ @Override
+ protected MvAppend mutateInstance(MvAppend instance) throws IOException {
+ Source source = randomSource();
+ Expression field1 = randomChild();
+ Expression field2 = randomChild();
+ if (randomBoolean()) {
+ field1 = randomValueOtherThan(field1, AbstractExpressionSerializationTests::randomChild);
+ } else {
+ field2 = randomValueOtherThan(field2, AbstractExpressionSerializationTests::randomChild);
+ }
+ return new MvAppend(source, field1, field2);
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgSerializationTests.java
new file mode 100644
index 0000000000000..f70702b001492
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAvgSerializationTests.java
@@ -0,0 +1,29 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvAvgSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvAvg createTestInstance() {
+ return new MvAvg(randomSource(), randomChild());
+ }
+
+ @Override
+ protected MvAvg mutateInstance(MvAvg instance) throws IOException {
+ return new MvAvg(instance.source(), randomValueOtherThan(instance.field(), AbstractExpressionSerializationTests::randomChild));
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatSerializationTests.java
new file mode 100644
index 0000000000000..9f2aba8d9d9ca
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatSerializationTests.java
@@ -0,0 +1,37 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvConcatSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvConcat createTestInstance() {
+ Source source = randomSource();
+ Expression left = randomChild();
+ Expression right = randomChild();
+ return new MvConcat(source, left, right);
+ }
+
+ @Override
+ protected MvConcat mutateInstance(MvConcat instance) throws IOException {
+ Source source = instance.source();
+ Expression left = instance.left();
+ Expression right = instance.right();
+ if (randomBoolean()) {
+ left = randomValueOtherThan(left, AbstractExpressionSerializationTests::randomChild);
+ } else {
+ right = randomValueOtherThan(right, AbstractExpressionSerializationTests::randomChild);
+ }
+ return new MvConcat(source, left, right);
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountSerializationTests.java
new file mode 100644
index 0000000000000..a0d28a6cf925b
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvCountSerializationTests.java
@@ -0,0 +1,29 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvCountSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvCount createTestInstance() {
+ return new MvCount(randomSource(), randomChild());
+ }
+
+ @Override
+ protected MvCount mutateInstance(MvCount instance) throws IOException {
+ return new MvCount(instance.source(), randomValueOtherThan(instance.field(), AbstractExpressionSerializationTests::randomChild));
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeSerializationTests.java
new file mode 100644
index 0000000000000..afb2ec90e1e3e
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvDedupeSerializationTests.java
@@ -0,0 +1,29 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvDedupeSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvDedupe createTestInstance() {
+ return new MvDedupe(randomSource(), randomChild());
+ }
+
+ @Override
+ protected MvDedupe mutateInstance(MvDedupe instance) throws IOException {
+ return new MvDedupe(instance.source(), randomValueOtherThan(instance.field(), AbstractExpressionSerializationTests::randomChild));
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstSerializationTests.java
new file mode 100644
index 0000000000000..dbb49bb96a663
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvFirstSerializationTests.java
@@ -0,0 +1,29 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvFirstSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvFirst createTestInstance() {
+ return new MvFirst(randomSource(), randomChild());
+ }
+
+ @Override
+ protected MvFirst mutateInstance(MvFirst instance) throws IOException {
+ return new MvFirst(instance.source(), randomValueOtherThan(instance.field(), AbstractExpressionSerializationTests::randomChild));
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastSerializationTests.java
new file mode 100644
index 0000000000000..190eb0263c162
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvLastSerializationTests.java
@@ -0,0 +1,29 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvLastSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvLast createTestInstance() {
+ return new MvLast(randomSource(), randomChild());
+ }
+
+ @Override
+ protected MvLast mutateInstance(MvLast instance) throws IOException {
+ return new MvLast(instance.source(), randomValueOtherThan(instance.field(), AbstractExpressionSerializationTests::randomChild));
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxSerializationTests.java
new file mode 100644
index 0000000000000..ffc51af5f103d
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxSerializationTests.java
@@ -0,0 +1,29 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvMaxSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvMax createTestInstance() {
+ return new MvMax(randomSource(), randomChild());
+ }
+
+ @Override
+ protected MvMax mutateInstance(MvMax instance) throws IOException {
+ return new MvMax(instance.source(), randomValueOtherThan(instance.field(), AbstractExpressionSerializationTests::randomChild));
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianSerializationTests.java
new file mode 100644
index 0000000000000..067cc6430ce01
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianSerializationTests.java
@@ -0,0 +1,29 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvMedianSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvMedian createTestInstance() {
+ return new MvMedian(randomSource(), randomChild());
+ }
+
+ @Override
+ protected MvMedian mutateInstance(MvMedian instance) throws IOException {
+ return new MvMedian(instance.source(), randomValueOtherThan(instance.field(), AbstractExpressionSerializationTests::randomChild));
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinSerializationTests.java
new file mode 100644
index 0000000000000..1f38587274353
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinSerializationTests.java
@@ -0,0 +1,29 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvMinSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvMin createTestInstance() {
+ return new MvMin(randomSource(), randomChild());
+ }
+
+ @Override
+ protected MvMin mutateInstance(MvMin instance) throws IOException {
+ return new MvMin(instance.source(), randomValueOtherThan(instance.field(), AbstractExpressionSerializationTests::randomChild));
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceSerializationTests.java
new file mode 100644
index 0000000000000..64209ce0f4644
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceSerializationTests.java
@@ -0,0 +1,44 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvSliceSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvSlice createTestInstance() {
+ Source source = randomSource();
+ Expression field = randomChild();
+ Expression start = randomChild();
+ Expression end = randomBoolean() ? null : randomChild();
+ return new MvSlice(source, field, start, end);
+ }
+
+ @Override
+ protected MvSlice mutateInstance(MvSlice instance) throws IOException {
+ Source source = instance.source();
+ Expression field = instance.field();
+ Expression start = instance.start();
+ Expression end = instance.end();
+ switch (between(0, 2)) {
+ case 0 -> field = randomValueOtherThan(field, AbstractExpressionSerializationTests::randomChild);
+ case 1 -> start = randomValueOtherThan(start, AbstractExpressionSerializationTests::randomChild);
+ case 2 -> end = randomValueOtherThan(end, () -> randomBoolean() ? null : randomChild());
+ }
+ return new MvSlice(source, field, start, end);
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortSerializationTests.java
new file mode 100644
index 0000000000000..1728ad6f09357
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortSerializationTests.java
@@ -0,0 +1,37 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvSortSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvSort createTestInstance() {
+ Source source = randomSource();
+ Expression field = randomChild();
+ Expression order = randomBoolean() ? null : randomChild();
+ return new MvSort(source, field, order);
+ }
+
+ @Override
+ protected MvSort mutateInstance(MvSort instance) throws IOException {
+ Source source = instance.source();
+ Expression field = instance.field();
+ Expression order = instance.order();
+ if (randomBoolean()) {
+ field = randomValueOtherThan(field, AbstractExpressionSerializationTests::randomChild);
+ } else {
+ order = randomValueOtherThan(order, () -> randomBoolean() ? null : randomChild());
+ }
+ return new MvSort(source, field, order);
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSumSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSumSerializationTests.java
new file mode 100644
index 0000000000000..e8ddcc9340b45
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSumSerializationTests.java
@@ -0,0 +1,29 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvSumSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvSum createTestInstance() {
+ return new MvSum(randomSource(), randomChild());
+ }
+
+ @Override
+ protected MvSum mutateInstance(MvSum instance) throws IOException {
+ return new MvSum(instance.source(), randomValueOtherThan(instance.field(), AbstractExpressionSerializationTests::randomChild));
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipSerializationTests.java
new file mode 100644
index 0000000000000..d16ca02627b29
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipSerializationTests.java
@@ -0,0 +1,44 @@
+/*
+ * 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.multivalue;
+
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.AbstractExpressionSerializationTests;
+
+import java.io.IOException;
+
+public class MvZipSerializationTests extends AbstractMvSerializationTests {
+ @Override
+ protected MvZip createTestInstance() {
+ Source source = randomSource();
+ Expression mvLeft = randomChild();
+ Expression mvRight = randomChild();
+ Expression delim = randomBoolean() ? null : randomChild();
+ return new MvZip(source, mvLeft, mvRight, delim);
+ }
+
+ @Override
+ protected MvZip mutateInstance(MvZip instance) throws IOException {
+ Source source = instance.source();
+ Expression mvLeft = instance.mvLeft();
+ Expression mvRight = instance.mvRight();
+ Expression delim = instance.delim();
+ switch (between(0, 2)) {
+ case 0 -> mvLeft = randomValueOtherThan(mvLeft, AbstractExpressionSerializationTests::randomChild);
+ case 1 -> mvRight = randomValueOtherThan(mvRight, AbstractExpressionSerializationTests::randomChild);
+ case 2 -> delim = randomValueOtherThan(delim, () -> randomBoolean() ? null : randomChild());
+ }
+ return new MvZip(source, mvLeft, mvRight, delim);
+ }
+
+ @Override
+ protected boolean alwaysEmptySource() {
+ return true;
+ }
+}