diff --git a/docs/changelog/115792.yaml b/docs/changelog/115792.yaml
new file mode 100644
index 0000000000000..2945a64e3043a
--- /dev/null
+++ b/docs/changelog/115792.yaml
@@ -0,0 +1,5 @@
+pr: 115792
+summary: Add ES|QL `bit_length` function
+area: ES|QL
+type: enhancement
+issues: []
diff --git a/docs/reference/esql/functions/description/bit_length.asciidoc b/docs/reference/esql/functions/description/bit_length.asciidoc
new file mode 100644
index 0000000000000..1aad47488802d
--- /dev/null
+++ b/docs/reference/esql/functions/description/bit_length.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*
+
+Returns the bit length of a string.
diff --git a/docs/reference/esql/functions/examples/bit_length.asciidoc b/docs/reference/esql/functions/examples/bit_length.asciidoc
new file mode 100644
index 0000000000000..a99f6f664e79e
--- /dev/null
+++ b/docs/reference/esql/functions/examples/bit_length.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}/docs.csv-spec[tag=bitLength]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/docs.csv-spec[tag=bitLength-result]
+|===
+
diff --git a/docs/reference/esql/functions/kibana/docs/bit_length.md b/docs/reference/esql/functions/kibana/docs/bit_length.md
new file mode 100644
index 0000000000000..22280febd7876
--- /dev/null
+++ b/docs/reference/esql/functions/kibana/docs/bit_length.md
@@ -0,0 +1,12 @@
+
+
+### BIT_LENGTH
+Returns the bit length of a string.
+
+```
+FROM employees
+| KEEP first_name, last_name
+| EVAL fn_bit_length = BIT_LENGTH(first_name)
+```
diff --git a/docs/reference/esql/functions/layout/bit_length.asciidoc b/docs/reference/esql/functions/layout/bit_length.asciidoc
new file mode 100644
index 0000000000000..00a7206f3ceda
--- /dev/null
+++ b/docs/reference/esql/functions/layout/bit_length.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-bit_length]]
+=== `BIT_LENGTH`
+
+*Syntax*
+
+[.text-center]
+image::esql/functions/signature/bit_length.svg[Embedded,opts=inline]
+
+include::../parameters/bit_length.asciidoc[]
+include::../description/bit_length.asciidoc[]
+include::../types/bit_length.asciidoc[]
+include::../examples/bit_length.asciidoc[]
diff --git a/docs/reference/esql/functions/parameters/bit_length.asciidoc b/docs/reference/esql/functions/parameters/bit_length.asciidoc
new file mode 100644
index 0000000000000..7bb8c080ce4a1
--- /dev/null
+++ b/docs/reference/esql/functions/parameters/bit_length.asciidoc
@@ -0,0 +1,6 @@
+// This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+*Parameters*
+
+`string`::
+String expression. If `null`, the function returns `null`.
diff --git a/docs/reference/esql/functions/signature/bit_length.svg b/docs/reference/esql/functions/signature/bit_length.svg
new file mode 100644
index 0000000000000..904dbbe25c9c2
--- /dev/null
+++ b/docs/reference/esql/functions/signature/bit_length.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/esql/functions/string-functions.asciidoc b/docs/reference/esql/functions/string-functions.asciidoc
index f5222330d579d..422860f0a7a1d 100644
--- a/docs/reference/esql/functions/string-functions.asciidoc
+++ b/docs/reference/esql/functions/string-functions.asciidoc
@@ -8,6 +8,7 @@
{esql} supports these string functions:
// tag::string_list[]
+* <>
* <>
* <>
* <>
@@ -30,6 +31,7 @@
* <>
// end::string_list[]
+include::layout/bit_length.asciidoc[]
include::layout/concat.asciidoc[]
include::layout/ends_with.asciidoc[]
include::layout/from_base64.asciidoc[]
diff --git a/docs/reference/esql/functions/types/bit_length.asciidoc b/docs/reference/esql/functions/types/bit_length.asciidoc
new file mode 100644
index 0000000000000..db5a48c7c4390
--- /dev/null
+++ b/docs/reference/esql/functions/types/bit_length.asciidoc
@@ -0,0 +1,10 @@
+// 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=|]
+|===
+string | result
+keyword | integer
+text | integer
+|===
diff --git a/x-pack/plugin/build.gradle b/x-pack/plugin/build.gradle
index cf6a8f51d1b81..8b920ac11cee7 100644
--- a/x-pack/plugin/build.gradle
+++ b/x-pack/plugin/build.gradle
@@ -82,8 +82,10 @@ tasks.named("precommit").configure {
tasks.named("yamlRestCompatTestTransform").configure({ task ->
task.skipTest("security/10_forbidden/Test bulk response with invalid credentials", "warning does not exist for compatibility")
- task.skipTest("inference/inference_crud/Test get all", "Assertions on number of inference models break due to default configs")
task.skipTest("esql/60_usage/Basic ESQL usage output (telemetry)", "The telemetry output changed. We dropped a column. That's safe.")
+ task.skipTest("inference/inference_crud/Test get all", "Assertions on number of inference models break due to default configs")
+ task.skipTest("esql/60_usage/Basic ESQL usage output (telemetry) snapshot version", "The number of functions is constantly increasing")
+ task.skipTest("esql/60_usage/Basic ESQL usage output (telemetry) non-snapshot version", "The number of functions is constantly increasing")
task.skipTest("esql/80_text/reverse text", "The output type changed from TEXT to KEYWORD.")
task.skipTest("esql/80_text/values function", "The output type changed from TEXT to KEYWORD.")
})
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/docs.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/docs.csv-spec
index a9c5a5214f159..14d811535aafd 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/docs.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/docs.csv-spec
@@ -656,3 +656,22 @@ FROM sample_data
@timestamp:date | client_ip:ip | event_duration:long | message:keyword
;
+
+docsBitLength
+required_capability: fn_bit_length
+// tag::bitLength[]
+FROM employees
+| KEEP first_name, last_name
+| EVAL fn_bit_length = BIT_LENGTH(first_name)
+// end::bitLength[]
+| SORT first_name
+| LIMIT 3
+;
+
+// tag::bitLength-result[]
+first_name:keyword | last_name:keyword | fn_bit_length:integer
+Alejandro |McAlpine |72
+Amabile |Gomatam |56
+Anneke |Preusig |48
+// end::bitLength-result[]
+;
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/string.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/string.csv-spec
index 305b8f3d8011e..de5981df999c7 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/string.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/string.csv-spec
@@ -38,6 +38,56 @@ emp_no:integer | l:integer
10003 | 5
;
+bitLength
+required_capability: fn_bit_length
+row a = "hello", b = "" | eval y = bit_length(a) + bit_length(b);
+
+a:keyword | b:keyword | y:integer
+hello | | 40
+;
+
+bitLengthWithNonAsciiChars
+required_capability: fn_bit_length
+row a = "¡", b = "❗️" | eval y = bit_length(a) | eval z = bit_length(b);
+
+a:keyword | b:keyword | y:integer | z:integer
+¡ | ❗️ | 16 | 48
+;
+
+foldBitLength
+required_capability: fn_bit_length
+row a = 1 | eval b = bit_length("hello");
+
+a:integer | b:integer
+1 | 40
+;
+
+bitLengthAndSourceQuoting
+required_capability: fn_bit_length
+from "employees" | sort emp_no | limit 3 | eval l = bit_length(first_name) | keep emp_no, l;
+
+emp_no:integer | l:integer
+10001 | 48
+10002 | 56
+10003 | 40
+;
+
+bitLengthInsideOtherFunction
+required_capability: fn_bit_length
+row a = "abc", b = "de" | eval g = greatest(bit_length(a), bit_length(b), bit_length("fghi"));
+
+a:keyword | b:keyword | g:integer
+abc | de | 32
+;
+
+bitLengthNull
+required_capability: fn_bit_length
+row a = "abc" | eval l = bit_length(null);
+
+a:string | l:integer
+abc | null
+;
+
startsWithConstant
from employees | sort emp_no | limit 10 | eval f_S = starts_with(first_name, "S") | keep emp_no, first_name, f_S;
diff --git a/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLengthEvaluator.java b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLengthEvaluator.java
new file mode 100644
index 0000000000000..6564a2f3ef167
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/generated/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLengthEvaluator.java
@@ -0,0 +1,137 @@
+// 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.string;
+
+import java.lang.ArithmeticException;
+import java.lang.IllegalArgumentException;
+import java.lang.Override;
+import java.lang.String;
+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.Page;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.compute.operator.Warnings;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+
+/**
+ * {@link EvalOperator.ExpressionEvaluator} implementation for {@link BitLength}.
+ * This class is generated. Do not edit it.
+ */
+public final class BitLengthEvaluator implements EvalOperator.ExpressionEvaluator {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator val;
+
+ private final DriverContext driverContext;
+
+ private Warnings warnings;
+
+ public BitLengthEvaluator(Source source, EvalOperator.ExpressionEvaluator val,
+ DriverContext driverContext) {
+ this.source = source;
+ this.val = val;
+ this.driverContext = driverContext;
+ }
+
+ @Override
+ public Block eval(Page page) {
+ try (BytesRefBlock valBlock = (BytesRefBlock) val.eval(page)) {
+ BytesRefVector valVector = valBlock.asVector();
+ if (valVector == null) {
+ return eval(page.getPositionCount(), valBlock);
+ }
+ return eval(page.getPositionCount(), valVector);
+ }
+ }
+
+ public IntBlock eval(int positionCount, BytesRefBlock valBlock) {
+ try(IntBlock.Builder result = driverContext.blockFactory().newIntBlockBuilder(positionCount)) {
+ BytesRef valScratch = new BytesRef();
+ position: for (int p = 0; p < positionCount; p++) {
+ if (valBlock.isNull(p)) {
+ result.appendNull();
+ continue position;
+ }
+ if (valBlock.getValueCount(p) != 1) {
+ if (valBlock.getValueCount(p) > 1) {
+ warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
+ }
+ result.appendNull();
+ continue position;
+ }
+ try {
+ result.appendInt(BitLength.process(valBlock.getBytesRef(valBlock.getFirstValueIndex(p), valScratch)));
+ } catch (ArithmeticException e) {
+ warnings().registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ public IntBlock eval(int positionCount, BytesRefVector valVector) {
+ try(IntBlock.Builder result = driverContext.blockFactory().newIntBlockBuilder(positionCount)) {
+ BytesRef valScratch = new BytesRef();
+ position: for (int p = 0; p < positionCount; p++) {
+ try {
+ result.appendInt(BitLength.process(valVector.getBytesRef(p, valScratch)));
+ } catch (ArithmeticException e) {
+ warnings().registerException(e);
+ result.appendNull();
+ }
+ }
+ return result.build();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "BitLengthEvaluator[" + "val=" + val + "]";
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(val);
+ }
+
+ private Warnings warnings() {
+ if (warnings == null) {
+ this.warnings = Warnings.createWarnings(
+ driverContext.warningsMode(),
+ source.source().getLineNumber(),
+ source.source().getColumnNumber(),
+ source.text()
+ );
+ }
+ return warnings;
+ }
+
+ static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory val;
+
+ public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory val) {
+ this.source = source;
+ this.val = val;
+ }
+
+ @Override
+ public BitLengthEvaluator get(DriverContext context) {
+ return new BitLengthEvaluator(source, val.get(context), context);
+ }
+
+ @Override
+ public String toString() {
+ return "BitLengthEvaluator[" + "val=" + val + "]";
+ }
+ }
+}
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 837acfc5083f7..38a8452095d68 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,12 @@
*/
public class EsqlCapabilities {
public enum Cap {
+
+ /**
+ * Support for function {@code BIT_LENGTH}. Done in #115792
+ */
+ FN_BIT_LENGTH,
+
/**
* Support for function {@code REVERSE}.
*/
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 66151275fc2e8..d7a23a6589d4f 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
@@ -117,6 +117,7 @@
import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StDistance;
import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StX;
import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StY;
+import org.elasticsearch.xpack.esql.expression.function.scalar.string.BitLength;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.Concat;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.EndsWith;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.LTrim;
@@ -305,6 +306,7 @@ private FunctionDefinition[][] functions() {
def(Tau.class, Tau::new, "tau") },
// string
new FunctionDefinition[] {
+ def(BitLength.class, BitLength::new, "bit_length"),
def(Concat.class, Concat::new, "concat"),
def(EndsWith.class, EndsWith::new, "ends_with"),
def(LTrim.class, LTrim::new, "ltrim"),
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/EsqlScalarFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/EsqlScalarFunction.java
index e4e1fbb6e5aac..65985f234ac92 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/EsqlScalarFunction.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/EsqlScalarFunction.java
@@ -38,6 +38,7 @@
import org.elasticsearch.xpack.esql.expression.function.scalar.math.Tau;
import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce;
import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.BinarySpatialFunction;
+import org.elasticsearch.xpack.esql.expression.function.scalar.string.BitLength;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.Concat;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.EndsWith;
import org.elasticsearch.xpack.esql.expression.function.scalar.string.Left;
@@ -74,6 +75,7 @@ public static List getNamedWriteables() {
List entries = new ArrayList<>();
entries.add(And.ENTRY);
entries.add(Atan2.ENTRY);
+ entries.add(BitLength.ENTRY);
entries.add(Bucket.ENTRY);
entries.add(Case.ENTRY);
entries.add(Categorize.ENTRY);
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLength.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLength.java
new file mode 100644
index 0000000000000..5deb6fa7feba6
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLength.java
@@ -0,0 +1,100 @@
+/*
+ * 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.string;
+
+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.operator.EvalOperator;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+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.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.UnaryScalarFunction;
+import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT;
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
+
+public class BitLength extends UnaryScalarFunction {
+
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
+ Expression.class,
+ "BitLength",
+ BitLength::new
+ );
+
+ @FunctionInfo(
+ returnType = "integer",
+ description = "Returns the bit length of a string.",
+ examples = @Example(file = "docs", tag = "bitLength")
+ )
+ public BitLength(
+ Source source,
+ @Param(
+ name = "string",
+ type = { "keyword", "text" },
+ description = "String expression. If `null`, the function returns `null`."
+ ) Expression field
+ ) {
+ super(source, field);
+ }
+
+ private BitLength(StreamInput in) throws IOException {
+ this(Source.readFrom((PlanStreamInput) in), in.readNamedWriteable(Expression.class));
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ source().writeTo(out);
+ out.writeNamedWriteable(field());
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
+ @Override
+ public DataType dataType() {
+ return DataType.INTEGER;
+ }
+
+ @Override
+ protected TypeResolution resolveType() {
+ return childrenResolved() == false ? new TypeResolution("Unresolved children") : isString(field(), sourceText(), DEFAULT);
+ }
+
+ @Evaluator(warnExceptions = { ArithmeticException.class })
+ static int process(BytesRef val) {
+ return Math.multiplyExact(val.length, Byte.SIZE);
+ }
+
+ @Override
+ public Expression replaceChildren(List newChildren) {
+ return new BitLength(source(), newChildren.get(0));
+ }
+
+ @Override
+ protected NodeInfo extends Expression> info() {
+ return NodeInfo.create(this, BitLength::new, field());
+ }
+
+ @Override
+ public EvalOperator.ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) {
+ return new BitLengthEvaluator.Factory(source(), toEvaluator.apply(field()));
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLengthSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLengthSerializationTests.java
new file mode 100644
index 0000000000000..2564ac0bdb1cf
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLengthSerializationTests.java
@@ -0,0 +1,19 @@
+/*
+ * 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.string;
+
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.expression.AbstractUnaryScalarSerializationTests;
+
+public class BitLengthSerializationTests extends AbstractUnaryScalarSerializationTests {
+ @Override
+ protected BitLength create(Source source, Expression child) {
+ return new BitLength(source, child);
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLengthTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLengthTests.java
new file mode 100644
index 0000000000000..bce4328a08abf
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/BitLengthTests.java
@@ -0,0 +1,61 @@
+/*
+ * 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.string;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
+import org.apache.lucene.util.BytesRef;
+import org.elasticsearch.common.lucene.BytesRefs;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class BitLengthTests extends AbstractScalarFunctionTestCase {
+
+ public BitLengthTests(@Name("TestCase") Supplier testCaseSupplier) {
+ this.testCase = testCaseSupplier.get();
+ }
+
+ @ParametersFactory
+ public static Iterable