mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-06-28 09:28:55 -04:00
Add ES|QL bit_length function (#115792)
This commit is contained in:
parent
30feced762
commit
81fd1de76b
20 changed files with 472 additions and 5 deletions
5
docs/changelog/115792.yaml
Normal file
5
docs/changelog/115792.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pr: 115792
|
||||||
|
summary: Add ES|QL `bit_length` function
|
||||||
|
area: ES|QL
|
||||||
|
type: enhancement
|
||||||
|
issues: []
|
5
docs/reference/esql/functions/description/bit_length.asciidoc
generated
Normal file
5
docs/reference/esql/functions/description/bit_length.asciidoc
generated
Normal file
|
@ -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.
|
13
docs/reference/esql/functions/examples/bit_length.asciidoc
generated
Normal file
13
docs/reference/esql/functions/examples/bit_length.asciidoc
generated
Normal file
|
@ -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]
|
||||||
|
|===
|
||||||
|
|
12
docs/reference/esql/functions/kibana/docs/bit_length.md
generated
Normal file
12
docs/reference/esql/functions/kibana/docs/bit_length.md
generated
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!--
|
||||||
|
This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### BIT_LENGTH
|
||||||
|
Returns the bit length of a string.
|
||||||
|
|
||||||
|
```
|
||||||
|
FROM employees
|
||||||
|
| KEEP first_name, last_name
|
||||||
|
| EVAL fn_bit_length = BIT_LENGTH(first_name)
|
||||||
|
```
|
15
docs/reference/esql/functions/layout/bit_length.asciidoc
generated
Normal file
15
docs/reference/esql/functions/layout/bit_length.asciidoc
generated
Normal file
|
@ -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[]
|
6
docs/reference/esql/functions/parameters/bit_length.asciidoc
generated
Normal file
6
docs/reference/esql/functions/parameters/bit_length.asciidoc
generated
Normal file
|
@ -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`.
|
1
docs/reference/esql/functions/signature/bit_length.svg
generated
Normal file
1
docs/reference/esql/functions/signature/bit_length.svg
generated
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="336" height="46" viewbox="0 0 336 46"><defs><style type="text/css">#guide .c{fill:none;stroke:#222222;}#guide .k{fill:#000000;font-family:Roboto Mono,Sans-serif;font-size:20px;}#guide .s{fill:#e4f4ff;stroke:#222222;}#guide .syn{fill:#8D8D8D;font-family:Roboto Mono,Sans-serif;font-size:20px;}</style></defs><path class="c" d="M0 31h5m140 0h10m32 0h10m92 0h10m32 0h5"/><rect class="s" x="5" y="5" width="140" height="36"/><text class="k" x="15" y="31">BIT_LENGTH</text><rect class="s" x="155" y="5" width="32" height="36" rx="7"/><text class="syn" x="165" y="31">(</text><rect class="s" x="197" y="5" width="92" height="36" rx="7"/><text class="k" x="207" y="31">string</text><rect class="s" x="299" y="5" width="32" height="36" rx="7"/><text class="syn" x="309" y="31">)</text></svg>
|
After Width: | Height: | Size: 887 B |
|
@ -8,6 +8,7 @@
|
||||||
{esql} supports these string functions:
|
{esql} supports these string functions:
|
||||||
|
|
||||||
// tag::string_list[]
|
// tag::string_list[]
|
||||||
|
* <<esql-bit_length>>
|
||||||
* <<esql-concat>>
|
* <<esql-concat>>
|
||||||
* <<esql-ends_with>>
|
* <<esql-ends_with>>
|
||||||
* <<esql-from_base64>>
|
* <<esql-from_base64>>
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
* <<esql-trim>>
|
* <<esql-trim>>
|
||||||
// end::string_list[]
|
// end::string_list[]
|
||||||
|
|
||||||
|
include::layout/bit_length.asciidoc[]
|
||||||
include::layout/concat.asciidoc[]
|
include::layout/concat.asciidoc[]
|
||||||
include::layout/ends_with.asciidoc[]
|
include::layout/ends_with.asciidoc[]
|
||||||
include::layout/from_base64.asciidoc[]
|
include::layout/from_base64.asciidoc[]
|
||||||
|
|
10
docs/reference/esql/functions/types/bit_length.asciidoc
generated
Normal file
10
docs/reference/esql/functions/types/bit_length.asciidoc
generated
Normal file
|
@ -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
|
||||||
|
|===
|
|
@ -82,8 +82,10 @@ tasks.named("precommit").configure {
|
||||||
|
|
||||||
tasks.named("yamlRestCompatTestTransform").configure({ task ->
|
tasks.named("yamlRestCompatTestTransform").configure({ task ->
|
||||||
task.skipTest("security/10_forbidden/Test bulk response with invalid credentials", "warning does not exist for compatibility")
|
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("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/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.")
|
task.skipTest("esql/80_text/values function", "The output type changed from TEXT to KEYWORD.")
|
||||||
})
|
})
|
||||||
|
|
|
@ -656,3 +656,22 @@ FROM sample_data
|
||||||
|
|
||||||
@timestamp:date | client_ip:ip | event_duration:long | message:keyword
|
@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[]
|
||||||
|
;
|
||||||
|
|
|
@ -38,6 +38,56 @@ emp_no:integer | l:integer
|
||||||
10003 | 5
|
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
|
startsWithConstant
|
||||||
from employees | sort emp_no | limit 10 | eval f_S = starts_with(first_name, "S") | keep emp_no, first_name, f_S;
|
from employees | sort emp_no | limit 10 | eval f_S = starts_with(first_name, "S") | keep emp_no, first_name, f_S;
|
||||||
|
|
||||||
|
|
|
@ -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 + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,12 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class EsqlCapabilities {
|
public class EsqlCapabilities {
|
||||||
public enum Cap {
|
public enum Cap {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support for function {@code BIT_LENGTH}. Done in #115792
|
||||||
|
*/
|
||||||
|
FN_BIT_LENGTH,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for function {@code REVERSE}.
|
* Support for function {@code REVERSE}.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -117,6 +117,7 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialWi
|
||||||
import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StDistance;
|
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.StX;
|
||||||
import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.StY;
|
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.Concat;
|
||||||
import org.elasticsearch.xpack.esql.expression.function.scalar.string.EndsWith;
|
import org.elasticsearch.xpack.esql.expression.function.scalar.string.EndsWith;
|
||||||
import org.elasticsearch.xpack.esql.expression.function.scalar.string.LTrim;
|
import org.elasticsearch.xpack.esql.expression.function.scalar.string.LTrim;
|
||||||
|
@ -305,6 +306,7 @@ public class EsqlFunctionRegistry {
|
||||||
def(Tau.class, Tau::new, "tau") },
|
def(Tau.class, Tau::new, "tau") },
|
||||||
// string
|
// string
|
||||||
new FunctionDefinition[] {
|
new FunctionDefinition[] {
|
||||||
|
def(BitLength.class, BitLength::new, "bit_length"),
|
||||||
def(Concat.class, Concat::new, "concat"),
|
def(Concat.class, Concat::new, "concat"),
|
||||||
def(EndsWith.class, EndsWith::new, "ends_with"),
|
def(EndsWith.class, EndsWith::new, "ends_with"),
|
||||||
def(LTrim.class, LTrim::new, "ltrim"),
|
def(LTrim.class, LTrim::new, "ltrim"),
|
||||||
|
|
|
@ -38,6 +38,7 @@ 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.math.Tau;
|
||||||
import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce;
|
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.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.Concat;
|
||||||
import org.elasticsearch.xpack.esql.expression.function.scalar.string.EndsWith;
|
import org.elasticsearch.xpack.esql.expression.function.scalar.string.EndsWith;
|
||||||
import org.elasticsearch.xpack.esql.expression.function.scalar.string.Left;
|
import org.elasticsearch.xpack.esql.expression.function.scalar.string.Left;
|
||||||
|
@ -74,6 +75,7 @@ public abstract class EsqlScalarFunction extends ScalarFunction implements Evalu
|
||||||
List<NamedWriteableRegistry.Entry> entries = new ArrayList<>();
|
List<NamedWriteableRegistry.Entry> entries = new ArrayList<>();
|
||||||
entries.add(And.ENTRY);
|
entries.add(And.ENTRY);
|
||||||
entries.add(Atan2.ENTRY);
|
entries.add(Atan2.ENTRY);
|
||||||
|
entries.add(BitLength.ENTRY);
|
||||||
entries.add(Bucket.ENTRY);
|
entries.add(Bucket.ENTRY);
|
||||||
entries.add(Case.ENTRY);
|
entries.add(Case.ENTRY);
|
||||||
entries.add(Categorize.ENTRY);
|
entries.add(Categorize.ENTRY);
|
||||||
|
|
|
@ -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<Expression> 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()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<BitLength> {
|
||||||
|
@Override
|
||||||
|
protected BitLength create(Source source, Expression child) {
|
||||||
|
return new BitLength(source, child);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.TestCase> testCaseSupplier) {
|
||||||
|
this.testCase = testCaseSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParametersFactory
|
||||||
|
public static Iterable<Object[]> parameters() {
|
||||||
|
List<TestCaseSupplier> suppliers = new ArrayList<>();
|
||||||
|
|
||||||
|
for (DataType stringType : DataType.stringTypes()) {
|
||||||
|
for (var supplier : TestCaseSupplier.stringCases(stringType)) {
|
||||||
|
suppliers.add(makeSupplier(supplier));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameterSuppliersFromTypedDataWithDefaultChecks(true, suppliers, (v, p) -> "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Expression build(Source source, List<Expression> args) {
|
||||||
|
return new BitLength(source, args.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TestCaseSupplier makeSupplier(TestCaseSupplier.TypedDataSupplier fieldSupplier) {
|
||||||
|
return new TestCaseSupplier(fieldSupplier.name(), List.of(fieldSupplier.type()), () -> {
|
||||||
|
var fieldTypedData = fieldSupplier.get();
|
||||||
|
String evaluatorToString = "BitLengthEvaluator[val=Attribute[channel=0]]";
|
||||||
|
BytesRef value = BytesRefs.toBytesRef(fieldTypedData.data());
|
||||||
|
var expectedValue = value.length * Byte.SIZE;
|
||||||
|
|
||||||
|
return new TestCaseSupplier.TestCase(List.of(fieldTypedData), evaluatorToString, DataType.INTEGER, equalTo(expectedValue));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ setup:
|
||||||
- method: POST
|
- method: POST
|
||||||
path: /_query
|
path: /_query
|
||||||
parameters: []
|
parameters: []
|
||||||
capabilities: [ snapshot_test_for_telemetry ]
|
capabilities: [ snapshot_test_for_telemetry, fn_bit_length ]
|
||||||
reason: "Test that should only be executed on snapshot versions"
|
reason: "Test that should only be executed on snapshot versions"
|
||||||
|
|
||||||
- do: {xpack.usage: {}}
|
- do: {xpack.usage: {}}
|
||||||
|
@ -91,7 +91,7 @@ setup:
|
||||||
- match: {esql.functions.cos: $functions_cos}
|
- match: {esql.functions.cos: $functions_cos}
|
||||||
- gt: {esql.functions.to_long: $functions_to_long}
|
- gt: {esql.functions.to_long: $functions_to_long}
|
||||||
- match: {esql.functions.coalesce: $functions_coalesce}
|
- match: {esql.functions.coalesce: $functions_coalesce}
|
||||||
- length: {esql.functions: 117} # check the "sister" test below for a likely update to the same esql.functions length check
|
- length: {esql.functions: 118} # check the "sister" test below for a likely update to the same esql.functions length check
|
||||||
|
|
||||||
---
|
---
|
||||||
"Basic ESQL usage output (telemetry) non-snapshot version":
|
"Basic ESQL usage output (telemetry) non-snapshot version":
|
||||||
|
@ -101,7 +101,7 @@ setup:
|
||||||
- method: POST
|
- method: POST
|
||||||
path: /_query
|
path: /_query
|
||||||
parameters: []
|
parameters: []
|
||||||
capabilities: [ non_snapshot_test_for_telemetry ]
|
capabilities: [ non_snapshot_test_for_telemetry, fn_bit_length ]
|
||||||
reason: "Test that should only be executed on release versions"
|
reason: "Test that should only be executed on release versions"
|
||||||
|
|
||||||
- do: {xpack.usage: {}}
|
- do: {xpack.usage: {}}
|
||||||
|
@ -162,4 +162,4 @@ setup:
|
||||||
- match: {esql.functions.cos: $functions_cos}
|
- match: {esql.functions.cos: $functions_cos}
|
||||||
- gt: {esql.functions.to_long: $functions_to_long}
|
- gt: {esql.functions.to_long: $functions_to_long}
|
||||||
- match: {esql.functions.coalesce: $functions_coalesce}
|
- match: {esql.functions.coalesce: $functions_coalesce}
|
||||||
- length: {esql.functions: 115} # check the "sister" test above for a likely update to the same esql.functions length check
|
- length: {esql.functions: 118} # check the "sister" test above for a likely update to the same esql.functions length check
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue