diff --git a/.gitattributes b/.gitattributes
index ebe1a34db347..6a8de5462ec3 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -8,3 +8,8 @@ x-pack/plugin/esql/src/main/antlr/*.tokens linguist-generated=true
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/*.interp linguist-generated=true
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseLexer*.java linguist-generated=true
x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/EsqlBaseParser*.java linguist-generated=true
+x-pack/plugin/esql/src/main/generated/** linguist-generated=true
+
+# ESQL functions docs are autogenerated. More information at `docs/reference/esql/functions/README.md`
+docs/reference/esql/functions/*/** linguist-generated=true
+
diff --git a/docs/changelog/109070.yaml b/docs/changelog/109070.yaml
new file mode 100644
index 000000000000..8dbc0ec1c6cf
--- /dev/null
+++ b/docs/changelog/109070.yaml
@@ -0,0 +1,6 @@
+pr: 109070
+summary: "ESQL: Add `ip_prefix` function"
+area: ES|QL
+type: feature
+issues:
+ - 99064
diff --git a/docs/reference/esql/functions/description/ip_prefix.asciidoc b/docs/reference/esql/functions/description/ip_prefix.asciidoc
new file mode 100644
index 000000000000..4b7a88486dea
--- /dev/null
+++ b/docs/reference/esql/functions/description/ip_prefix.asciidoc
@@ -0,0 +1,5 @@
+// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+*Description*
+
+Truncates an IP to a given prefix length.
diff --git a/docs/reference/esql/functions/examples/ip_prefix.asciidoc b/docs/reference/esql/functions/examples/ip_prefix.asciidoc
new file mode 100644
index 000000000000..19f0ed266afb
--- /dev/null
+++ b/docs/reference/esql/functions/examples/ip_prefix.asciidoc
@@ -0,0 +1,13 @@
+// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+*Example*
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/ip.csv-spec[tag=ipPrefix]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/ip.csv-spec[tag=ipPrefix-result]
+|===
+
diff --git a/docs/reference/esql/functions/kibana/definition/ip_prefix.json b/docs/reference/esql/functions/kibana/definition/ip_prefix.json
new file mode 100644
index 000000000000..00c3cf75a949
--- /dev/null
+++ b/docs/reference/esql/functions/kibana/definition/ip_prefix.json
@@ -0,0 +1,35 @@
+{
+ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
+ "type" : "eval",
+ "name" : "ip_prefix",
+ "description" : "Truncates an IP to a given prefix length.",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "ip",
+ "type" : "ip",
+ "optional" : false,
+ "description" : "IP address of type `ip` (both IPv4 and IPv6 are supported)."
+ },
+ {
+ "name" : "prefixLengthV4",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Prefix length for IPv4 addresses."
+ },
+ {
+ "name" : "prefixLengthV6",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Prefix length for IPv6 addresses."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "ip"
+ }
+ ],
+ "examples" : [
+ "row ip4 = to_ip(\"1.2.3.4\"), ip6 = to_ip(\"fe80::cae2:65ff:fece:feb9\")\n| eval ip4_prefix = ip_prefix(ip4, 24, 0), ip6_prefix = ip_prefix(ip6, 0, 112);"
+ ]
+}
diff --git a/docs/reference/esql/functions/kibana/docs/ip_prefix.md b/docs/reference/esql/functions/kibana/docs/ip_prefix.md
new file mode 100644
index 000000000000..5c0009528bb6
--- /dev/null
+++ b/docs/reference/esql/functions/kibana/docs/ip_prefix.md
@@ -0,0 +1,11 @@
+
+
+### IP_PREFIX
+Truncates an IP to a given prefix length.
+
+```
+row ip4 = to_ip("1.2.3.4"), ip6 = to_ip("fe80::cae2:65ff:fece:feb9")
+| eval ip4_prefix = ip_prefix(ip4, 24, 0), ip6_prefix = ip_prefix(ip6, 0, 112);
+```
diff --git a/docs/reference/esql/functions/layout/ip_prefix.asciidoc b/docs/reference/esql/functions/layout/ip_prefix.asciidoc
new file mode 100644
index 000000000000..ca51c871daf7
--- /dev/null
+++ b/docs/reference/esql/functions/layout/ip_prefix.asciidoc
@@ -0,0 +1,15 @@
+// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+[discrete]
+[[esql-ip_prefix]]
+=== `IP_PREFIX`
+
+*Syntax*
+
+[.text-center]
+image::esql/functions/signature/ip_prefix.svg[Embedded,opts=inline]
+
+include::../parameters/ip_prefix.asciidoc[]
+include::../description/ip_prefix.asciidoc[]
+include::../types/ip_prefix.asciidoc[]
+include::../examples/ip_prefix.asciidoc[]
diff --git a/docs/reference/esql/functions/parameters/ip_prefix.asciidoc b/docs/reference/esql/functions/parameters/ip_prefix.asciidoc
new file mode 100644
index 000000000000..945601c2476e
--- /dev/null
+++ b/docs/reference/esql/functions/parameters/ip_prefix.asciidoc
@@ -0,0 +1,12 @@
+// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+*Parameters*
+
+`ip`::
+IP address of type `ip` (both IPv4 and IPv6 are supported).
+
+`prefixLengthV4`::
+Prefix length for IPv4 addresses.
+
+`prefixLengthV6`::
+Prefix length for IPv6 addresses.
diff --git a/docs/reference/esql/functions/signature/ip_prefix.svg b/docs/reference/esql/functions/signature/ip_prefix.svg
new file mode 100644
index 000000000000..4699c2335746
--- /dev/null
+++ b/docs/reference/esql/functions/signature/ip_prefix.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/esql/functions/types/ip_prefix.asciidoc b/docs/reference/esql/functions/types/ip_prefix.asciidoc
new file mode 100644
index 000000000000..786d99d45d32
--- /dev/null
+++ b/docs/reference/esql/functions/types/ip_prefix.asciidoc
@@ -0,0 +1,9 @@
+// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+*Supported types*
+
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+ip | prefixLengthV4 | prefixLengthV6 | result
+ip | integer | integer | ip
+|===
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestUtils.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestUtils.java
index 1927cfd03ac0..7d1c168bd203 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestUtils.java
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/CsvTestUtils.java
@@ -101,7 +101,7 @@ public final class CsvTestUtils {
Map pairs = extractInstructions(testName);
String versionRange = pairs.get("skip");
if (versionRange != null) {
- String[] skipVersions = versionRange.split("-");
+ String[] skipVersions = versionRange.split("-", Integer.MAX_VALUE);
if (skipVersions.length != 2) {
throw new IllegalArgumentException("malformed version range : " + versionRange);
}
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/ip.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/ip.csv-spec
index ae683acbb2c3..0de64d3e2d9d 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/ip.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/ip.csv-spec
@@ -485,3 +485,108 @@ beta | 127.0.0.1
beta | 127.0.0.1
beta | 127.0.0.1
;
+
+ipPrefix
+required_capability: fn_ip_prefix
+//tag::ipPrefix[]
+row ip4 = to_ip("1.2.3.4"), ip6 = to_ip("fe80::cae2:65ff:fece:feb9")
+| eval ip4_prefix = ip_prefix(ip4, 24, 0), ip6_prefix = ip_prefix(ip6, 0, 112);
+//end::ipPrefix[]
+
+//tag::ipPrefix-result[]
+ip4:ip | ip6:ip | ip4_prefix:ip | ip6_prefix:ip
+1.2.3.4 | fe80::cae2:65ff:fece:feb9 | 1.2.3.0 | fe80::cae2:65ff:fece:0000
+//end::ipPrefix-result[]
+;
+
+ipPrefixCompleteIp
+required_capability: fn_ip_prefix
+row ip4 = to_ip("1.2.3.4"), ip6 = to_ip("fe80::cae2:65ff:fece:feb9")
+| eval ip4_prefix = ip_prefix(ip4, 32, 0), ip6_prefix = ip_prefix(ip6, 0, 128);
+
+ip4:ip | ip6:ip | ip4_prefix:ip | ip6_prefix:ip
+1.2.3.4 | fe80::cae2:65ff:fece:feb9 | 1.2.3.4 | fe80::cae2:65ff:fece:feb9
+;
+
+ipPrefixZeroBits
+required_capability: fn_ip_prefix
+row ip4 = to_ip("1.2.3.4"), ip6 = to_ip("fe80::cae2:65ff:fece:feb9")
+| eval ip4_prefix = ip_prefix(ip4, 0, 128), ip6_prefix = ip_prefix(ip6, 32, 0);
+
+ip4:ip | ip6:ip | ip4_prefix:ip | ip6_prefix:ip
+1.2.3.4 | fe80::cae2:65ff:fece:feb9 | 0.0.0.0 | ::0
+;
+
+ipPrefixWithBits
+required_capability: fn_ip_prefix
+row ip4 = to_ip("1.2.3.255"), ip6 = to_ip("fe80::cae2:65ff:fece:feff")
+| eval ip4_prefix = ip_prefix(ip4, 25, 0), ip6_prefix = ip_prefix(ip6, 0, 121);
+
+ip4:ip | ip6:ip | ip4_prefix:ip | ip6_prefix:ip
+1.2.3.255 | fe80::cae2:65ff:fece:feff | 1.2.3.128 | fe80::cae2:65ff:fece:fe80
+;
+
+ipPrefixLengthFromColumn
+required_capability: fn_ip_prefix
+from hosts
+| where host == "alpha"
+| sort card
+| eval prefix = ip_prefix(ip0, 24, 128)
+| keep card, ip0, prefix;
+
+card:keyword | ip0:ip | prefix:ip
+eth0 | 127.0.0.1 | 127.0.0.0
+eth1 | ::1 | ::0
+;
+
+ipPrefixLengthFromExpression
+required_capability: fn_ip_prefix
+row ip4 = to_ip("1.2.3.4"), ip6 = to_ip("fe80::cae2:65ff:fece:feb9"), bits_per_byte = 8
+| eval ip4_length = 3 * bits_per_byte, ip4_prefix = ip_prefix(ip4, ip4_length, 0), ip6_prefix = ip_prefix(ip6, 0, 12 * 10);
+
+ip4:ip | ip6:ip | bits_per_byte:integer | ip4_length:integer | ip4_prefix:ip | ip6_prefix:ip
+1.2.3.4 | fe80::cae2:65ff:fece:feb9 | 8 | 24 | 1.2.3.0 | fe80::cae2:65ff:fece:fe00
+;
+
+ipPrefixAsGroup
+required_capability: fn_ip_prefix
+from hosts
+| stats count(*) by ip_prefix(ip1, 24, 120)
+| sort `ip_prefix(ip1, 24, 120)`;
+warning:Line 2:21: evaluation of [ip_prefix(ip1, 24, 120)] failed, treating result as null. Only first 20 failures recorded.
+warning:Line 2:21: java.lang.IllegalArgumentException: single-value function encountered multi-value
+
+count(*):long | ip_prefix(ip1, 24, 120):ip
+2 | ::0
+3 | 127.0.0.0
+1 | 128.0.0.0
+1 | fe80::cae2:65ff:fece:fe00
+1 | fe81::cae2:65ff:fece:fe00
+2 | null
+;
+
+ipPrefixWithWrongLengths
+required_capability: fn_ip_prefix
+row ip4 = to_ip("1.2.3.4")
+| eval a = ip_prefix(ip4, -1, 128), b = ip_prefix(ip4, 32, -1), c = ip_prefix(ip4, 33, 0), d = ip_prefix(ip4, 32, 129);
+warning:Line 2:12: evaluation of [ip_prefix(ip4, -1, 128)] failed, treating result as null. Only first 20 failures recorded.
+warning:Line 2:12: java.lang.IllegalArgumentException: Prefix length v4 must be in range [0, 32], found -1
+warning:Line 2:41: evaluation of [ip_prefix(ip4, 32, -1)] failed, treating result as null. Only first 20 failures recorded.
+warning:Line 2:41: java.lang.IllegalArgumentException: Prefix length v6 must be in range [0, 128], found -1
+warning:Line 2:69: evaluation of [ip_prefix(ip4, 33, 0)] failed, treating result as null. Only first 20 failures recorded.
+warning:Line 2:69: java.lang.IllegalArgumentException: Prefix length v4 must be in range [0, 32], found 33
+warning:Line 2:96: evaluation of [ip_prefix(ip4, 32, 129)] failed, treating result as null. Only first 20 failures recorded.
+warning:Line 2:96: java.lang.IllegalArgumentException: Prefix length v6 must be in range [0, 128], found 129
+
+ip4:ip | a:ip | b:ip | c:ip | d:ip
+1.2.3.4 | null | null | null | null
+;
+
+ipPrefixWithNullArguments
+required_capability: fn_ip_prefix
+row ip4 = to_ip("1.2.3.4")
+| eval a = ip_prefix(null, 32, 128), b = ip_prefix(ip4, null, 128), c = ip_prefix(ip4, 32, null);
+
+ip4:ip | a:ip | b:ip | c:ip
+1.2.3.4 | null | null | null
+;
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec
index f68dd15e9c51..eff4cb05bd8c 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/meta.csv-spec
@@ -30,6 +30,7 @@ double e()
"double|integer|long|unsigned_long floor(number:double|integer|long|unsigned_long)"
"keyword from_base64(string:keyword|text)"
"boolean|double|integer|ip|keyword|long|text|version greatest(first:boolean|double|integer|ip|keyword|long|text|version, ?rest...:boolean|double|integer|ip|keyword|long|text|version)"
+"ip ip_prefix(ip:ip, prefixLengthV4:integer, prefixLengthV6:integer)"
"boolean|double|integer|ip|keyword|long|text|version least(first:boolean|double|integer|ip|keyword|long|text|version, ?rest...:boolean|double|integer|ip|keyword|long|text|version)"
"keyword left(string:keyword|text, length:integer)"
"integer length(string:keyword|text)"
@@ -144,6 +145,7 @@ ends_with |[str, suffix] |["keyword|text", "keyword|te
floor |number |"double|integer|long|unsigned_long" |Numeric expression. If `null`, the function returns `null`.
from_base64 |string |"keyword|text" |A base64 string.
greatest |first |"boolean|double|integer|ip|keyword|long|text|version" |First of the columns to evaluate.
+ip_prefix |[ip, prefixLengthV4, prefixLengthV6]|[ip, integer, integer] |[IP address of type `ip` (both IPv4 and IPv6 are supported)., Prefix length for IPv4 addresses., Prefix length for IPv6 addresses.]
least |first |"boolean|double|integer|ip|keyword|long|text|version" |First of the columns to evaluate.
left |[string, length] |["keyword|text", integer] |[The string from which to return a substring., The number of characters to return.]
length |string |"keyword|text" |String expression. If `null`, the function returns `null`.
@@ -259,6 +261,7 @@ ends_with |Returns a boolean that indicates whether a keyword string ends wi
floor |Round a number down to the nearest integer.
from_base64 |Decode a base64 string.
greatest |Returns the maximum value from multiple columns. This is similar to <> except it is intended to run on multiple columns at once.
+ip_prefix |Truncates an IP to a given prefix length.
least |Returns the minimum value from multiple columns. This is similar to <> except it is intended to run on multiple columns at once.
left |Returns the substring that extracts 'length' chars from 'string' starting from the left.
length |Returns the character length of a string.
@@ -375,6 +378,7 @@ ends_with |boolean
floor |"double|integer|long|unsigned_long" |false |false |false
from_base64 |keyword |false |false |false
greatest |"boolean|double|integer|ip|keyword|long|text|version" |false |true |false
+ip_prefix |ip |[false, false, false] |false |false
least |"boolean|double|integer|ip|keyword|long|text|version" |false |true |false
left |keyword |[false, false] |false |false
length |integer |false |false |false
@@ -471,5 +475,5 @@ countFunctions#[skip:-8.14.99, reason:BIN added]
meta functions | stats a = count(*), b = count(*), c = count(*) | mv_expand c;
a:long | b:long | c:long
-106 | 106 | 106
+107 | 107 | 107
;
diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefixEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefixEvaluator.java
new file mode 100644
index 000000000000..174df48d5ce6
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefixEvaluator.java
@@ -0,0 +1,183 @@
+// 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.ip;
+
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
+import java.lang.String;
+import java.util.function.Function;
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.BytesRefBlock;
+import org.elasticsearch.compute.data.BytesRefVector;
+import org.elasticsearch.compute.data.IntBlock;
+import org.elasticsearch.compute.data.IntVector;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.function.Warnings;
+
+/**
+ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link IpPrefix}.
+ * This class is generated. Do not edit it.
+ */
+public final class IpPrefixEvaluator implements EvalOperator.ExpressionEvaluator {
+ private final Warnings warnings;
+
+ private final EvalOperator.ExpressionEvaluator ip;
+
+ private final EvalOperator.ExpressionEvaluator prefixLengthV4;
+
+ private final EvalOperator.ExpressionEvaluator prefixLengthV6;
+
+ private final BytesRef scratch;
+
+ private final DriverContext driverContext;
+
+ public IpPrefixEvaluator(Source source, EvalOperator.ExpressionEvaluator ip,
+ EvalOperator.ExpressionEvaluator prefixLengthV4,
+ EvalOperator.ExpressionEvaluator prefixLengthV6, BytesRef scratch,
+ DriverContext driverContext) {
+ this.warnings = new Warnings(source);
+ this.ip = ip;
+ this.prefixLengthV4 = prefixLengthV4;
+ this.prefixLengthV6 = prefixLengthV6;
+ this.scratch = scratch;
+ this.driverContext = driverContext;
+ }
+
+ @Override
+ public Block eval(Page page) {
+ try (BytesRefBlock ipBlock = (BytesRefBlock) ip.eval(page)) {
+ try (IntBlock prefixLengthV4Block = (IntBlock) prefixLengthV4.eval(page)) {
+ try (IntBlock prefixLengthV6Block = (IntBlock) prefixLengthV6.eval(page)) {
+ BytesRefVector ipVector = ipBlock.asVector();
+ if (ipVector == null) {
+ return eval(page.getPositionCount(), ipBlock, prefixLengthV4Block, prefixLengthV6Block);
+ }
+ IntVector prefixLengthV4Vector = prefixLengthV4Block.asVector();
+ if (prefixLengthV4Vector == null) {
+ return eval(page.getPositionCount(), ipBlock, prefixLengthV4Block, prefixLengthV6Block);
+ }
+ IntVector prefixLengthV6Vector = prefixLengthV6Block.asVector();
+ if (prefixLengthV6Vector == null) {
+ return eval(page.getPositionCount(), ipBlock, prefixLengthV4Block, prefixLengthV6Block);
+ }
+ return eval(page.getPositionCount(), ipVector, prefixLengthV4Vector, prefixLengthV6Vector);
+ }
+ }
+ }
+ }
+
+ public BytesRefBlock eval(int positionCount, BytesRefBlock ipBlock, IntBlock prefixLengthV4Block,
+ IntBlock prefixLengthV6Block) {
+ try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) {
+ BytesRef ipScratch = new BytesRef();
+ position: for (int p = 0; p < positionCount; p++) {
+ if (ipBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (ipBlock.getValueCount(p) != 1) {
+ if (ipBlock.getValueCount(p) > 1) {
+ warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ if (prefixLengthV4Block.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (prefixLengthV4Block.getValueCount(p) != 1) {
+ if (prefixLengthV4Block.getValueCount(p) > 1) {
+ warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ if (prefixLengthV6Block.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (prefixLengthV6Block.getValueCount(p) != 1) {
+ if (prefixLengthV6Block.getValueCount(p) > 1) {
+ warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ try {
+ result.appendBytesRef(IpPrefix.process(ipBlock.getBytesRef(ipBlock.getFirstValueIndex(p), ipScratch), prefixLengthV4Block.getInt(prefixLengthV4Block.getFirstValueIndex(p)), prefixLengthV6Block.getInt(prefixLengthV6Block.getFirstValueIndex(p)), scratch));
+ } catch (IllegalArgumentException e) {
+ warnings.registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ public BytesRefBlock eval(int positionCount, BytesRefVector ipVector,
+ IntVector prefixLengthV4Vector, IntVector prefixLengthV6Vector) {
+ try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) {
+ BytesRef ipScratch = new BytesRef();
+ position: for (int p = 0; p < positionCount; p++) {
+ try {
+ result.appendBytesRef(IpPrefix.process(ipVector.getBytesRef(p, ipScratch), prefixLengthV4Vector.getInt(p), prefixLengthV6Vector.getInt(p), scratch));
+ } catch (IllegalArgumentException e) {
+ warnings.registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "IpPrefixEvaluator[" + "ip=" + ip + ", prefixLengthV4=" + prefixLengthV4 + ", prefixLengthV6=" + prefixLengthV6 + "]";
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(ip, prefixLengthV4, prefixLengthV6);
+ }
+
+ static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory ip;
+
+ private final EvalOperator.ExpressionEvaluator.Factory prefixLengthV4;
+
+ private final EvalOperator.ExpressionEvaluator.Factory prefixLengthV6;
+
+ private final Function scratch;
+
+ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory ip,
+ EvalOperator.ExpressionEvaluator.Factory prefixLengthV4,
+ EvalOperator.ExpressionEvaluator.Factory prefixLengthV6,
+ Function scratch) {
+ this.source = source;
+ this.ip = ip;
+ this.prefixLengthV4 = prefixLengthV4;
+ this.prefixLengthV6 = prefixLengthV6;
+ this.scratch = scratch;
+ }
+
+ @Override
+ public IpPrefixEvaluator get(DriverContext context) {
+ return new IpPrefixEvaluator(source, ip.get(context), prefixLengthV4.get(context), prefixLengthV6.get(context), scratch.apply(context), context);
+ }
+
+ @Override
+ public String toString() {
+ return "IpPrefixEvaluator[" + "ip=" + ip + ", prefixLengthV4=" + prefixLengthV4 + ", prefixLengthV6=" + prefixLengthV6 + "]";
+ }
+ }
+}
diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefixOnlyV4Evaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefixOnlyV4Evaluator.java
new file mode 100644
index 000000000000..a6cb7c7f9b68
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefixOnlyV4Evaluator.java
@@ -0,0 +1,148 @@
+// 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.ip;
+
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
+import java.lang.String;
+import java.util.function.Function;
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.BytesRefBlock;
+import org.elasticsearch.compute.data.BytesRefVector;
+import org.elasticsearch.compute.data.IntBlock;
+import org.elasticsearch.compute.data.IntVector;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.function.Warnings;
+
+/**
+ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link IpPrefix}.
+ * This class is generated. Do not edit it.
+ */
+public final class IpPrefixOnlyV4Evaluator implements EvalOperator.ExpressionEvaluator {
+ private final Warnings warnings;
+
+ private final EvalOperator.ExpressionEvaluator ip;
+
+ private final EvalOperator.ExpressionEvaluator prefixLengthV4;
+
+ private final BytesRef scratch;
+
+ private final DriverContext driverContext;
+
+ public IpPrefixOnlyV4Evaluator(Source source, EvalOperator.ExpressionEvaluator ip,
+ EvalOperator.ExpressionEvaluator prefixLengthV4, BytesRef scratch,
+ DriverContext driverContext) {
+ this.warnings = new Warnings(source);
+ this.ip = ip;
+ this.prefixLengthV4 = prefixLengthV4;
+ this.scratch = scratch;
+ this.driverContext = driverContext;
+ }
+
+ @Override
+ public Block eval(Page page) {
+ try (BytesRefBlock ipBlock = (BytesRefBlock) ip.eval(page)) {
+ try (IntBlock prefixLengthV4Block = (IntBlock) prefixLengthV4.eval(page)) {
+ BytesRefVector ipVector = ipBlock.asVector();
+ if (ipVector == null) {
+ return eval(page.getPositionCount(), ipBlock, prefixLengthV4Block);
+ }
+ IntVector prefixLengthV4Vector = prefixLengthV4Block.asVector();
+ if (prefixLengthV4Vector == null) {
+ return eval(page.getPositionCount(), ipBlock, prefixLengthV4Block);
+ }
+ return eval(page.getPositionCount(), ipVector, prefixLengthV4Vector).asBlock();
+ }
+ }
+ }
+
+ public BytesRefBlock eval(int positionCount, BytesRefBlock ipBlock,
+ IntBlock prefixLengthV4Block) {
+ try(BytesRefBlock.Builder result = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) {
+ BytesRef ipScratch = new BytesRef();
+ position: for (int p = 0; p < positionCount; p++) {
+ if (ipBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (ipBlock.getValueCount(p) != 1) {
+ if (ipBlock.getValueCount(p) > 1) {
+ warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ if (prefixLengthV4Block.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (prefixLengthV4Block.getValueCount(p) != 1) {
+ if (prefixLengthV4Block.getValueCount(p) > 1) {
+ warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ result.appendBytesRef(IpPrefix.process(ipBlock.getBytesRef(ipBlock.getFirstValueIndex(p), ipScratch), prefixLengthV4Block.getInt(prefixLengthV4Block.getFirstValueIndex(p)), scratch));
+ }
+ return result.build();
+ }
+ }
+
+ public BytesRefVector eval(int positionCount, BytesRefVector ipVector,
+ IntVector prefixLengthV4Vector) {
+ try(BytesRefVector.Builder result = driverContext.blockFactory().newBytesRefVectorBuilder(positionCount)) {
+ BytesRef ipScratch = new BytesRef();
+ position: for (int p = 0; p < positionCount; p++) {
+ result.appendBytesRef(IpPrefix.process(ipVector.getBytesRef(p, ipScratch), prefixLengthV4Vector.getInt(p), scratch));
+ }
+ return result.build();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "IpPrefixOnlyV4Evaluator[" + "ip=" + ip + ", prefixLengthV4=" + prefixLengthV4 + "]";
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(ip, prefixLengthV4);
+ }
+
+ static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory ip;
+
+ private final EvalOperator.ExpressionEvaluator.Factory prefixLengthV4;
+
+ private final Function scratch;
+
+ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory ip,
+ EvalOperator.ExpressionEvaluator.Factory prefixLengthV4,
+ Function scratch) {
+ this.source = source;
+ this.ip = ip;
+ this.prefixLengthV4 = prefixLengthV4;
+ this.scratch = scratch;
+ }
+
+ @Override
+ public IpPrefixOnlyV4Evaluator get(DriverContext context) {
+ return new IpPrefixOnlyV4Evaluator(source, ip.get(context), prefixLengthV4.get(context), scratch.apply(context), context);
+ }
+
+ @Override
+ public String toString() {
+ return "IpPrefixOnlyV4Evaluator[" + "ip=" + ip + ", prefixLengthV4=" + prefixLengthV4 + "]";
+ }
+ }
+}
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
index ca90b9e1e3f2..e8f136c297ce 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java
@@ -27,6 +27,11 @@ public class EsqlCapabilities {
*/
private static final String FN_CBRT = "fn_cbrt";
+ /**
+ * Support for function {@code IP_PREFIX}.
+ */
+ private static final String FN_IP_PREFIX = "fn_ip_prefix";
+
/**
* Optimization for ST_CENTROID changed some results in cartesian data. #108713
*/
@@ -47,6 +52,7 @@ public class EsqlCapabilities {
private static Set capabilities() {
List caps = new ArrayList<>();
caps.add(FN_CBRT);
+ caps.add(FN_IP_PREFIX);
caps.add(ST_CENTROID_AGG_OPTIMIZED);
caps.add(METADATA_IGNORED_FIELD);
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
index 90f28b263f07..382b02d2f9b3 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
@@ -52,6 +52,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateParse;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateTrunc;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.Now;
import org.elasticsearch.xpack.esql.expression.function.scalar.ip.CIDRMatch;
+import org.elasticsearch.xpack.esql.expression.function.scalar.ip.IpPrefix;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Abs;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Acos;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Asin;
@@ -257,6 +258,7 @@ public final class EsqlFunctionRegistry extends FunctionRegistry {
new FunctionDefinition[] { def(Coalesce.class, Coalesce::new, "coalesce"), },
// IP
new FunctionDefinition[] { def(CIDRMatch.class, CIDRMatch::new, "cidr_match") },
+ new FunctionDefinition[] { def(IpPrefix.class, IpPrefix::new, "ip_prefix") },
// conversion functions
new FunctionDefinition[] {
def(FromBase64.class, FromBase64::new, "from_base64"),
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefix.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefix.java
new file mode 100644
index 000000000000..d271742aef82
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefix.java
@@ -0,0 +1,191 @@
+/*
+ * 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.ip;
+
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.compute.ann.Evaluator;
+import org.elasticsearch.compute.ann.Fixed;
+import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.Expressions;
+import org.elasticsearch.xpack.esql.core.expression.function.OptionalArgument;
+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.type.DataTypes;
+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.io.stream.PlanStreamOutput;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.FIRST;
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND;
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.THIRD;
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isIPAndExact;
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
+import static org.elasticsearch.xpack.esql.core.type.DataTypes.INTEGER;
+
+/**
+ * Truncates an IP value to a given prefix length.
+ */
+public class IpPrefix extends EsqlScalarFunction implements OptionalArgument {
+ // Borrowed from Lucene, rfc4291 prefix
+ private static final byte[] IPV4_PREFIX = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1 };
+
+ private final Expression ipField;
+ private final Expression prefixLengthV4Field;
+ private final Expression prefixLengthV6Field;
+
+ @FunctionInfo(
+ returnType = "ip",
+ description = "Truncates an IP to a given prefix length.",
+ examples = @Example(file = "ip", tag = "ipPrefix")
+ )
+ public IpPrefix(
+ Source source,
+ @Param(
+ name = "ip",
+ type = { "ip" },
+ description = "IP address of type `ip` (both IPv4 and IPv6 are supported)."
+ ) Expression ipField,
+ @Param(
+ name = "prefixLengthV4",
+ type = { "integer" },
+ description = "Prefix length for IPv4 addresses."
+ ) Expression prefixLengthV4Field,
+ @Param(
+ name = "prefixLengthV6",
+ type = { "integer" },
+ description = "Prefix length for IPv6 addresses."
+ ) Expression prefixLengthV6Field
+ ) {
+ super(source, Arrays.asList(ipField, prefixLengthV4Field, prefixLengthV6Field));
+ this.ipField = ipField;
+ this.prefixLengthV4Field = prefixLengthV4Field;
+ this.prefixLengthV6Field = prefixLengthV6Field;
+ }
+
+ public static IpPrefix readFrom(PlanStreamInput in) throws IOException {
+ return new IpPrefix(in.readSource(), in.readExpression(), in.readExpression(), in.readExpression());
+ }
+
+ public static void writeTo(PlanStreamOutput out, IpPrefix ipPrefix) throws IOException {
+ out.writeSource(ipPrefix.source());
+ List fields = ipPrefix.children();
+ assert fields.size() == 3;
+ out.writeExpression(fields.get(0));
+ out.writeExpression(fields.get(1));
+ out.writeExpression(fields.get(2));
+ }
+
+ public Expression ipField() {
+ return ipField;
+ }
+
+ public Expression prefixLengthV4Field() {
+ return prefixLengthV4Field;
+ }
+
+ public Expression prefixLengthV6Field() {
+ return prefixLengthV6Field;
+ }
+
+ @Override
+ public boolean foldable() {
+ return Expressions.foldable(children());
+ }
+
+ @Override
+ public ExpressionEvaluator.Factory toEvaluator(Function toEvaluator) {
+ var ipEvaluatorSupplier = toEvaluator.apply(ipField);
+ var prefixLengthV4EvaluatorSupplier = toEvaluator.apply(prefixLengthV4Field);
+ var prefixLengthV6EvaluatorSupplier = toEvaluator.apply(prefixLengthV6Field);
+
+ return new IpPrefixEvaluator.Factory(
+ source(),
+ ipEvaluatorSupplier,
+ prefixLengthV4EvaluatorSupplier,
+ prefixLengthV6EvaluatorSupplier,
+ context -> new BytesRef(new byte[16])
+ );
+ }
+
+ @Evaluator(warnExceptions = IllegalArgumentException.class)
+ static BytesRef process(
+ BytesRef ip,
+ int prefixLengthV4,
+ int prefixLengthV6,
+ @Fixed(includeInToString = false, build = true) BytesRef scratch
+ ) {
+ if (prefixLengthV4 < 0 || prefixLengthV4 > 32) {
+ throw new IllegalArgumentException("Prefix length v4 must be in range [0, 32], found " + prefixLengthV4);
+ }
+ if (prefixLengthV6 < 0 || prefixLengthV6 > 128) {
+ throw new IllegalArgumentException("Prefix length v6 must be in range [0, 128], found " + prefixLengthV6);
+ }
+
+ boolean isIpv4 = Arrays.compareUnsigned(ip.bytes, 0, IPV4_PREFIX.length, IPV4_PREFIX, 0, IPV4_PREFIX.length) == 0;
+
+ if (isIpv4) {
+ makePrefix(ip, scratch, 12 + prefixLengthV4 / 8, prefixLengthV4 % 8);
+ } else {
+ makePrefix(ip, scratch, prefixLengthV6 / 8, prefixLengthV6 % 8);
+ }
+
+ return scratch;
+ }
+
+ private static void makePrefix(BytesRef ip, BytesRef scratch, int fullBytes, int remainingBits) {
+ // Copy the first full bytes
+ System.arraycopy(ip.bytes, ip.offset, scratch.bytes, 0, fullBytes);
+
+ // Copy the last byte ignoring the trailing bits
+ if (remainingBits > 0) {
+ byte lastByteMask = (byte) (0xFF << (8 - remainingBits));
+ scratch.bytes[fullBytes] = (byte) (ip.bytes[fullBytes] & lastByteMask);
+ }
+
+ // Copy the last empty bytes
+ if (fullBytes < 16) {
+ Arrays.fill(scratch.bytes, fullBytes + 1, 16, (byte) 0);
+ }
+ }
+
+ @Override
+ public DataType dataType() {
+ return DataTypes.IP;
+ }
+
+ @Override
+ protected TypeResolution resolveType() {
+ if (childrenResolved() == false) {
+ return new TypeResolution("Unresolved children");
+ }
+
+ return isIPAndExact(ipField, sourceText(), FIRST).and(
+ isType(prefixLengthV4Field, dt -> dt == INTEGER, sourceText(), SECOND, "integer")
+ ).and(isType(prefixLengthV6Field, dt -> dt == INTEGER, sourceText(), THIRD, "integer"));
+ }
+
+ @Override
+ public Expression replaceChildren(List newChildren) {
+ return new IpPrefix(source(), newChildren.get(0), newChildren.get(1), newChildren.get(2));
+ }
+
+ @Override
+ protected NodeInfo extends Expression> info() {
+ return NodeInfo.create(this, IpPrefix::new, ipField, prefixLengthV4Field, prefixLengthV6Field);
+ }
+}
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 e846360258eb..67a6a5d4bb98 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
@@ -98,6 +98,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateParse;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateTrunc;
import org.elasticsearch.xpack.esql.expression.function.scalar.date.Now;
import org.elasticsearch.xpack.esql.expression.function.scalar.ip.CIDRMatch;
+import org.elasticsearch.xpack.esql.expression.function.scalar.ip.IpPrefix;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Abs;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Acos;
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Asin;
@@ -394,6 +395,7 @@ public final class PlanNamedTypes {
of(ScalarFunction.class, DateTrunc.class, PlanNamedTypes::writeDateTrunc, PlanNamedTypes::readDateTrunc),
of(ScalarFunction.class, E.class, PlanNamedTypes::writeNoArgScalar, PlanNamedTypes::readNoArgScalar),
of(ScalarFunction.class, Greatest.class, PlanNamedTypes::writeVararg, PlanNamedTypes::readVarag),
+ of(ScalarFunction.class, IpPrefix.class, IpPrefix::writeTo, IpPrefix::readFrom),
of(ScalarFunction.class, Least.class, PlanNamedTypes::writeVararg, PlanNamedTypes::readVarag),
of(ScalarFunction.class, Log.class, PlanNamedTypes::writeLog, PlanNamedTypes::readLog),
of(ScalarFunction.class, Now.class, PlanNamedTypes::writeNow, PlanNamedTypes::readNow),
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefixTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefixTests.java
new file mode 100644
index 000000000000..e46eaea849bb
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/IpPrefixTests.java
@@ -0,0 +1,116 @@
+/*
+ * 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.ip;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
+import org.elasticsearch.common.network.InetAddresses;
+import org.elasticsearch.common.network.NetworkAddress;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.type.DataTypes;
+import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class IpPrefixTests extends AbstractFunctionTestCase {
+ public IpPrefixTests(@Name("TestCase") Supplier testCaseSupplier) {
+ this.testCase = testCaseSupplier.get();
+ }
+
+ @ParametersFactory
+ public static Iterable