From 2d45e47bb5abf66ad80a06a7abf60de26ce5f147 Mon Sep 17 00:00:00 2001
From: Huaixinww <141887897+Huaixinww@users.noreply.github.com>
Date: Thu, 27 Jun 2024 15:12:28 +0800
Subject: [PATCH 001/216] [ML] Add InferenceAction request query validation
(#110147)
---
.../inference/action/InferenceAction.java | 14 +++
.../action/InferenceActionRequestTests.java | 89 +++++++++++++++++++
2 files changed, 103 insertions(+)
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/inference/action/InferenceAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/inference/action/InferenceAction.java
index cfd4da0d59e31..16d0b940d40e6 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/inference/action/InferenceAction.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/inference/action/InferenceAction.java
@@ -38,6 +38,8 @@
import java.util.Map;
import java.util.Objects;
+import static org.elasticsearch.core.Strings.format;
+
public class InferenceAction extends ActionType {
public static final InferenceAction INSTANCE = new InferenceAction();
@@ -173,6 +175,18 @@ public ActionRequestValidationException validate() {
e.addValidationError("input array is empty");
return e;
}
+ if (taskType.equals(TaskType.RERANK)) {
+ if (query == null) {
+ var e = new ActionRequestValidationException();
+ e.addValidationError(format("Field [query] cannot be null for task type [%s]", TaskType.RERANK));
+ return e;
+ }
+ if (query.isEmpty()) {
+ var e = new ActionRequestValidationException();
+ e.addValidationError(format("Field [query] cannot be empty for task type [%s]", TaskType.RERANK));
+ return e;
+ }
+ }
return null;
}
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/inference/action/InferenceActionRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/inference/action/InferenceActionRequestTests.java
index 476167c5db0fb..fa7044ffd8c8b 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/inference/action/InferenceActionRequestTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/inference/action/InferenceActionRequestTests.java
@@ -9,6 +9,7 @@
import org.elasticsearch.TransportVersion;
import org.elasticsearch.TransportVersions;
+import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.core.Tuple;
@@ -71,6 +72,94 @@ public void testParsing() throws IOException {
}
}
+ public void testValidation_TextEmbedding() {
+ InferenceAction.Request request = new InferenceAction.Request(
+ TaskType.TEXT_EMBEDDING,
+ "model",
+ null,
+ List.of("input"),
+ null,
+ null,
+ null
+ );
+ ActionRequestValidationException e = request.validate();
+ assertNull(e);
+ }
+
+ public void testValidation_Rerank() {
+ InferenceAction.Request request = new InferenceAction.Request(
+ TaskType.RERANK,
+ "model",
+ "query",
+ List.of("input"),
+ null,
+ null,
+ null
+ );
+ ActionRequestValidationException e = request.validate();
+ assertNull(e);
+ }
+
+ public void testValidation_TextEmbedding_Null() {
+ InferenceAction.Request inputNullRequest = new InferenceAction.Request(
+ TaskType.TEXT_EMBEDDING,
+ "model",
+ null,
+ null,
+ null,
+ null,
+ null
+ );
+ ActionRequestValidationException inputNullError = inputNullRequest.validate();
+ assertNotNull(inputNullError);
+ assertThat(inputNullError.getMessage(), is("Validation Failed: 1: missing input;"));
+ }
+
+ public void testValidation_TextEmbedding_Empty() {
+ InferenceAction.Request inputEmptyRequest = new InferenceAction.Request(
+ TaskType.TEXT_EMBEDDING,
+ "model",
+ null,
+ List.of(),
+ null,
+ null,
+ null
+ );
+ ActionRequestValidationException inputEmptyError = inputEmptyRequest.validate();
+ assertNotNull(inputEmptyError);
+ assertThat(inputEmptyError.getMessage(), is("Validation Failed: 1: input array is empty;"));
+ }
+
+ public void testValidation_Rerank_Null() {
+ InferenceAction.Request queryNullRequest = new InferenceAction.Request(
+ TaskType.RERANK,
+ "model",
+ null,
+ List.of("input"),
+ null,
+ null,
+ null
+ );
+ ActionRequestValidationException queryNullError = queryNullRequest.validate();
+ assertNotNull(queryNullError);
+ assertThat(queryNullError.getMessage(), is("Validation Failed: 1: Field [query] cannot be null for task type [rerank];"));
+ }
+
+ public void testValidation_Rerank_Empty() {
+ InferenceAction.Request queryEmptyRequest = new InferenceAction.Request(
+ TaskType.RERANK,
+ "model",
+ "",
+ List.of("input"),
+ null,
+ null,
+ null
+ );
+ ActionRequestValidationException queryEmptyError = queryEmptyRequest.validate();
+ assertNotNull(queryEmptyError);
+ assertThat(queryEmptyError.getMessage(), is("Validation Failed: 1: Field [query] cannot be empty for task type [rerank];"));
+ }
+
public void testParseRequest_DefaultsInputTypeToIngest() throws IOException {
String singleInputRequest = """
{
From b7c18bcfe1090033f88b2afc00ece1e30a01f5d3 Mon Sep 17 00:00:00 2001
From: Luigi Dell'Aquila
Date: Thu, 27 Jun 2024 10:15:59 +0200
Subject: [PATCH 002/216] ES|QL: Fix DISSECT that overwrites input (#110201)
Fixes https://github.com/elastic/elasticsearch/issues/110184
When a DISSECT command overwrites the input, eg.
```
FROM idx | DISSECT foo "%{foo} %{bar}" | KEEP foo, bar
```
The input field (`foo` in this case) could be excluded from the index
resolution (incorrectly masked by the `foo` that is the result of the
DISSECT). This PR makes sure that the input field does not get lost and
is correctly passed to the indexResolver.
---
docs/changelog/110201.yaml | 6 ++++++
.../testFixtures/src/main/resources/dissect.csv-spec | 11 +++++++++++
.../qa/testFixtures/src/main/resources/grok.csv-spec | 8 ++++++++
.../xpack/esql/action/EsqlCapabilities.java | 8 +++++++-
.../elasticsearch/xpack/esql/session/EsqlSession.java | 8 +++-----
.../esql/session/IndexResolverFieldNamesTests.java | 8 ++++++++
6 files changed, 43 insertions(+), 6 deletions(-)
create mode 100644 docs/changelog/110201.yaml
diff --git a/docs/changelog/110201.yaml b/docs/changelog/110201.yaml
new file mode 100644
index 0000000000000..a880638881948
--- /dev/null
+++ b/docs/changelog/110201.yaml
@@ -0,0 +1,6 @@
+pr: 110201
+summary: "ES|QL: Fix DISSECT that overwrites input"
+area: ES|QL
+type: bug
+issues:
+ - 110184
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/dissect.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/dissect.csv-spec
index f8a49c3a59f98..812198c324217 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/dissect.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/dissect.csv-spec
@@ -175,6 +175,17 @@ Parto Bamford | Parto | Bamford
;
+// different from shadowingSelf because in this case we dissect an indexed field
+// see https://github.com/elastic/elasticsearch/issues/110184
+overwriteInputName
+required_capability: grok_dissect_masking
+from employees | sort emp_no asc | dissect first_name "%{first_name}o%{rest}" | keep emp_no, first_name, rest | limit 1;
+
+emp_no:integer | first_name:keyword | rest:keyword
+10001 | Ge | rgi
+;
+
+
overwriteNameWhere
from employees | sort emp_no asc | eval full_name = concat(first_name, " ", last_name) | dissect full_name "%{emp_no} %{b}" | where emp_no == "Bezalel" | keep full_name, emp_no, b | limit 3;
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/grok.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/grok.csv-spec
index 49a8085e0c186..9d574eed7be6b 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/grok.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/grok.csv-spec
@@ -229,3 +229,11 @@ emp_no:integer | a:keyword | b:keyword
10004 | [Head, Reporting, Support, Tech] | [Human, Analyst, Engineer, Lead] | Resources | [Head Human Resources, Reporting Analyst, Support Engineer, Tech Lead]
10005 | null | null | null | null
;
+
+overwriteInputName
+required_capability: grok_dissect_masking
+row text = "123 abc", int = 5 | sort int asc | grok text "%{NUMBER:text:int} %{WORD:description}" | keep text, int, description;
+
+text:integer | int:integer | description:keyword
+123 | 5 | abc
+;
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 1caf94dde5c30..ecbe25227616b 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
@@ -89,7 +89,13 @@ public enum Cap {
/**
* Support for function {@code ST_DISTANCE}. Done in #108764.
*/
- ST_DISTANCE;
+ ST_DISTANCE,
+
+ /**
+ * Fix to GROK and DISSECT that allows extracting attributes with the same name as the input
+ * https://github.com/elastic/elasticsearch/issues/110184
+ */
+ GROK_DISSECT_MASKING;
Cap() {
snapshotOnly = false;
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java
index 0589424b37d1e..44c08fc5fd60b 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java
@@ -235,14 +235,12 @@ static Set fieldNames(LogicalPlan parsed, Set enrichPolicyMatchF
parsed.forEachDown(p -> {// go over each plan top-down
if (p instanceof RegexExtract re) { // for Grok and Dissect
- AttributeSet dissectRefs = p.references();
- // don't add to the list of fields the extracted ones (they are not real fields in mappings)
- dissectRefs.removeAll(re.extractedFields());
- references.addAll(dissectRefs);
- // also remove other down-the-tree references to the extracted fields
+ // remove other down-the-tree references to the extracted fields
for (Attribute extracted : re.extractedFields()) {
references.removeIf(attr -> matchByName(attr, extracted.qualifiedName(), false));
}
+ // but keep the inputs needed by Grok/Dissect
+ references.addAll(re.input().references());
} else if (p instanceof Enrich) {
AttributeSet enrichRefs = p.references();
// Enrich adds an EmptyAttribute if no match field is specified
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java
index 17dca8096de0f..925601bded425 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java
@@ -1212,6 +1212,14 @@ public void testEnrichOnDefaultFieldWithKeep() {
assertThat(fieldNames, equalTo(Set.of("emp_no", "emp_no.*", "language_name", "language_name.*")));
}
+ public void testDissectOverwriteName() {
+ Set fieldNames = EsqlSession.fieldNames(parser.createStatement("""
+ from employees
+ | dissect first_name "%{first_name} %{more}"
+ | keep emp_no, first_name, more"""), Set.of());
+ assertThat(fieldNames, equalTo(Set.of("emp_no", "emp_no.*", "first_name", "first_name.*")));
+ }
+
public void testEnrichOnDefaultField() {
Set fieldNames = EsqlSession.fieldNames(parser.createStatement("""
from employees
From 186edb2c352c1f3000a5bdf5bfe41b23d7e63396 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20R=C3=BChsen?=
Date: Thu, 27 Jun 2024 10:22:04 +0200
Subject: [PATCH 003/216] [Profiling] Add field env_https_proxy to
profiling-hosts (#110219)
---
.../profiling/component-template/profiling-hosts.json | 3 +++
.../profiling/persistence/ProfilingIndexTemplateRegistry.java | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugin/core/template-resources/src/main/resources/profiling/component-template/profiling-hosts.json b/x-pack/plugin/core/template-resources/src/main/resources/profiling/component-template/profiling-hosts.json
index d9b92f5cd4f0c..e58a3cbd39f97 100644
--- a/x-pack/plugin/core/template-resources/src/main/resources/profiling/component-template/profiling-hosts.json
+++ b/x-pack/plugin/core/template-resources/src/main/resources/profiling/component-template/profiling-hosts.json
@@ -79,6 +79,9 @@
"protocol": {
"type": "keyword"
},
+ "env_https_proxy": {
+ "type": "keyword"
+ },
"config.bpf_log_level": {
"type": "long"
},
diff --git a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/persistence/ProfilingIndexTemplateRegistry.java b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/persistence/ProfilingIndexTemplateRegistry.java
index 647f4b64090b3..3b361748abf67 100644
--- a/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/persistence/ProfilingIndexTemplateRegistry.java
+++ b/x-pack/plugin/profiling/src/main/java/org/elasticsearch/xpack/profiling/persistence/ProfilingIndexTemplateRegistry.java
@@ -52,7 +52,8 @@ public class ProfilingIndexTemplateRegistry extends IndexTemplateRegistry {
// version 9: Changed sort order for profiling-events-*
// version 10: changed mapping profiling-events @timestamp to 'date_nanos' from 'date'
// version 11: Added 'profiling.agent.protocol' keyword mapping to profiling-hosts
- public static final int INDEX_TEMPLATE_VERSION = 11;
+ // version 12: Added 'profiling.agent.env_https_proxy' keyword mapping to profiling-hosts
+ public static final int INDEX_TEMPLATE_VERSION = 12;
// history for individual indices / index templates. Only bump these for breaking changes that require to create a new index
public static final int PROFILING_EVENTS_VERSION = 4;
From 7e81229b7fdd9e5d4924f6cb11660ac7bda1cada Mon Sep 17 00:00:00 2001
From: Armin Braun
Date: Thu, 27 Jun 2024 11:18:27 +0200
Subject: [PATCH 004/216] Wait for dynamic mapping update more precisely
(#110187)
We ran into a situation where dynamic mapping updates where retried in a
fairly hot loop. The problem that triggered this was waiting for any cluster state
update in this logic. This is mostly fine but adds a lot of overhead for
retries when there's other actions running at a higher priority than the
mapping update. Lets make it specific so that we at least wait for there
to be any mapping and for its version to be different from the version
that made us request a mapping update in the first place.
Also added a breakout in case the index got concurrently deleted so we
don't run out the clock in that case.
---
.../action/bulk/TransportShardBulkAction.java | 17 +++++---
.../bulk/TransportShardBulkActionTests.java | 42 +++++++++----------
.../authz/AuthorizationServiceTests.java | 4 +-
3 files changed, 34 insertions(+), 29 deletions(-)
diff --git a/server/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java b/server/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java
index cafc25438e98b..7591ef402847e 100644
--- a/server/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java
+++ b/server/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java
@@ -70,6 +70,7 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.LongSupplier;
+import java.util.function.ObjLongConsumer;
import static org.elasticsearch.core.Strings.format;
@@ -150,7 +151,7 @@ protected void dispatchedShardOperationOnPrimary(
assert update != null;
assert shardId != null;
mappingUpdatedAction.updateMappingOnMaster(shardId.getIndex(), update, mappingListener);
- }, mappingUpdateListener -> observer.waitForNextChange(new ClusterStateObserver.Listener() {
+ }, (mappingUpdateListener, initialMappingVersion) -> observer.waitForNextChange(new ClusterStateObserver.Listener() {
@Override
public void onNewClusterState(ClusterState state) {
mappingUpdateListener.onResponse(null);
@@ -165,6 +166,9 @@ public void onClusterServiceClose() {
public void onTimeout(TimeValue timeout) {
mappingUpdateListener.onFailure(new MapperException("timed out while waiting for a dynamic mapping update"));
}
+ }, clusterState -> {
+ var indexMetadata = clusterState.metadata().index(primary.shardId().getIndex());
+ return indexMetadata == null || (indexMetadata.mapping() != null && indexMetadata.getMappingVersion() != initialMappingVersion);
}), listener, executor(primary), postWriteRefresh, postWriteAction, documentParsingProvider);
}
@@ -184,7 +188,7 @@ public static void performOnPrimary(
UpdateHelper updateHelper,
LongSupplier nowInMillisSupplier,
MappingUpdatePerformer mappingUpdater,
- Consumer> waitForMappingUpdate,
+ ObjLongConsumer> waitForMappingUpdate,
ActionListener> listener,
Executor executor
) {
@@ -209,7 +213,7 @@ public static void performOnPrimary(
UpdateHelper updateHelper,
LongSupplier nowInMillisSupplier,
MappingUpdatePerformer mappingUpdater,
- Consumer> waitForMappingUpdate,
+ ObjLongConsumer> waitForMappingUpdate,
ActionListener> listener,
Executor executor,
@Nullable PostWriteRefresh postWriteRefresh,
@@ -308,7 +312,7 @@ static boolean executeBulkItemRequest(
UpdateHelper updateHelper,
LongSupplier nowInMillisSupplier,
MappingUpdatePerformer mappingUpdater,
- Consumer> waitForMappingUpdate,
+ ObjLongConsumer> waitForMappingUpdate,
ActionListener itemDoneListener,
DocumentParsingProvider documentParsingProvider
) throws Exception {
@@ -398,7 +402,7 @@ static boolean executeBulkItemRequest(
private static boolean handleMappingUpdateRequired(
BulkPrimaryExecutionContext context,
MappingUpdatePerformer mappingUpdater,
- Consumer> waitForMappingUpdate,
+ ObjLongConsumer> waitForMappingUpdate,
ActionListener itemDoneListener,
IndexShard primary,
Engine.Result result,
@@ -406,6 +410,7 @@ private static boolean handleMappingUpdateRequired(
UpdateHelper.Result updateResult
) {
final var mapperService = primary.mapperService();
+ final long initialMappingVersion = mapperService.mappingVersion();
try {
CompressedXContent mergedSource = mapperService.merge(
MapperService.SINGLE_MAPPING_NAME,
@@ -439,7 +444,7 @@ public void onResponse(Void v) {
public void onFailure(Exception e) {
context.failOnMappingUpdate(e);
}
- }, () -> itemDoneListener.onResponse(null)));
+ }, () -> itemDoneListener.onResponse(null)), initialMappingVersion);
}
@Override
diff --git a/server/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java b/server/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java
index 18418dda59a3b..3b18c541bd80c 100644
--- a/server/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java
+++ b/server/src/test/java/org/elasticsearch/action/bulk/TransportShardBulkActionTests.java
@@ -120,7 +120,7 @@ public void testExecuteBulkIndexRequest() throws Exception {
null,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -152,7 +152,7 @@ public void testExecuteBulkIndexRequest() throws Exception {
null,
threadPool::absoluteTimeInMillis,
new ThrowingMappingUpdatePerformer(new RuntimeException("fail")),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -209,7 +209,7 @@ public void testSkipBulkIndexRequestIfAborted() throws Exception {
null,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> {},
+ (listener, mappingVersion) -> {},
ActionListener.runAfter(ActionTestUtils.assertNoFailureListener(result -> {
// since at least 1 item passed, the tran log location should exist,
assertThat(((WritePrimaryResult) result).location, notNullValue());
@@ -285,7 +285,7 @@ public void testExecuteBulkIndexRequestWithMappingUpdates() throws Exception {
assertNotNull(update);
updateCalled.incrementAndGet();
listener.onResponse(null);
- }, listener -> listener.onResponse(null), ASSERTING_DONE_LISTENER, DocumentParsingProvider.EMPTY_INSTANCE);
+ }, (listener, mappingVersion) -> listener.onResponse(null), ASSERTING_DONE_LISTENER, DocumentParsingProvider.EMPTY_INSTANCE);
assertTrue(context.isInitial());
assertTrue(context.hasMoreOperationsToExecute());
assertThat(context.getUpdateRetryCounter(), equalTo(0));
@@ -304,7 +304,7 @@ public void testExecuteBulkIndexRequestWithMappingUpdates() throws Exception {
null,
threadPool::absoluteTimeInMillis,
(update, shardId, listener) -> fail("should not have had to update the mappings"),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -345,7 +345,7 @@ public void testExecuteBulkIndexRequestWithErrorWhileUpdatingMapping() throws Ex
null,
threadPool::absoluteTimeInMillis,
errorOnWait == false ? new ThrowingMappingUpdatePerformer(err) : new NoopMappingUpdatePerformer(),
- errorOnWait ? listener -> listener.onFailure(err) : listener -> listener.onResponse(null),
+ errorOnWait ? (listener, mappingVersion) -> listener.onFailure(err) : (listener, mappingVersion) -> listener.onResponse(null),
new LatchedActionListener<>(new ActionListener() {
@Override
public void onResponse(Void aVoid) {}
@@ -398,7 +398,7 @@ public void testExecuteBulkDeleteRequest() throws Exception {
null,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -446,7 +446,7 @@ public void testExecuteBulkDeleteRequest() throws Exception {
null,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -510,7 +510,7 @@ public void testNoopUpdateRequest() throws Exception {
updateHelper,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -566,7 +566,7 @@ public void testUpdateRequestWithFailure() throws Exception {
updateHelper,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -631,7 +631,7 @@ public void testUpdateRequestWithConflictFailure() throws Exception {
updateHelper,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> listener.onResponse(null),
+ (listener, mappingVersion) -> listener.onResponse(null),
ASSERTING_DONE_LISTENER,
documentParsingProvider
);
@@ -697,7 +697,7 @@ public void testUpdateRequestWithSuccess() throws Exception {
updateHelper,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
documentParsingProvider
);
@@ -756,7 +756,7 @@ public void testUpdateWithDelete() throws Exception {
updateHelper,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> listener.onResponse(null),
+ (listener, mappingVersion) -> listener.onResponse(null),
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -794,7 +794,7 @@ public void testFailureDuringUpdateProcessing() throws Exception {
updateHelper,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -834,7 +834,7 @@ public void testTranslogPositionToSync() throws Exception {
null,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> {},
+ (listener, mappingVersion) -> {},
ASSERTING_DONE_LISTENER,
DocumentParsingProvider.EMPTY_INSTANCE
);
@@ -937,7 +937,7 @@ public void testRetries() throws Exception {
updateHelper,
threadPool::absoluteTimeInMillis,
new NoopMappingUpdatePerformer(),
- listener -> listener.onResponse(null),
+ (listener, mappingVersion) -> listener.onResponse(null),
new LatchedActionListener<>(ActionTestUtils.assertNoFailureListener(result -> {
assertThat(((WritePrimaryResult) result).location, equalTo(resultLocation));
BulkItemResponse primaryResponse = result.replicaRequest().items()[0].getPrimaryResponse();
@@ -1034,7 +1034,7 @@ public void testForceExecutionOnRejectionAfterMappingUpdate() throws Exception {
throw new IllegalStateException(e);
}
},
- listener -> listener.onResponse(null),
+ (listener, mappingVersion) -> listener.onResponse(null),
new LatchedActionListener<>(ActionTestUtils.assertNoFailureListener(result ->
// Assert that we still need to fsync the location that was successfully written
assertThat(((WritePrimaryResult) result).location, equalTo(resultLocation1))), latch),
@@ -1096,7 +1096,7 @@ public void testPerformOnPrimaryReportsBulkStats() throws Exception {
listener.onResponse(null);
}
},
- listener -> listener.onFailure(new IllegalStateException("no failure expected")),
+ (listener, mappingVersion) -> listener.onFailure(new IllegalStateException("no failure expected")),
new LatchedActionListener<>(ActionTestUtils.assertNoFailureListener(result -> {
try {
BulkStats bulkStats = shard.bulkStats();
@@ -1156,7 +1156,7 @@ public void testNoopMappingUpdateInfiniteLoopPrevention() throws Exception {
updateHelper,
threadPool::absoluteTimeInMillis,
(update, shardId, listener) -> fail("the master should not be contacted as the operation yielded a noop mapping update"),
- listener -> listener.onResponse(null),
+ (listener, mappingVersion) -> listener.onResponse(null),
ActionTestUtils.assertNoFailureListener(result -> {}),
threadPool.executor(Names.WRITE)
)
@@ -1200,7 +1200,7 @@ public void testNoopMappingUpdateSuccessOnRetry() throws Exception {
when(mapperService.merge(any(), any(CompressedXContent.class), any())).thenReturn(documentMapper);
// on the second invocation, the mapping version is incremented
// so that the second mapping update attempt doesn't trigger the infinite loop prevention
- when(mapperService.mappingVersion()).thenReturn(0L, 1L);
+ when(mapperService.mappingVersion()).thenReturn(0L, 0L, 1L);
UpdateHelper updateHelper = mock(UpdateHelper.class);
when(updateHelper.prepare(any(), eq(shard), any())).thenReturn(
@@ -1223,7 +1223,7 @@ public void testNoopMappingUpdateSuccessOnRetry() throws Exception {
updateHelper,
threadPool::absoluteTimeInMillis,
(update, shardId, listener) -> fail("the master should not be contacted as the operation yielded a noop mapping update"),
- listener -> listener.onFailure(new IllegalStateException("no failure expected")),
+ (listener, mappingVersion) -> listener.onFailure(new IllegalStateException("no failure expected")),
new LatchedActionListener<>(ActionTestUtils.assertNoFailureListener(result -> {
BulkItemResponse primaryResponse = result.replicaRequest().items()[0].getPrimaryResponse();
assertFalse(primaryResponse.isFailed());
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java
index 9d9528ec6f48b..5f878480a7d0d 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java
@@ -203,7 +203,7 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
-import java.util.function.Consumer;
+import java.util.function.ObjLongConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -1571,7 +1571,7 @@ public void testDenialErrorMessagesForBulkIngest() throws Exception {
authorize(authentication, TransportShardBulkAction.ACTION_NAME, request);
MappingUpdatePerformer mappingUpdater = (m, s, l) -> l.onResponse(null);
- Consumer> waitForMappingUpdate = l -> l.onResponse(null);
+ ObjLongConsumer> waitForMappingUpdate = (l, mappingVersion) -> l.onResponse(null);
PlainActionFuture> future = new PlainActionFuture<>();
IndexShard indexShard = mock(IndexShard.class);
when(indexShard.getBulkOperationListener()).thenReturn(new BulkOperationListener() {
From 5179b0db29b8fa06f904d17017fcf17d4dfedc0e Mon Sep 17 00:00:00 2001
From: Jedr Blaszyk
Date: Thu, 27 Jun 2024 12:17:33 +0200
Subject: [PATCH 005/216] [Connector API] Update status when setting/resetting
connector error (#110192)
---
.../apis/update-connector-error-api.asciidoc | 5 ++++
.../connector/100_connector_update_error.yml | 2 ++
.../connector/ConnectorIndexService.java | 7 +++++-
.../connector/ConnectorIndexServiceTests.java | 23 ++++++++-----------
4 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/docs/reference/connector/apis/update-connector-error-api.asciidoc b/docs/reference/connector/apis/update-connector-error-api.asciidoc
index 67ea6b6d17cf0..c6ac0c9a1ac22 100644
--- a/docs/reference/connector/apis/update-connector-error-api.asciidoc
+++ b/docs/reference/connector/apis/update-connector-error-api.asciidoc
@@ -21,6 +21,11 @@ To get started with Connector APIs, check out the {enterprise-search-ref}/connec
* To sync data using self-managed connectors, you need to deploy the {enterprise-search-ref}/build-connector.html[Elastic connector service] on your own infrastructure. This service runs automatically on Elastic Cloud for native connectors.
* The `connector_id` parameter should reference an existing connector.
+[[update-connector-error-api-desc]]
+==== {api-description-title}
+
+Sets the `error` field for the specified connector. If the `error` provided in the request body is non-null, the connector's status is updated to `error`. Otherwise, if the `error` is reset to null, the connector status is updated to `connected`.
+
[[update-connector-error-api-path-params]]
==== {api-path-parms-title}
diff --git a/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/100_connector_update_error.yml b/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/100_connector_update_error.yml
index a58f2399301d3..5943f9208c50f 100644
--- a/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/100_connector_update_error.yml
+++ b/x-pack/plugin/ent-search/qa/rest/src/yamlRestTest/resources/rest-api-spec/test/entsearch/connector/100_connector_update_error.yml
@@ -29,6 +29,7 @@ setup:
connector_id: test-connector
- match: { error: "some error" }
+ - match: { status: error }
---
@@ -59,6 +60,7 @@ setup:
connector_id: test-connector
- match: { error: null }
+ - match: { status: connected }
---
"Update Connector Error - 404 when connector doesn't exist":
diff --git a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java
index bb03d3c69c74a..cd98b43adc159 100644
--- a/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java
+++ b/x-pack/plugin/ent-search/src/main/java/org/elasticsearch/xpack/application/connector/ConnectorIndexService.java
@@ -467,7 +467,8 @@ else if (configurationValues != null) {
}
/**
- * Updates the error property of a {@link Connector}.
+ * Updates the error property of a {@link Connector}. If error is non-null the resulting {@link ConnectorStatus}
+ * is 'error', otherwise it's 'connected'.
*
* @param connectorId The ID of the {@link Connector} to be updated.
* @param error An instance of error property of {@link Connector}, can be reset to [null].
@@ -475,6 +476,9 @@ else if (configurationValues != null) {
*/
public void updateConnectorError(String connectorId, String error, ActionListener listener) {
try {
+
+ ConnectorStatus connectorStatus = Strings.isNullOrEmpty(error) ? ConnectorStatus.CONNECTED : ConnectorStatus.ERROR;
+
final UpdateRequest updateRequest = new UpdateRequest(CONNECTOR_INDEX_NAME, connectorId).doc(
new IndexRequest(CONNECTOR_INDEX_NAME).opType(DocWriteRequest.OpType.INDEX)
.id(connectorId)
@@ -482,6 +486,7 @@ public void updateConnectorError(String connectorId, String error, ActionListene
.source(new HashMap<>() {
{
put(Connector.ERROR_FIELD.getPreferredName(), error);
+ put(Connector.STATUS_FIELD.getPreferredName(), connectorStatus.toString());
}
})
);
diff --git a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java
index e7de5b073b114..12abca3a78591 100644
--- a/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java
+++ b/x-pack/plugin/ent-search/src/test/java/org/elasticsearch/xpack/application/connector/ConnectorIndexServiceTests.java
@@ -27,7 +27,6 @@
import org.elasticsearch.xpack.application.connector.action.ConnectorCreateActionResponse;
import org.elasticsearch.xpack.application.connector.action.UpdateConnectorApiKeyIdAction;
import org.elasticsearch.xpack.application.connector.action.UpdateConnectorConfigurationAction;
-import org.elasticsearch.xpack.application.connector.action.UpdateConnectorErrorAction;
import org.elasticsearch.xpack.application.connector.action.UpdateConnectorIndexNameAction;
import org.elasticsearch.xpack.application.connector.action.UpdateConnectorLastSeenAction;
import org.elasticsearch.xpack.application.connector.action.UpdateConnectorLastSyncStatsAction;
@@ -712,17 +711,14 @@ public void testUpdateConnectorError() throws Exception {
String connectorId = randomUUID();
ConnectorCreateActionResponse resp = awaitCreateConnector(connectorId, connector);
assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK)));
+ String error = randomAlphaOfLengthBetween(5, 15);
- UpdateConnectorErrorAction.Request updateErrorRequest = new UpdateConnectorErrorAction.Request(
- connectorId,
- randomAlphaOfLengthBetween(5, 15)
- );
-
- DocWriteResponse updateResponse = awaitUpdateConnectorError(updateErrorRequest);
+ DocWriteResponse updateResponse = awaitUpdateConnectorError(connectorId, error);
assertThat(updateResponse.status(), equalTo(RestStatus.OK));
Connector indexedConnector = awaitGetConnector(connectorId);
- assertThat(updateErrorRequest.getError(), equalTo(indexedConnector.getError()));
+ assertThat(indexedConnector.getError(), equalTo(error));
+ assertThat(indexedConnector.getStatus(), equalTo(ConnectorStatus.ERROR));
}
public void testUpdateConnectorError_resetWithNull() throws Exception {
@@ -731,13 +727,12 @@ public void testUpdateConnectorError_resetWithNull() throws Exception {
ConnectorCreateActionResponse resp = awaitCreateConnector(connectorId, connector);
assertThat(resp.status(), anyOf(equalTo(RestStatus.CREATED), equalTo(RestStatus.OK)));
- UpdateConnectorErrorAction.Request updateErrorRequest = new UpdateConnectorErrorAction.Request(connectorId, null);
-
- DocWriteResponse updateResponse = awaitUpdateConnectorError(updateErrorRequest);
+ DocWriteResponse updateResponse = awaitUpdateConnectorError(connectorId, null);
assertThat(updateResponse.status(), equalTo(RestStatus.OK));
Connector indexedConnector = awaitGetConnector(connectorId);
- assertThat(updateErrorRequest.getError(), equalTo(indexedConnector.getError()));
+ assertNull(indexedConnector.getError());
+ assertThat(indexedConnector.getStatus(), equalTo(ConnectorStatus.CONNECTED));
}
public void testUpdateConnectorNameOrDescription() throws Exception {
@@ -1347,11 +1342,11 @@ public void onFailure(Exception e) {
return resp.get();
}
- private UpdateResponse awaitUpdateConnectorError(UpdateConnectorErrorAction.Request updatedError) throws Exception {
+ private UpdateResponse awaitUpdateConnectorError(String connectorId, String error) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
final AtomicReference resp = new AtomicReference<>(null);
final AtomicReference exc = new AtomicReference<>(null);
- connectorIndexService.updateConnectorError(updatedError.getConnectorId(), updatedError.getError(), new ActionListener<>() {
+ connectorIndexService.updateConnectorError(connectorId, error, new ActionListener<>() {
@Override
public void onResponse(UpdateResponse indexResponse) {
resp.set(indexResponse);
From abcc38388d4a7c3c63ca0bcc6732e63f9b29090a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lorenzo=20Dematt=C3=A9?=
Date: Thu, 27 Jun 2024 12:18:52 +0200
Subject: [PATCH 006/216] Add request metric to RestController to track
success/failure (by status code) (#109957)
* Propagate TelemetryProvider in place of Tracer
* Add counter for rest requests, with RestHandler name and response status-code attributes
---
docs/changelog/109957.yaml | 6 +
.../elasticsearch/test/CustomRestPlugin.java | 10 +-
.../elasticsearch/rest/RestControllerIT.java | 123 +++++++++++-
.../elasticsearch/action/ActionModule.java | 8 +-
.../elasticsearch/node/NodeConstruction.java | 2 +-
.../interceptor/RestServerActionPlugin.java | 4 +-
.../elasticsearch/rest/RestController.java | 129 ++++++++++---
.../org/elasticsearch/rest/RestHandler.java | 4 +
.../action/ActionModuleTests.java | 14 +-
.../AbstractHttpServerTransportTests.java | 3 +-
.../rest/RestControllerTests.java | 178 ++++++++++++++++--
.../rest/RestHttpResponseHeadersTests.java | 4 +-
.../indices/RestValidateQueryActionTests.java | 10 +-
.../test/rest/RestActionTestCase.java | 4 +-
.../action/RestTermsEnumActionTests.java | 10 +-
.../xpack/security/SecurityTests.java | 3 +-
16 files changed, 437 insertions(+), 75 deletions(-)
create mode 100644 docs/changelog/109957.yaml
diff --git a/docs/changelog/109957.yaml b/docs/changelog/109957.yaml
new file mode 100644
index 0000000000000..6bbcd8175501c
--- /dev/null
+++ b/docs/changelog/109957.yaml
@@ -0,0 +1,6 @@
+pr: 109957
+summary: Add request metric to `RestController` to track success/failure (by status
+ code)
+area: Infra/Metrics
+type: enhancement
+issues: []
diff --git a/qa/custom-rest-controller/src/javaRestTest/java/co/elastic/elasticsearch/test/CustomRestPlugin.java b/qa/custom-rest-controller/src/javaRestTest/java/co/elastic/elasticsearch/test/CustomRestPlugin.java
index 4fbdfa65d40ba..e978e7f2a5c11 100644
--- a/qa/custom-rest-controller/src/javaRestTest/java/co/elastic/elasticsearch/test/CustomRestPlugin.java
+++ b/qa/custom-rest-controller/src/javaRestTest/java/co/elastic/elasticsearch/test/CustomRestPlugin.java
@@ -21,7 +21,7 @@
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.rest.RestInterceptor;
import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.telemetry.tracing.Tracer;
+import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.usage.UsageService;
public class CustomRestPlugin extends Plugin implements RestServerActionPlugin {
@@ -59,9 +59,9 @@ public CustomController(
NodeClient client,
CircuitBreakerService circuitBreakerService,
UsageService usageService,
- Tracer tracer
+ TelemetryProvider telemetryProvider
) {
- super(interceptor, client, circuitBreakerService, usageService, tracer);
+ super(interceptor, client, circuitBreakerService, usageService, telemetryProvider);
}
@Override
@@ -83,9 +83,9 @@ public RestController getRestController(
NodeClient client,
CircuitBreakerService circuitBreakerService,
UsageService usageService,
- Tracer tracer
+ TelemetryProvider telemetryProvider
) {
- return new CustomController(interceptor, client, circuitBreakerService, usageService, tracer);
+ return new CustomController(interceptor, client, circuitBreakerService, usageService, telemetryProvider);
}
}
diff --git a/server/src/internalClusterTest/java/org/elasticsearch/rest/RestControllerIT.java b/server/src/internalClusterTest/java/org/elasticsearch/rest/RestControllerIT.java
index b76bec0652732..7ad464fee92ba 100644
--- a/server/src/internalClusterTest/java/org/elasticsearch/rest/RestControllerIT.java
+++ b/server/src/internalClusterTest/java/org/elasticsearch/rest/RestControllerIT.java
@@ -9,6 +9,7 @@
package org.elasticsearch.rest;
import org.elasticsearch.client.Request;
+import org.elasticsearch.client.ResponseException;
import org.elasticsearch.client.internal.node.NodeClient;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNodes;
@@ -18,18 +19,28 @@
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
-import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.features.NodeFeature;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.plugins.PluginsService;
+import org.elasticsearch.telemetry.Measurement;
+import org.elasticsearch.telemetry.TestTelemetryPlugin;
import org.elasticsearch.test.ESIntegTestCase;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
+import java.util.stream.Stream;
+import static org.hamcrest.Matchers.hasEntry;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numClientNodes = 1, numDataNodes = 0)
public class RestControllerIT extends ESIntegTestCase {
@Override
protected boolean addMockHttpTransport() {
@@ -43,9 +54,117 @@ public void testHeadersEmittedWithChunkedResponses() throws IOException {
assertEquals(ChunkedResponseWithHeadersPlugin.HEADER_VALUE, response.getHeader(ChunkedResponseWithHeadersPlugin.HEADER_NAME));
}
+ public void testMetricsEmittedOnSuccess() throws IOException {
+ final var client = getRestClient();
+ final var request = new Request("GET", TestEchoStatusCodePlugin.ROUTE);
+ request.addParameter("status_code", "200");
+ final var response = client.performRequest(request);
+
+ assertEquals(200, response.getStatusLine().getStatusCode());
+
+ assertMeasurement(metric -> {
+ assertThat(metric.getLong(), is(1L));
+ assertThat(metric.attributes(), hasEntry(RestController.HANDLER_NAME_KEY, TestEchoStatusCodePlugin.NAME));
+ assertThat(metric.attributes(), hasEntry(RestController.REQUEST_METHOD_KEY, "GET"));
+ assertThat(metric.attributes(), hasEntry(RestController.STATUS_CODE_KEY, 200));
+ });
+ }
+
+ public void testMetricsEmittedOnRestError() throws IOException {
+ final var client = getRestClient();
+ final var request = new Request("GET", TestEchoStatusCodePlugin.ROUTE);
+ request.addParameter("status_code", "503");
+ final var response = expectThrows(ResponseException.class, () -> client.performRequest(request));
+
+ assertEquals(503, response.getResponse().getStatusLine().getStatusCode());
+ assertMeasurement(metric -> {
+ assertThat(metric.getLong(), is(1L));
+ assertThat(metric.attributes(), hasEntry(RestController.HANDLER_NAME_KEY, TestEchoStatusCodePlugin.NAME));
+ assertThat(metric.attributes(), hasEntry(RestController.REQUEST_METHOD_KEY, "GET"));
+ assertThat(metric.attributes(), hasEntry(RestController.STATUS_CODE_KEY, 503));
+ });
+ }
+
+ public void testMetricsEmittedOnWrongMethod() throws IOException {
+ final var client = getRestClient();
+ final var request = new Request("DELETE", TestEchoStatusCodePlugin.ROUTE);
+ final var response = expectThrows(ResponseException.class, () -> client.performRequest(request));
+
+ assertEquals(405, response.getResponse().getStatusLine().getStatusCode());
+ assertMeasurement(metric -> {
+ assertThat(metric.getLong(), is(1L));
+ assertThat(metric.attributes(), hasEntry(RestController.STATUS_CODE_KEY, RestStatus.METHOD_NOT_ALLOWED.getStatus()));
+ });
+ }
+
+ private static void assertMeasurement(Consumer measurementConsumer) {
+ var measurements = new ArrayList();
+ for (PluginsService pluginsService : internalCluster().getInstances(PluginsService.class)) {
+ final TestTelemetryPlugin telemetryPlugin = pluginsService.filterPlugins(TestTelemetryPlugin.class).findFirst().orElseThrow();
+ telemetryPlugin.collect();
+
+ final var metrics = telemetryPlugin.getLongCounterMeasurement(RestController.METRIC_REQUESTS_TOTAL);
+ measurements.addAll(metrics);
+ }
+ assertThat(measurements, hasSize(1));
+ measurementConsumer.accept(measurements.get(0));
+ }
+
@Override
protected Collection> nodePlugins() {
- return CollectionUtils.appendToCopy(super.nodePlugins(), ChunkedResponseWithHeadersPlugin.class);
+ return Stream.concat(
+ super.nodePlugins().stream(),
+ Stream.of(ChunkedResponseWithHeadersPlugin.class, TestEchoStatusCodePlugin.class, TestTelemetryPlugin.class)
+ ).toList();
+ }
+
+ public static class TestEchoStatusCodePlugin extends Plugin implements ActionPlugin {
+ static final String ROUTE = "/_test/echo_status_code";
+ static final String NAME = "test_echo_status_code";
+
+ @Override
+ public Collection getRestHandlers(
+ Settings settings,
+ NamedWriteableRegistry namedWriteableRegistry,
+ RestController restController,
+ ClusterSettings clusterSettings,
+ IndexScopedSettings indexScopedSettings,
+ SettingsFilter settingsFilter,
+ IndexNameExpressionResolver indexNameExpressionResolver,
+ Supplier nodesInCluster,
+ Predicate clusterSupportsFeature
+ ) {
+ return List.of(new BaseRestHandler() {
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public List routes() {
+ return List.of(new Route(RestRequest.Method.GET, ROUTE), new Route(RestRequest.Method.POST, ROUTE));
+ }
+
+ @Override
+ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) {
+ var statusCode = request.param("status_code");
+ client.getLocalNodeId();
+ var restStatus = RestStatus.fromCode(Integer.parseInt(statusCode));
+ return channel -> {
+ final var response = RestResponse.chunked(
+ restStatus,
+ ChunkedRestResponseBodyPart.fromXContent(
+ params -> Iterators.single((b, p) -> b.startObject().endObject()),
+ request,
+ channel
+ ),
+ null
+ );
+ channel.sendResponse(response);
+ };
+ }
+ });
+ }
}
public static class ChunkedResponseWithHeadersPlugin extends Plugin implements ActionPlugin {
diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java
index 1c41f2cdff37d..b550755ce7bdd 100644
--- a/server/src/main/java/org/elasticsearch/action/ActionModule.java
+++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java
@@ -405,7 +405,7 @@
import org.elasticsearch.rest.action.synonyms.RestPutSynonymRuleAction;
import org.elasticsearch.rest.action.synonyms.RestPutSynonymsAction;
import org.elasticsearch.tasks.Task;
-import org.elasticsearch.telemetry.tracing.Tracer;
+import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.usage.UsageService;
@@ -470,7 +470,7 @@ public ActionModule(
CircuitBreakerService circuitBreakerService,
UsageService usageService,
SystemIndices systemIndices,
- Tracer tracer,
+ TelemetryProvider telemetryProvider,
ClusterService clusterService,
RerouteService rerouteService,
List> reservedStateHandlers,
@@ -513,12 +513,12 @@ public ActionModule(
var customController = getRestServerComponent(
"REST controller",
actionPlugins,
- restPlugin -> restPlugin.getRestController(restInterceptor, nodeClient, circuitBreakerService, usageService, tracer)
+ restPlugin -> restPlugin.getRestController(restInterceptor, nodeClient, circuitBreakerService, usageService, telemetryProvider)
);
if (customController != null) {
restController = customController;
} else {
- restController = new RestController(restInterceptor, nodeClient, circuitBreakerService, usageService, tracer);
+ restController = new RestController(restInterceptor, nodeClient, circuitBreakerService, usageService, telemetryProvider);
}
reservedClusterStateService = new ReservedClusterStateService(clusterService, rerouteService, reservedStateHandlers);
this.restExtension = restExtension;
diff --git a/server/src/main/java/org/elasticsearch/node/NodeConstruction.java b/server/src/main/java/org/elasticsearch/node/NodeConstruction.java
index bcf8451e5fe54..aa0f9b8552d22 100644
--- a/server/src/main/java/org/elasticsearch/node/NodeConstruction.java
+++ b/server/src/main/java/org/elasticsearch/node/NodeConstruction.java
@@ -883,7 +883,7 @@ record PluginServiceInstances(
circuitBreakerService,
createUsageService(),
systemIndices,
- telemetryProvider.getTracer(),
+ telemetryProvider,
clusterService,
rerouteService,
buildReservedStateHandlers(
diff --git a/server/src/main/java/org/elasticsearch/plugins/interceptor/RestServerActionPlugin.java b/server/src/main/java/org/elasticsearch/plugins/interceptor/RestServerActionPlugin.java
index 44653dcf8b5fe..29e4efe576116 100644
--- a/server/src/main/java/org/elasticsearch/plugins/interceptor/RestServerActionPlugin.java
+++ b/server/src/main/java/org/elasticsearch/plugins/interceptor/RestServerActionPlugin.java
@@ -15,7 +15,7 @@
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestInterceptor;
-import org.elasticsearch.telemetry.tracing.Tracer;
+import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.usage.UsageService;
import java.util.function.UnaryOperator;
@@ -58,7 +58,7 @@ default RestController getRestController(
NodeClient client,
CircuitBreakerService circuitBreakerService,
UsageService usageService,
- Tracer tracer
+ TelemetryProvider telemetryProvider
) {
return null;
}
diff --git a/server/src/main/java/org/elasticsearch/rest/RestController.java b/server/src/main/java/org/elasticsearch/rest/RestController.java
index b08f6ed81017a..3f9c0dbaa11d6 100644
--- a/server/src/main/java/org/elasticsearch/rest/RestController.java
+++ b/server/src/main/java/org/elasticsearch/rest/RestController.java
@@ -39,6 +39,8 @@
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.rest.RestHandler.Route;
import org.elasticsearch.tasks.Task;
+import org.elasticsearch.telemetry.TelemetryProvider;
+import org.elasticsearch.telemetry.metric.LongCounter;
import org.elasticsearch.telemetry.tracing.Tracer;
import org.elasticsearch.transport.Transports;
import org.elasticsearch.usage.SearchUsageHolder;
@@ -86,6 +88,9 @@ public class RestController implements HttpServerTransport.Dispatcher {
static final String ELASTIC_PRODUCT_HTTP_HEADER_VALUE = "Elasticsearch";
static final Set RESERVED_PATHS = Set.of("/__elb_health__", "/__elb_health__/zk", "/_health", "/_health/zk");
private static final BytesReference FAVICON_RESPONSE;
+ public static final String STATUS_CODE_KEY = "es_rest_status_code";
+ public static final String HANDLER_NAME_KEY = "es_rest_handler_name";
+ public static final String REQUEST_METHOD_KEY = "es_rest_request_method";
static {
try (InputStream stream = RestController.class.getResourceAsStream("/config/favicon.ico")) {
@@ -107,18 +112,23 @@ public class RestController implements HttpServerTransport.Dispatcher {
private final UsageService usageService;
private final Tracer tracer;
+ private final LongCounter requestsCounter;
// If true, the ServerlessScope annotations will be enforced
private final ServerlessApiProtections apiProtections;
+ public static final String METRIC_REQUESTS_TOTAL = "es.rest.requests.total";
+
public RestController(
RestInterceptor restInterceptor,
NodeClient client,
CircuitBreakerService circuitBreakerService,
UsageService usageService,
- Tracer tracer
+ TelemetryProvider telemetryProvider
) {
this.usageService = usageService;
- this.tracer = tracer;
+ this.tracer = telemetryProvider.getTracer();
+ this.requestsCounter = telemetryProvider.getMeterRegistry()
+ .registerLongCounter(METRIC_REQUESTS_TOTAL, "The total number of rest requests/responses processed", "unit");
if (restInterceptor == null) {
restInterceptor = (request, channel, targetHandler, listener) -> listener.onResponse(Boolean.TRUE);
}
@@ -355,6 +365,7 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th
sendFailure(channel, (Exception) e.getCause());
} else {
channel.sendResponse(new RestResponse(channel, BAD_REQUEST, e));
+ recordRequestMetric(BAD_REQUEST, requestsCounter);
}
} catch (final IOException e) {
if (cause != null) {
@@ -362,6 +373,7 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th
}
logger.warn("failed to send bad request response", e);
channel.sendResponse(new RestResponse(INTERNAL_SERVER_ERROR, RestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY));
+ recordRequestMetric(INTERNAL_SERVER_ERROR, requestsCounter);
}
}
@@ -502,8 +514,10 @@ public void onFailure(Exception e) {
@SuppressWarnings("unused")
protected void validateRequest(RestRequest request, RestHandler handler, NodeClient client) throws ElasticsearchStatusException {}
- private static void sendFailure(RestChannel responseChannel, Exception e) throws IOException {
- responseChannel.sendResponse(new RestResponse(responseChannel, e));
+ private void sendFailure(RestChannel responseChannel, Exception e) throws IOException {
+ var restResponse = new RestResponse(responseChannel, e);
+ responseChannel.sendResponse(restResponse);
+ recordRequestMetric(restResponse.status(), requestsCounter);
}
/**
@@ -602,6 +616,7 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel
} catch (IllegalArgumentException e) {
startTrace(threadContext, channel);
channel.sendResponse(RestResponse.createSimpleErrorResponse(channel, BAD_REQUEST, e.getMessage()));
+ recordRequestMetric(BAD_REQUEST, requestsCounter);
return;
}
@@ -629,7 +644,8 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel
}
} else {
startTrace(threadContext, channel, handlers.getPath());
- dispatchRequest(request, channel, handler, handlers, threadContext);
+ var decoratedChannel = new MeteringRestChannelDecorator(channel, requestsCounter, handler.getConcreteRestHandler());
+ dispatchRequest(request, decoratedChannel, handler, handlers, threadContext);
return;
}
}
@@ -689,7 +705,7 @@ public SearchUsageHolder getSearchUsageHolder() {
* HTTP/1.1 -
* 10.4.6 - 405 Method Not Allowed).
*/
- private static void handleUnsupportedHttpMethod(
+ private void handleUnsupportedHttpMethod(
String uri,
@Nullable RestRequest.Method method,
final RestChannel channel,
@@ -712,9 +728,11 @@ private static void handleUnsupportedHttpMethod(
restResponse.addHeader("Allow", Strings.collectionToDelimitedString(validMethodSet, ","));
}
channel.sendResponse(restResponse);
+ recordRequestMetric(METHOD_NOT_ALLOWED, requestsCounter);
} catch (final IOException e) {
logger.warn("failed to send bad request response", e);
channel.sendResponse(new RestResponse(INTERNAL_SERVER_ERROR, RestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY));
+ recordRequestMetric(INTERNAL_SERVER_ERROR, requestsCounter);
}
}
@@ -725,7 +743,7 @@ private static void handleUnsupportedHttpMethod(
* HTTP/1.1 - 9.2
* - Options).
*/
- private static void handleOptionsRequest(RestChannel channel, Set validMethodSet) {
+ private void handleOptionsRequest(RestChannel channel, Set validMethodSet) {
RestResponse restResponse = new RestResponse(OK, TEXT_CONTENT_TYPE, BytesArray.EMPTY);
// When we have an OPTIONS HTTP request and no valid handlers, simply send OK by default (with the Access Control Origin header
// which gets automatically added).
@@ -733,13 +751,14 @@ private static void handleOptionsRequest(RestChannel channel, Set getValidHandlerMethodSet(String rawPath) {
return validMethods;
}
- private static final class ResourceHandlingHttpChannel implements RestChannel {
+ private static void recordRequestMetric(RestStatus statusCode, String handlerName, String requestMethod, LongCounter requestsCounter) {
+ try {
+ Map attributes = Map.of(
+ STATUS_CODE_KEY,
+ statusCode.getStatus(),
+ HANDLER_NAME_KEY,
+ handlerName,
+ REQUEST_METHOD_KEY,
+ requestMethod
+ );
+ requestsCounter.incrementBy(1, attributes);
+ } catch (Exception ex) {
+ logger.error("Cannot track request status code", ex);
+ }
+ }
+
+ private static void recordRequestMetric(RestStatus statusCode, LongCounter requestsCounter) {
+ try {
+ Map attributes = Map.of(STATUS_CODE_KEY, statusCode.getStatus());
+ requestsCounter.incrementBy(1, attributes);
+ } catch (Exception ex) {
+ logger.error("Cannot track request status code", ex);
+ }
+ }
+
+ private static class DelegatingRestChannel implements RestChannel {
+
private final RestChannel delegate;
- private final CircuitBreakerService circuitBreakerService;
- private final int contentLength;
- private final MethodHandlers methodHandlers;
- private final long startTime;
- private final AtomicBoolean closed = new AtomicBoolean();
- ResourceHandlingHttpChannel(
- RestChannel delegate,
- CircuitBreakerService circuitBreakerService,
- int contentLength,
- MethodHandlers methodHandlers
- ) {
+ private DelegatingRestChannel(RestChannel delegate) {
this.delegate = delegate;
- this.circuitBreakerService = circuitBreakerService;
- this.contentLength = contentLength;
- this.methodHandlers = methodHandlers;
- this.startTime = rawRelativeTimeInMillis();
}
@Override
@@ -843,6 +874,50 @@ public boolean detailedErrorsEnabled() {
return delegate.detailedErrorsEnabled();
}
+ @Override
+ public void sendResponse(RestResponse response) {
+ delegate.sendResponse(response);
+ }
+ }
+
+ private static final class MeteringRestChannelDecorator extends DelegatingRestChannel {
+
+ private final LongCounter requestsCounter;
+ private final RestHandler restHandler;
+
+ private MeteringRestChannelDecorator(RestChannel delegate, LongCounter requestCounter, RestHandler restHandler) {
+ super(delegate);
+ this.requestsCounter = requestCounter;
+ this.restHandler = restHandler;
+ }
+
+ @Override
+ public void sendResponse(RestResponse response) {
+ super.sendResponse(response);
+ recordRequestMetric(response.status(), restHandler.getName(), request().method().name(), requestsCounter);
+ }
+ }
+
+ private static final class ResourceHandlingHttpChannel extends DelegatingRestChannel {
+ private final CircuitBreakerService circuitBreakerService;
+ private final int contentLength;
+ private final MethodHandlers methodHandlers;
+ private final long startTime;
+ private final AtomicBoolean closed = new AtomicBoolean();
+
+ ResourceHandlingHttpChannel(
+ RestChannel delegate,
+ CircuitBreakerService circuitBreakerService,
+ int contentLength,
+ MethodHandlers methodHandlers
+ ) {
+ super(delegate);
+ this.circuitBreakerService = circuitBreakerService;
+ this.contentLength = contentLength;
+ this.methodHandlers = methodHandlers;
+ this.startTime = rawRelativeTimeInMillis();
+ }
+
@Override
public void sendResponse(RestResponse response) {
boolean success = false;
@@ -866,7 +941,7 @@ public void sendResponse(RestResponse response) {
}
}
}
- delegate.sendResponse(response);
+ super.sendResponse(response);
success = true;
} finally {
if (success == false) {
diff --git a/server/src/main/java/org/elasticsearch/rest/RestHandler.java b/server/src/main/java/org/elasticsearch/rest/RestHandler.java
index c490f68499783..11208a24ceb10 100644
--- a/server/src/main/java/org/elasticsearch/rest/RestHandler.java
+++ b/server/src/main/java/org/elasticsearch/rest/RestHandler.java
@@ -126,6 +126,10 @@ default boolean mediaTypesValid(RestRequest request) {
return request.getXContentType() != null;
}
+ default String getName() {
+ return this.getClass().getSimpleName();
+ }
+
class Route {
private final Method method;
diff --git a/server/src/test/java/org/elasticsearch/action/ActionModuleTests.java b/server/src/test/java/org/elasticsearch/action/ActionModuleTests.java
index 289ab715e3e78..7afa7adedc7bf 100644
--- a/server/src/test/java/org/elasticsearch/action/ActionModuleTests.java
+++ b/server/src/test/java/org/elasticsearch/action/ActionModuleTests.java
@@ -37,7 +37,7 @@
import org.elasticsearch.rest.action.admin.cluster.RestNodesInfoAction;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.tasks.TaskManager;
-import org.elasticsearch.telemetry.tracing.Tracer;
+import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
@@ -123,7 +123,7 @@ public void testSetupRestHandlerContainsKnownBuiltin() {
null,
usageService,
null,
- null,
+ TelemetryProvider.NOOP,
mock(ClusterService.class),
null,
List.of(),
@@ -187,7 +187,7 @@ public String getName() {
null,
usageService,
null,
- null,
+ TelemetryProvider.NOOP,
mock(ClusterService.class),
null,
List.of(),
@@ -244,7 +244,7 @@ public List getRestHandlers(
null,
usageService,
null,
- null,
+ TelemetryProvider.NOOP,
mock(ClusterService.class),
null,
List.of(),
@@ -335,7 +335,7 @@ public void test3rdPartyRestControllerIsNotInstalled() {
null,
usageService,
null,
- null,
+ TelemetryProvider.NOOP,
mock(ClusterService.class),
null,
List.of(),
@@ -388,10 +388,10 @@ public RestController getRestController(
NodeClient client,
CircuitBreakerService circuitBreakerService,
UsageService usageService,
- Tracer tracer
+ TelemetryProvider telemetryProvider
) {
if (installController) {
- return new RestController(interceptor, client, circuitBreakerService, usageService, tracer);
+ return new RestController(interceptor, client, circuitBreakerService, usageService, telemetryProvider);
} else {
return null;
}
diff --git a/server/src/test/java/org/elasticsearch/http/AbstractHttpServerTransportTests.java b/server/src/test/java/org/elasticsearch/http/AbstractHttpServerTransportTests.java
index 8dcecca0f65c0..26087ce5f1f0b 100644
--- a/server/src/test/java/org/elasticsearch/http/AbstractHttpServerTransportTests.java
+++ b/server/src/test/java/org/elasticsearch/http/AbstractHttpServerTransportTests.java
@@ -39,6 +39,7 @@
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.tasks.Task;
+import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.telemetry.tracing.Tracer;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.MockLog;
@@ -1171,7 +1172,7 @@ public Collection getRestHeaders() {
null,
new UsageService(),
null,
- null,
+ TelemetryProvider.NOOP,
mock(ClusterService.class),
null,
List.of(),
diff --git a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java
index 10ea83e59c0ad..67f42e6cf1808 100644
--- a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java
+++ b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java
@@ -36,6 +36,9 @@
import org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService;
import org.elasticsearch.rest.RestHandler.Route;
import org.elasticsearch.rest.action.RestToXContentListener;
+import org.elasticsearch.telemetry.TelemetryProvider;
+import org.elasticsearch.telemetry.metric.LongCounter;
+import org.elasticsearch.telemetry.metric.MeterRegistry;
import org.elasticsearch.telemetry.tracing.Tracer;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.client.NoOpNodeClient;
@@ -66,8 +69,12 @@
import static org.elasticsearch.rest.RestController.ELASTIC_PRODUCT_HTTP_HEADER;
import static org.elasticsearch.rest.RestController.ELASTIC_PRODUCT_HTTP_HEADER_VALUE;
+import static org.elasticsearch.rest.RestController.HANDLER_NAME_KEY;
+import static org.elasticsearch.rest.RestController.REQUEST_METHOD_KEY;
+import static org.elasticsearch.rest.RestController.STATUS_CODE_KEY;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.OPTIONS;
+import static org.elasticsearch.rest.RestRequest.Method.POST;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
@@ -92,6 +99,8 @@ public class RestControllerTests extends ESTestCase {
private TestThreadPool threadPool;
private NodeClient client;
private Tracer tracer;
+ private LongCounter requestsCounter;
+ private TelemetryProvider telemetryProvider;
private List methodList;
@Before
@@ -114,7 +123,16 @@ public void setup() {
threadPool = createThreadPool();
client = new NoOpNodeClient(threadPool);
tracer = mock(Tracer.class);
- restController = new RestController(null, client, circuitBreakerService, usageService, tracer);
+ requestsCounter = mock(LongCounter.class);
+ telemetryProvider = mock(TelemetryProvider.class);
+ var mockMeterRegister = mock(MeterRegistry.class);
+ when(telemetryProvider.getTracer()).thenReturn(tracer);
+ when(telemetryProvider.getMeterRegistry()).thenReturn(mockMeterRegister);
+ when(mockMeterRegister.registerLongCounter(eq(RestController.METRIC_REQUESTS_TOTAL), anyString(), anyString())).thenReturn(
+ requestsCounter
+ );
+
+ restController = new RestController(null, client, circuitBreakerService, usageService, telemetryProvider);
restController.registerHandler(
new Route(GET, "/"),
(request, channel, client) -> channel.sendResponse(
@@ -136,7 +154,7 @@ public void teardown() throws IOException {
public void testApplyProductSpecificResponseHeaders() {
final ThreadContext threadContext = client.threadPool().getThreadContext();
- final RestController restController = new RestController(null, null, circuitBreakerService, usageService, tracer);
+ final RestController restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
RestRequest fakeRequest = new FakeRestRequest.Builder(xContentRegistry()).build();
AssertingChannel channel = new AssertingChannel(fakeRequest, false, RestStatus.BAD_REQUEST);
restController.dispatchRequest(fakeRequest, channel, threadContext);
@@ -152,7 +170,7 @@ public void testRequestWithDisallowedMultiValuedHeader() {
Set headers = new HashSet<>(
Arrays.asList(new RestHeaderDefinition("header.1", true), new RestHeaderDefinition("header.2", false))
);
- final RestController restController = new RestController(null, null, circuitBreakerService, usageService, tracer);
+ final RestController restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
Map> restHeaders = new HashMap<>();
restHeaders.put("header.1", Collections.singletonList("boo"));
restHeaders.put("header.2", List.of("foo", "bar"));
@@ -162,12 +180,122 @@ public void testRequestWithDisallowedMultiValuedHeader() {
assertTrue(channel.getSendResponseCalled());
}
+ public void testDispatchWithNamedHandlerEmitsMetricWithName() {
+ final ThreadContext threadContext = client.threadPool().getThreadContext();
+ final RestController restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
+ RestRequest fakeRequest = new FakeRestRequest.Builder(xContentRegistry()).build();
+ final RestController spyRestController = spy(restController);
+ when(spyRestController.getAllHandlers(any(), eq(fakeRequest.rawPath()))).thenReturn(new Iterator<>() {
+ @Override
+ public boolean hasNext() {
+ return true;
+ }
+
+ @Override
+ public MethodHandlers next() {
+ return new MethodHandlers("/").addMethod(GET, RestApiVersion.current(), new RestHandler() {
+ @Override
+ public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) {
+ channel.sendResponse(new RestResponse(RestStatus.OK, "Test"));
+ }
+
+ @Override
+ public String getName() {
+ return "test_handler_name";
+ }
+ });
+ }
+ });
+ AssertingChannel channel = new AssertingChannel(fakeRequest, false, RestStatus.OK);
+ spyRestController.dispatchRequest(fakeRequest, channel, threadContext);
+ verify(requestsCounter).incrementBy(
+ eq(1L),
+ eq(Map.of(STATUS_CODE_KEY, 200, HANDLER_NAME_KEY, "test_handler_name", REQUEST_METHOD_KEY, fakeRequest.method().name()))
+ );
+ }
+
+ public void testDispatchWithoutANamedHandlerEmitsMetricWithNoName() {
+ final ThreadContext threadContext = client.threadPool().getThreadContext();
+ final RestController restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
+ RestRequest fakeRequest = new FakeRestRequest.Builder(xContentRegistry()).build();
+ final RestController spyRestController = spy(restController);
+ when(spyRestController.getAllHandlers(any(), eq(fakeRequest.rawPath()))).thenReturn(new Iterator<>() {
+ @Override
+ public boolean hasNext() {
+ return false;
+ }
+
+ @Override
+ public MethodHandlers next() {
+ return null;
+ }
+ });
+ AssertingChannel channel = new AssertingChannel(fakeRequest, false, RestStatus.BAD_REQUEST);
+ spyRestController.dispatchRequest(fakeRequest, channel, threadContext);
+ verify(requestsCounter).incrementBy(eq(1L), eq(Map.of(STATUS_CODE_KEY, 400)));
+ }
+
+ public void testDispatchThrowsEmitsMetric() {
+ final ThreadContext threadContext = client.threadPool().getThreadContext();
+ final RestController restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
+ RestRequest fakeRequest = new FakeRestRequest.Builder(xContentRegistry()).build();
+ final RestController spyRestController = spy(restController);
+ when(spyRestController.getAllHandlers(any(), eq(fakeRequest.rawPath()))).thenReturn(new Iterator<>() {
+ @Override
+ public boolean hasNext() {
+ return true;
+ }
+
+ @Override
+ public MethodHandlers next() {
+ throw new IllegalArgumentException();
+ }
+ });
+
+ AssertingChannel channel = new AssertingChannel(fakeRequest, false, RestStatus.BAD_REQUEST);
+ spyRestController.dispatchRequest(fakeRequest, channel, threadContext);
+ verify(requestsCounter).incrementBy(eq(1L), eq(Map.of(STATUS_CODE_KEY, 400)));
+ }
+
+ public void testDispatchNoHandlerEmitsMetric() {
+ final ThreadContext threadContext = client.threadPool().getThreadContext();
+ final RestController restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
+ RestRequest fakeRequest = new FakeRestRequest.Builder(xContentRegistry()).build();
+ final RestController spyRestController = spy(restController);
+ var handlers = List.of(new MethodHandlers("/").addMethod(POST, RestApiVersion.current(), new RestHandler() {
+ @Override
+ public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) {
+ channel.sendResponse(new RestResponse(RestStatus.OK, "Test"));
+ }
+
+ @Override
+ public String getName() {
+ return "test_handler_name";
+ }
+ }));
+ when(spyRestController.getAllHandlers(any(), eq(fakeRequest.rawPath()))).thenAnswer(x -> handlers.iterator());
+
+ AssertingChannel channel = new AssertingChannel(fakeRequest, false, RestStatus.METHOD_NOT_ALLOWED);
+ spyRestController.dispatchRequest(fakeRequest, channel, threadContext);
+ verify(requestsCounter).incrementBy(eq(1L), eq(Map.of(STATUS_CODE_KEY, 405)));
+ }
+
+ public void testDispatchBadRequestEmitsMetric() {
+ final ThreadContext threadContext = client.threadPool().getThreadContext();
+ final RestController restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
+ RestRequest fakeRequest = new FakeRestRequest.Builder(xContentRegistry()).build();
+
+ AssertingChannel channel = new AssertingChannel(fakeRequest, false, RestStatus.BAD_REQUEST);
+ restController.dispatchBadRequest(channel, threadContext, new Exception());
+ verify(requestsCounter).incrementBy(eq(1L), eq(Map.of(STATUS_CODE_KEY, 400)));
+ }
+
/**
* Check that dispatching a request causes a trace span to be started.
*/
public void testDispatchStartsTrace() {
final ThreadContext threadContext = client.threadPool().getThreadContext();
- final RestController restController = new RestController(null, null, circuitBreakerService, usageService, tracer);
+ final RestController restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
RestRequest fakeRequest = new FakeRestRequest.Builder(xContentRegistry()).build();
final RestController spyRestController = spy(restController);
when(spyRestController.getAllHandlers(null, fakeRequest.rawPath())).thenReturn(new Iterator<>() {
@@ -196,7 +324,7 @@ public void testRequestWithDisallowedMultiValuedHeaderButSameValues() {
Set headers = new HashSet<>(
Arrays.asList(new RestHeaderDefinition("header.1", true), new RestHeaderDefinition("header.2", false))
);
- final RestController restController = new RestController(null, client, circuitBreakerService, usageService, tracer);
+ final RestController restController = new RestController(null, client, circuitBreakerService, usageService, telemetryProvider);
Map> restHeaders = new HashMap<>();
restHeaders.put("header.1", Collections.singletonList("boo"));
restHeaders.put("header.2", List.of("foo", "foo"));
@@ -267,7 +395,7 @@ public void testRegisterAsReplacedHandler() {
}
public void testRegisterSecondMethodWithDifferentNamedWildcard() {
- final RestController restController = new RestController(null, null, circuitBreakerService, usageService, tracer);
+ final RestController restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
RestRequest.Method firstMethod = randomFrom(methodList);
RestRequest.Method secondMethod = randomFrom(methodList.stream().filter(m -> m != firstMethod).toList());
@@ -297,7 +425,13 @@ public void testRestInterceptor() throws Exception {
wrapperCalled.set(true);
listener.onResponse(callHandler);
};
- final RestController restController = new RestController(interceptor, client, circuitBreakerService, usageService, tracer);
+ final RestController restController = new RestController(
+ interceptor,
+ client,
+ circuitBreakerService,
+ usageService,
+ telemetryProvider
+ );
restController.registerHandler(new Route(GET, "/wrapped"), handler);
RestRequest request = testRestRequest("/wrapped", "{}", XContentType.JSON);
AssertingChannel channel = new AssertingChannel(request, true, RestStatus.BAD_REQUEST);
@@ -384,7 +518,7 @@ public void testDispatchRequiresContentTypeForRequestsWithContent() {
String content = randomAlphaOfLength((int) Math.round(BREAKER_LIMIT.getBytes() / inFlightRequestsBreaker.getOverhead()));
RestRequest request = testRestRequest("/", content, null);
AssertingChannel channel = new AssertingChannel(request, true, RestStatus.NOT_ACCEPTABLE);
- restController = new RestController(null, null, circuitBreakerService, usageService, tracer);
+ restController = new RestController(null, null, circuitBreakerService, usageService, telemetryProvider);
restController.registerHandler(
new Route(GET, "/"),
(r, c, client) -> c.sendResponse(new RestResponse(RestStatus.OK, RestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY))
@@ -779,7 +913,7 @@ public Method method() {
public void testDispatchCompatibleHandler() {
- RestController restController = new RestController(null, client, circuitBreakerService, usageService, tracer);
+ RestController restController = new RestController(null, client, circuitBreakerService, usageService, telemetryProvider);
final RestApiVersion version = RestApiVersion.minimumSupported();
@@ -803,7 +937,7 @@ public void testDispatchCompatibleHandler() {
public void testDispatchCompatibleRequestToNewlyAddedHandler() {
- RestController restController = new RestController(null, client, circuitBreakerService, usageService, tracer);
+ RestController restController = new RestController(null, client, circuitBreakerService, usageService, telemetryProvider);
final RestApiVersion version = RestApiVersion.minimumSupported();
@@ -846,7 +980,7 @@ private FakeRestRequest requestWithContent(String mediaType) {
}
public void testCurrentVersionVNDMediaTypeIsNotUsingCompatibility() {
- RestController restController = new RestController(null, client, circuitBreakerService, usageService, tracer);
+ RestController restController = new RestController(null, client, circuitBreakerService, usageService, telemetryProvider);
final RestApiVersion version = RestApiVersion.current();
@@ -871,7 +1005,7 @@ public void testCurrentVersionVNDMediaTypeIsNotUsingCompatibility() {
}
public void testCustomMediaTypeValidation() {
- RestController restController = new RestController(null, client, circuitBreakerService, usageService, tracer);
+ RestController restController = new RestController(null, client, circuitBreakerService, usageService, telemetryProvider);
final String mediaType = "application/x-protobuf";
FakeRestRequest fakeRestRequest = requestWithContent(mediaType);
@@ -897,7 +1031,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c
}
public void testBrowserSafelistedContentTypesAreRejected() {
- RestController restController = new RestController(null, client, circuitBreakerService, usageService, tracer);
+ RestController restController = new RestController(null, client, circuitBreakerService, usageService, telemetryProvider);
final String mediaType = randomFrom(RestController.SAFELISTED_MEDIA_TYPES);
FakeRestRequest fakeRestRequest = requestWithContent(mediaType);
@@ -918,7 +1052,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c
}
public void testRegisterWithReservedPath() {
- final RestController restController = new RestController(null, client, circuitBreakerService, usageService, tracer);
+ final RestController restController = new RestController(null, client, circuitBreakerService, usageService, telemetryProvider);
for (String path : RestController.RESERVED_PATHS) {
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> {
restController.registerHandler(
@@ -936,7 +1070,13 @@ public void testRegisterWithReservedPath() {
* Test that when serverless is disabled, all endpoints are available regardless of ServerlessScope annotations.
*/
public void testApiProtectionWithServerlessDisabled() {
- final RestController restController = new RestController(null, client, circuitBreakerService, new UsageService(), tracer);
+ final RestController restController = new RestController(
+ null,
+ client,
+ circuitBreakerService,
+ new UsageService(),
+ telemetryProvider
+ );
restController.registerHandler(new PublicRestHandler());
restController.registerHandler(new InternalRestHandler());
restController.registerHandler(new HiddenRestHandler());
@@ -952,7 +1092,13 @@ public void testApiProtectionWithServerlessDisabled() {
* Test that when serverless is enabled, a normal user can not access endpoints without a ServerlessScope annotation.
*/
public void testApiProtectionWithServerlessEnabledAsEndUser() {
- final RestController restController = new RestController(null, client, circuitBreakerService, new UsageService(), tracer);
+ final RestController restController = new RestController(
+ null,
+ client,
+ circuitBreakerService,
+ new UsageService(),
+ telemetryProvider
+ );
restController.registerHandler(new PublicRestHandler());
restController.registerHandler(new InternalRestHandler());
restController.registerHandler(new HiddenRestHandler());
diff --git a/server/src/test/java/org/elasticsearch/rest/RestHttpResponseHeadersTests.java b/server/src/test/java/org/elasticsearch/rest/RestHttpResponseHeadersTests.java
index 9c38cd2615355..acb1485740238 100644
--- a/server/src/test/java/org/elasticsearch/rest/RestHttpResponseHeadersTests.java
+++ b/server/src/test/java/org/elasticsearch/rest/RestHttpResponseHeadersTests.java
@@ -15,7 +15,7 @@
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService;
import org.elasticsearch.rest.RestHandler.Route;
-import org.elasticsearch.telemetry.tracing.Tracer;
+import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.rest.FakeRestChannel;
import org.elasticsearch.test.rest.FakeRestRequest;
@@ -80,7 +80,7 @@ public void testUnsupportedMethodResponseHttpHeader() throws Exception {
);
UsageService usageService = new UsageService();
- RestController restController = new RestController(null, null, circuitBreakerService, usageService, Tracer.NOOP);
+ RestController restController = new RestController(null, null, circuitBreakerService, usageService, TelemetryProvider.NOOP);
// A basic RestHandler handles requests to the endpoint
RestHandler restHandler = (request, channel, client) -> channel.sendResponse(new RestResponse(RestStatus.OK, ""));
diff --git a/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestValidateQueryActionTests.java b/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestValidateQueryActionTests.java
index 761d2b454b134..59ab7ec719cf4 100644
--- a/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestValidateQueryActionTests.java
+++ b/server/src/test/java/org/elasticsearch/rest/action/admin/indices/RestValidateQueryActionTests.java
@@ -26,7 +26,7 @@
import org.elasticsearch.search.AbstractSearchTestCase;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.tasks.TaskManager;
-import org.elasticsearch.telemetry.tracing.Tracer;
+import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.test.rest.FakeRestChannel;
import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.threadpool.TestThreadPool;
@@ -53,7 +53,13 @@ public class RestValidateQueryActionTests extends AbstractSearchTestCase {
private NodeClient client = new NodeClient(Settings.EMPTY, threadPool);
private UsageService usageService = new UsageService();
- private RestController controller = new RestController(null, client, new NoneCircuitBreakerService(), usageService, Tracer.NOOP);
+ private RestController controller = new RestController(
+ null,
+ client,
+ new NoneCircuitBreakerService(),
+ usageService,
+ TelemetryProvider.NOOP
+ );
private RestValidateQueryAction action = new RestValidateQueryAction();
/**
diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/RestActionTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/rest/RestActionTestCase.java
index fad8575ae1d58..9fed08234f7a4 100644
--- a/test/framework/src/main/java/org/elasticsearch/test/rest/RestActionTestCase.java
+++ b/test/framework/src/main/java/org/elasticsearch/test/rest/RestActionTestCase.java
@@ -18,7 +18,7 @@
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.tasks.Task;
-import org.elasticsearch.telemetry.tracing.Tracer;
+import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.client.NoOpNodeClient;
import org.elasticsearch.threadpool.TestThreadPool;
@@ -45,7 +45,7 @@ public abstract class RestActionTestCase extends ESTestCase {
public void setUpController() {
threadPool = createThreadPool();
verifyingClient = new VerifyingClient(threadPool);
- controller = new RestController(null, verifyingClient, new NoneCircuitBreakerService(), new UsageService(), Tracer.NOOP);
+ controller = new RestController(null, verifyingClient, new NoneCircuitBreakerService(), new UsageService(), TelemetryProvider.NOOP);
}
@After
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/termsenum/action/RestTermsEnumActionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/termsenum/action/RestTermsEnumActionTests.java
index b0ad137f0f1b6..2ea372a84b66c 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/termsenum/action/RestTermsEnumActionTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/termsenum/action/RestTermsEnumActionTests.java
@@ -21,7 +21,7 @@
import org.elasticsearch.search.SearchModule;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.tasks.TaskManager;
-import org.elasticsearch.telemetry.tracing.Tracer;
+import org.elasticsearch.telemetry.TelemetryProvider;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.rest.FakeRestChannel;
import org.elasticsearch.test.rest.FakeRestRequest;
@@ -50,7 +50,13 @@ public class RestTermsEnumActionTests extends ESTestCase {
private static NodeClient client = new NodeClient(Settings.EMPTY, threadPool);
private static UsageService usageService = new UsageService();
- private static RestController controller = new RestController(null, client, new NoneCircuitBreakerService(), usageService, Tracer.NOOP);
+ private static RestController controller = new RestController(
+ null,
+ client,
+ new NoneCircuitBreakerService(),
+ usageService,
+ TelemetryProvider.NOOP
+ );
private static RestTermsEnumAction action = new RestTermsEnumAction();
/**
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java
index 69e8d7b8b681e..1aa40a48ecc97 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java
@@ -61,7 +61,6 @@
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.telemetry.TelemetryProvider;
-import org.elasticsearch.telemetry.tracing.Tracer;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.IndexSettingsModule;
import org.elasticsearch.test.MockLog;
@@ -821,7 +820,7 @@ public void testSecurityRestHandlerInterceptorCanBeInstalled() throws IllegalAcc
null,
usageService,
null,
- Tracer.NOOP,
+ TelemetryProvider.NOOP,
mock(ClusterService.class),
null,
List.of(),
From fc0313f4297a3f90030dbdec73230396bfebecc1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?=
Date: Thu, 27 Jun 2024 13:21:55 +0200
Subject: [PATCH 007/216] ESQL: Add aggregations testing base and docs
(#110042)
- Added a new `AbstractAggregationTestCase` base class for tests, that shares most of the code of function tests, adapted for aggregations. Including both testing and docs generation.
- Reused the `AbstractFunctionTestCase` class to also let us test evaluators if the aggregation is foldable
- Added a `TopListTests` example
- This includes the docs for Top_list _(Also added a missing include of Ip_prefix docs)_
- Adapted Kibana docs to use `type: "agg"` (@drewdaemon)
The current tests are very basic: Consume a page, generate an output,
all in Single aggregation mode (No intermediates, no grouping). More
complex testing will be added in future PRs
Initial PR of https://github.com/elastic/elasticsearch/issues/109917
---
.../functions/aggregation-functions.asciidoc | 2 +
.../functions/description/top_list.asciidoc | 5 +
.../esql/functions/examples/top_list.asciidoc | 13 +
.../esql/functions/ip-functions.asciidoc | 2 +
.../functions/kibana/definition/top_list.json | 107 ++
.../esql/functions/kibana/docs/top_list.md | 11 +
.../esql/functions/layout/top_list.asciidoc | 15 +
.../functions/parameters/top_list.asciidoc | 12 +
.../esql/functions/signature/top_list.svg | 1 +
.../esql/functions/types/top_list.asciidoc | 12 +
.../esql/core/expression/TypeResolutions.java | 22 +
.../function/aggregate/TopList.java | 6 +-
.../function/scalar/package-info.java | 2 +-
.../function/AbstractAggregationTestCase.java | 213 ++++
.../function/AbstractFunctionTestCase.java | 960 ++----------------
.../AbstractScalarFunctionTestCase.java | 884 ++++++++++++++++
.../expression/function/FunctionName.java | 2 +-
.../expression/function/TestCaseSupplier.java | 73 +-
.../function/aggregate/TopListTests.java | 249 +++++
.../function/grouping/BucketTests.java | 4 +-
...AbstractConfigurationFunctionTestCase.java | 4 +-
.../scalar/conditional/CaseTests.java | 4 +-
.../scalar/conditional/GreatestTests.java | 4 +-
.../scalar/conditional/LeastTests.java | 4 +-
.../scalar/convert/FromBase64Tests.java | 4 +-
.../scalar/convert/ToBase64Tests.java | 4 +-
.../scalar/convert/ToBooleanTests.java | 4 +-
.../scalar/convert/ToCartesianPointTests.java | 4 +-
.../scalar/convert/ToCartesianShapeTests.java | 4 +-
.../scalar/convert/ToDatetimeTests.java | 4 +-
.../scalar/convert/ToDegreesTests.java | 4 +-
.../scalar/convert/ToDoubleTests.java | 4 +-
.../scalar/convert/ToGeoPointTests.java | 4 +-
.../scalar/convert/ToGeoShapeTests.java | 4 +-
.../function/scalar/convert/ToIPTests.java | 4 +-
.../scalar/convert/ToIntegerTests.java | 4 +-
.../function/scalar/convert/ToLongTests.java | 4 +-
.../scalar/convert/ToRadiansTests.java | 4 +-
.../scalar/convert/ToStringTests.java | 4 +-
.../scalar/convert/ToUnsignedLongTests.java | 4 +-
.../scalar/convert/ToVersionTests.java | 4 +-
.../function/scalar/date/DateDiffTests.java | 4 +-
.../function/scalar/date/DateParseTests.java | 4 +-
.../function/scalar/date/DateTruncTests.java | 4 +-
.../function/scalar/ip/CIDRMatchTests.java | 4 +-
.../function/scalar/ip/IpPrefixTests.java | 4 +-
.../function/scalar/math/AbsTests.java | 4 +-
.../function/scalar/math/AcosTests.java | 4 +-
.../function/scalar/math/AsinTests.java | 4 +-
.../function/scalar/math/Atan2Tests.java | 4 +-
.../function/scalar/math/AtanTests.java | 4 +-
.../function/scalar/math/CbrtTests.java | 4 +-
.../function/scalar/math/CeilTests.java | 4 +-
.../function/scalar/math/CosTests.java | 4 +-
.../function/scalar/math/CoshTests.java | 4 +-
.../function/scalar/math/ETests.java | 4 +-
.../function/scalar/math/FloorTests.java | 4 +-
.../function/scalar/math/Log10Tests.java | 4 +-
.../function/scalar/math/LogTests.java | 4 +-
.../function/scalar/math/PiTests.java | 4 +-
.../function/scalar/math/PowTests.java | 4 +-
.../function/scalar/math/RoundTests.java | 4 +-
.../function/scalar/math/SignumTests.java | 4 +-
.../function/scalar/math/SinTests.java | 4 +-
.../function/scalar/math/SinhTests.java | 4 +-
.../function/scalar/math/SqrtTests.java | 4 +-
.../function/scalar/math/TanTests.java | 4 +-
.../function/scalar/math/TanhTests.java | 4 +-
.../function/scalar/math/TauTests.java | 4 +-
.../AbstractMultivalueFunctionTestCase.java | 4 +-
.../scalar/multivalue/MvAppendTests.java | 4 +-
.../scalar/multivalue/MvConcatTests.java | 4 +-
.../scalar/multivalue/MvSliceTests.java | 4 +-
.../scalar/multivalue/MvSortTests.java | 4 +-
.../scalar/multivalue/MvZipTests.java | 4 +-
.../function/scalar/nulls/CoalesceTests.java | 4 +-
.../function/scalar/nulls/IsNotNullTests.java | 4 +-
.../function/scalar/nulls/IsNullTests.java | 4 +-
.../BinarySpatialFunctionTestCase.java | 4 +-
.../function/scalar/spatial/StXTests.java | 4 +-
.../function/scalar/spatial/StYTests.java | 4 +-
.../scalar/string/AbstractTrimTests.java | 4 +-
.../function/scalar/string/ConcatTests.java | 4 +-
.../function/scalar/string/EndsWithTests.java | 4 +-
.../function/scalar/string/LeftTests.java | 4 +-
.../function/scalar/string/LengthTests.java | 4 +-
.../function/scalar/string/LocateTests.java | 4 +-
.../function/scalar/string/RLikeTests.java | 4 +-
.../scalar/string/RepeatStaticTests.java | 8 +-
.../function/scalar/string/RepeatTests.java | 4 +-
.../function/scalar/string/ReplaceTests.java | 4 +-
.../function/scalar/string/RightTests.java | 4 +-
.../function/scalar/string/SplitTests.java | 4 +-
.../scalar/string/StartsWithTests.java | 4 +-
.../scalar/string/SubstringTests.java | 4 +-
.../scalar/string/WildcardLikeTests.java | 4 +-
.../AbstractBinaryOperatorTestCase.java | 4 +-
.../predicate/operator/BreakerTests.java | 3 +-
.../operator/arithmetic/AddTests.java | 4 +-
.../operator/arithmetic/DivTests.java | 4 +-
.../operator/arithmetic/ModTests.java | 4 +-
.../operator/arithmetic/MulTests.java | 4 +-
.../operator/arithmetic/NegTests.java | 4 +-
.../operator/arithmetic/SubTests.java | 4 +-
.../operator/comparison/EqualsTests.java | 9 +-
.../comparison/GreaterThanOrEqualTests.java | 9 +-
.../operator/comparison/GreaterThanTests.java | 9 +-
.../comparison/LessThanOrEqualTests.java | 9 +-
.../operator/comparison/LessThanTests.java | 9 +-
.../operator/comparison/NotEqualsTests.java | 9 +-
110 files changed, 1895 insertions(+), 1093 deletions(-)
create mode 100644 docs/reference/esql/functions/description/top_list.asciidoc
create mode 100644 docs/reference/esql/functions/examples/top_list.asciidoc
create mode 100644 docs/reference/esql/functions/kibana/definition/top_list.json
create mode 100644 docs/reference/esql/functions/kibana/docs/top_list.md
create mode 100644 docs/reference/esql/functions/layout/top_list.asciidoc
create mode 100644 docs/reference/esql/functions/parameters/top_list.asciidoc
create mode 100644 docs/reference/esql/functions/signature/top_list.svg
create mode 100644 docs/reference/esql/functions/types/top_list.asciidoc
create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractAggregationTestCase.java
create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java
create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/TopListTests.java
diff --git a/docs/reference/esql/functions/aggregation-functions.asciidoc b/docs/reference/esql/functions/aggregation-functions.asciidoc
index 074fcce9ad43d..cf3512449e26f 100644
--- a/docs/reference/esql/functions/aggregation-functions.asciidoc
+++ b/docs/reference/esql/functions/aggregation-functions.asciidoc
@@ -18,6 +18,7 @@ The <> command supports these aggregate functions:
* <>
* experimental:[] <>
* <>
+* <>
* <>
// end::agg_list[]
@@ -31,4 +32,5 @@ include::min.asciidoc[]
include::percentile.asciidoc[]
include::st_centroid_agg.asciidoc[]
include::sum.asciidoc[]
+include::layout/top_list.asciidoc[]
include::values.asciidoc[]
diff --git a/docs/reference/esql/functions/description/top_list.asciidoc b/docs/reference/esql/functions/description/top_list.asciidoc
new file mode 100644
index 0000000000000..39b31e17aec55
--- /dev/null
+++ b/docs/reference/esql/functions/description/top_list.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*
+
+Collects the top values for a field. Includes repeated values.
diff --git a/docs/reference/esql/functions/examples/top_list.asciidoc b/docs/reference/esql/functions/examples/top_list.asciidoc
new file mode 100644
index 0000000000000..09d32bc9f601a
--- /dev/null
+++ b/docs/reference/esql/functions/examples/top_list.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}/stats_top_list.csv-spec[tag=top-list]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats_top_list.csv-spec[tag=top-list-result]
+|===
+
diff --git a/docs/reference/esql/functions/ip-functions.asciidoc b/docs/reference/esql/functions/ip-functions.asciidoc
index 55c808e587a18..0d58e24c02945 100644
--- a/docs/reference/esql/functions/ip-functions.asciidoc
+++ b/docs/reference/esql/functions/ip-functions.asciidoc
@@ -9,6 +9,8 @@
// tag::ip_list[]
* <>
+* <>
// end::ip_list[]
include::layout/cidr_match.asciidoc[]
+include::layout/ip_prefix.asciidoc[]
diff --git a/docs/reference/esql/functions/kibana/definition/top_list.json b/docs/reference/esql/functions/kibana/definition/top_list.json
new file mode 100644
index 0000000000000..99518a40680ee
--- /dev/null
+++ b/docs/reference/esql/functions/kibana/definition/top_list.json
@@ -0,0 +1,107 @@
+{
+ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
+ "type" : "agg",
+ "name" : "top_list",
+ "description" : "Collects the top values for a field. Includes repeated values.",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "datetime",
+ "optional" : false,
+ "description" : "The field to collect the top values for."
+ },
+ {
+ "name" : "limit",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The maximum number of values to collect."
+ },
+ {
+ "name" : "order",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : "The order to calculate the top values. Either `asc` or `desc`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "datetime"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "double",
+ "optional" : false,
+ "description" : "The field to collect the top values for."
+ },
+ {
+ "name" : "limit",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The maximum number of values to collect."
+ },
+ {
+ "name" : "order",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : "The order to calculate the top values. Either `asc` or `desc`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The field to collect the top values for."
+ },
+ {
+ "name" : "limit",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The maximum number of values to collect."
+ },
+ {
+ "name" : "order",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : "The order to calculate the top values. Either `asc` or `desc`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "field",
+ "type" : "long",
+ "optional" : false,
+ "description" : "The field to collect the top values for."
+ },
+ {
+ "name" : "limit",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "The maximum number of values to collect."
+ },
+ {
+ "name" : "order",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : "The order to calculate the top values. Either `asc` or `desc`."
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ }
+ ],
+ "examples" : [
+ "FROM employees\n| STATS top_salaries = TOP_LIST(salary, 3, \"desc\"), top_salary = MAX(salary)"
+ ]
+}
diff --git a/docs/reference/esql/functions/kibana/docs/top_list.md b/docs/reference/esql/functions/kibana/docs/top_list.md
new file mode 100644
index 0000000000000..f7acdf3162b38
--- /dev/null
+++ b/docs/reference/esql/functions/kibana/docs/top_list.md
@@ -0,0 +1,11 @@
+
+
+### TOP_LIST
+Collects the top values for a field. Includes repeated values.
+
+```
+FROM employees
+| STATS top_salaries = TOP_LIST(salary, 3, "desc"), top_salary = MAX(salary)
+```
diff --git a/docs/reference/esql/functions/layout/top_list.asciidoc b/docs/reference/esql/functions/layout/top_list.asciidoc
new file mode 100644
index 0000000000000..4735395ca0c0d
--- /dev/null
+++ b/docs/reference/esql/functions/layout/top_list.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-top_list]]
+=== `TOP_LIST`
+
+*Syntax*
+
+[.text-center]
+image::esql/functions/signature/top_list.svg[Embedded,opts=inline]
+
+include::../parameters/top_list.asciidoc[]
+include::../description/top_list.asciidoc[]
+include::../types/top_list.asciidoc[]
+include::../examples/top_list.asciidoc[]
diff --git a/docs/reference/esql/functions/parameters/top_list.asciidoc b/docs/reference/esql/functions/parameters/top_list.asciidoc
new file mode 100644
index 0000000000000..979bca393b5aa
--- /dev/null
+++ b/docs/reference/esql/functions/parameters/top_list.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*
+
+`field`::
+The field to collect the top values for.
+
+`limit`::
+The maximum number of values to collect.
+
+`order`::
+The order to calculate the top values. Either `asc` or `desc`.
diff --git a/docs/reference/esql/functions/signature/top_list.svg b/docs/reference/esql/functions/signature/top_list.svg
new file mode 100644
index 0000000000000..e7a5c7a292d41
--- /dev/null
+++ b/docs/reference/esql/functions/signature/top_list.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/esql/functions/types/top_list.asciidoc b/docs/reference/esql/functions/types/top_list.asciidoc
new file mode 100644
index 0000000000000..1874cd8b12bf3
--- /dev/null
+++ b/docs/reference/esql/functions/types/top_list.asciidoc
@@ -0,0 +1,12 @@
+// 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=|]
+|===
+field | limit | order | result
+datetime | integer | keyword | datetime
+double | integer | keyword | double
+integer | integer | keyword | integer
+long | integer | keyword | long
+|===
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/TypeResolutions.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/TypeResolutions.java
index 588b0a2af55d3..7302d08f81925 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/TypeResolutions.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/TypeResolutions.java
@@ -132,6 +132,28 @@ public static TypeResolution isFoldable(Expression e, String operationName, Para
return TypeResolution.TYPE_RESOLVED;
}
+ public static TypeResolution isNotNullAndFoldable(Expression e, String operationName, ParamOrdinal paramOrd) {
+ TypeResolution resolution = isFoldable(e, operationName, paramOrd);
+
+ if (resolution.unresolved()) {
+ return resolution;
+ }
+
+ if (e.dataType() == DataType.NULL || e.fold() == null) {
+ resolution = new TypeResolution(
+ format(
+ null,
+ "{}argument of [{}] cannot be null, received [{}]",
+ paramOrd == null || paramOrd == DEFAULT ? "" : paramOrd.name().toLowerCase(Locale.ROOT) + " ",
+ operationName,
+ Expressions.name(e)
+ )
+ );
+ }
+
+ return resolution;
+ }
+
public static TypeResolution isNotFoldable(Expression e, String operationName, ParamOrdinal paramOrd) {
if (e.foldable()) {
return new TypeResolution(
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/TopList.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/TopList.java
index 93e3da7c19cf8..16cfdad89612b 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/TopList.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/TopList.java
@@ -36,7 +36,7 @@
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.isFoldable;
+import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isNotNullAndFoldable;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType;
@@ -125,9 +125,9 @@ protected TypeResolution resolveType() {
sourceText(),
FIRST,
"numeric except unsigned_long or counter types"
- ).and(isFoldable(limitField(), sourceText(), SECOND))
+ ).and(isNotNullAndFoldable(limitField(), sourceText(), SECOND))
.and(isType(limitField(), dt -> dt == DataType.INTEGER, sourceText(), SECOND, "integer"))
- .and(isFoldable(orderField(), sourceText(), THIRD))
+ .and(isNotNullAndFoldable(orderField(), sourceText(), THIRD))
.and(isString(orderField(), sourceText(), THIRD));
if (typeResolution.unresolved()) {
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java
index 2e40ee1634d1b..cd88619c4fdbe 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/package-info.java
@@ -133,7 +133,7 @@
*
*
* Now it's time to make a unit test! The infrastructure for these is under some flux at
- * the moment, but it's good to extend from {@code AbstractFunctionTestCase}. All of
+ * the moment, but it's good to extend from {@code AbstractScalarFunctionTestCase}. All of
* these tests are parameterized and expect to spend some time finding good parameters.
*
*
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractAggregationTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractAggregationTestCase.java
new file mode 100644
index 0000000000000..05a6cec51284f
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractAggregationTestCase.java
@@ -0,0 +1,213 @@
+/*
+ * 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;
+
+import org.elasticsearch.compute.aggregation.Aggregator;
+import org.elasticsearch.compute.aggregation.AggregatorFunctionSupplier;
+import org.elasticsearch.compute.aggregation.AggregatorMode;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.ElementType;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.core.util.NumericUtils;
+import org.elasticsearch.xpack.esql.expression.SurrogateExpression;
+import org.elasticsearch.xpack.esql.expression.function.aggregate.AggregateFunction;
+import org.elasticsearch.xpack.esql.optimizer.FoldNull;
+import org.elasticsearch.xpack.esql.planner.PlannerUtils;
+import org.elasticsearch.xpack.esql.planner.ToAggregator;
+
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.elasticsearch.compute.data.BlockUtils.toJavaObject;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.not;
+
+/**
+ * Base class for aggregation tests.
+ */
+public abstract class AbstractAggregationTestCase extends AbstractFunctionTestCase {
+ /**
+ * Converts a list of aggregation test cases into a list of parameter suppliers.
+ * Also, adds a default set of extra test cases.
+ *
+ * Use if possible, as this method may get updated with new checks in the future.
+ *
+ */
+ protected static Iterable
- *
- * @param entirelyNullPreservesType See {@link #anyNullIsNull(boolean, List)}
- */
- protected static Iterable parameterSuppliersFromTypedDataWithDefaultChecks(
- boolean entirelyNullPreservesType,
- List suppliers
- ) {
- return parameterSuppliersFromTypedData(
- errorsForCasesWithoutExamples(anyNullIsNull(entirelyNullPreservesType, randomizeBytesRefsOffset(suppliers)))
- );
- }
-
/**
* Build an {@link Attribute} that loads a field.
*/
@@ -224,6 +188,7 @@ protected final Expression buildDeepCopyOfFieldExpression(TestCaseSupplier.TestC
}
protected final Expression buildLiteralExpression(TestCaseSupplier.TestCase testCase) {
+ assumeTrue("Data can't be converted to literals", testCase.canGetDataAsLiterals());
return build(testCase.getSource(), testCase.getDataAsLiterals());
}
@@ -249,6 +214,29 @@ protected final Page row(List values) {
return new Page(1, BlockUtils.fromListRow(TestBlockFactory.getNonBreakingInstance(), values));
}
+ /**
+ * Creates a page based on a list of lists, where each list represents a column.
+ */
+ protected final Page rows(List> values) {
+ if (values.isEmpty()) {
+ return new Page(0, BlockUtils.NO_BLOCKS);
+ }
+
+ var rowsCount = values.get(0).size();
+
+ values.stream().skip(1).forEach(l -> assertThat("All multi-row fields must have the same number of rows", l, hasSize(rowsCount)));
+
+ var rows = new ArrayList>();
+ for (int i = 0; i < rowsCount; i++) {
+ final int index = i;
+ rows.add(values.stream().map(l -> l.get(index)).toList());
+ }
+
+ var blocks = BlockUtils.fromList(TestBlockFactory.getNonBreakingInstance(), rows);
+
+ return new Page(rowsCount, blocks);
+ }
+
/**
* Hack together a layout by scanning for Fields.
* Those will show up in the layout in whatever order a depth first traversal finds them.
@@ -263,49 +251,7 @@ protected static void buildLayout(Layout.Builder builder, Expression e) {
}
}
- protected final void assertResolveTypeValid(Expression expression, DataType expectedType) {
- assertTrue(expression.typeResolved().resolved());
- assertThat(expression.dataType(), equalTo(expectedType));
- }
-
- public final void testEvaluate() {
- assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
- boolean readFloating = randomBoolean();
- Expression expression = readFloating ? buildDeepCopyOfFieldExpression(testCase) : buildFieldExpression(testCase);
- if (testCase.getExpectedTypeError() != null) {
- assertTypeResolutionFailure(expression);
- return;
- }
- assumeTrue("Expected type must be representable to build an evaluator", EsqlDataTypes.isRepresentable(testCase.expectedType()));
- logger.info(
- "Test Values: " + testCase.getData().stream().map(TestCaseSupplier.TypedData::toString).collect(Collectors.joining(","))
- );
- Expression.TypeResolution resolution = expression.typeResolved();
- if (resolution.unresolved()) {
- throw new AssertionError("expected resolved " + resolution.message());
- }
- expression = new FoldNull().rule(expression);
- assertThat(expression.dataType(), equalTo(testCase.expectedType()));
- logger.info("Result type: " + expression.dataType());
-
- Object result;
- try (ExpressionEvaluator evaluator = evaluator(expression).get(driverContext())) {
- try (Block block = evaluator.eval(row(testCase.getDataValues()))) {
- result = toJavaObjectUnsignedLongAware(block, 0);
- }
- }
- assertThat(result, not(equalTo(Double.NaN)));
- assert testCase.getMatcher().matches(Double.POSITIVE_INFINITY) == false;
- assertThat(result, not(equalTo(Double.POSITIVE_INFINITY)));
- assert testCase.getMatcher().matches(Double.NEGATIVE_INFINITY) == false;
- assertThat(result, not(equalTo(Double.NEGATIVE_INFINITY)));
- assertThat(result, testCase.getMatcher());
- if (testCase.getExpectedWarnings() != null) {
- assertWarnings(testCase.getExpectedWarnings());
- }
- }
-
- private Object toJavaObjectUnsignedLongAware(Block block, int position) {
+ protected Object toJavaObjectUnsignedLongAware(Block block, int position) {
Object result;
result = toJavaObject(block, position);
if (result != null && testCase.expectedType() == DataType.UNSIGNED_LONG) {
@@ -315,266 +261,44 @@ private Object toJavaObjectUnsignedLongAware(Block block, int position) {
return result;
}
- /**
- * Evaluates a {@link Block} of values, all copied from the input pattern..
- *
- * Note that this'll sometimes be a {@link Vector} of values if the
- * input pattern contained only a single value.
- *
- */
- public final void testEvaluateBlockWithoutNulls() {
- assumeTrue("no warning is expected", testCase.getExpectedWarnings() == null);
- try {
- testEvaluateBlock(driverContext().blockFactory(), driverContext(), false);
- } catch (CircuitBreakingException ex) {
- assertThat(ex.getMessage(), equalTo(MockBigArrays.ERROR_MESSAGE));
- assertFalse("Test data is too large to fit in the memory", true);
- }
- }
-
- /**
- * Evaluates a {@link Block} of values, all copied from the input pattern with
- * some null values inserted between.
- */
- public final void testEvaluateBlockWithNulls() {
- assumeTrue("no warning is expected", testCase.getExpectedWarnings() == null);
- try {
- testEvaluateBlock(driverContext().blockFactory(), driverContext(), true);
- } catch (CircuitBreakingException ex) {
- assertThat(ex.getMessage(), equalTo(MockBigArrays.ERROR_MESSAGE));
- assertFalse("Test data is too large to fit in the memory", true);
- }
+ protected void assertSimpleWithNulls(List data, Block value, int nullBlock) {
+ // TODO remove me in favor of cases containing null
+ assertTrue("argument " + nullBlock + " is null", value.isNull(0));
}
/**
- * Evaluates a {@link Block} of values, all copied from the input pattern,
- * using the {@link CrankyCircuitBreakerService} which fails randomly.
- *
- * Note that this'll sometimes be a {@link Vector} of values if the
- * input pattern contained only a single value.
- *
+ * Modifies suppliers to generate BytesRefs with random offsets.
*/
- public final void testCrankyEvaluateBlockWithoutNulls() {
- assumeTrue("sometimes the cranky breaker silences warnings, just skip these cases", testCase.getExpectedWarnings() == null);
- try {
- testEvaluateBlock(driverContext().blockFactory(), crankyContext(), false);
- } catch (CircuitBreakingException ex) {
- assertThat(ex.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE));
- }
- }
+ protected static List randomizeBytesRefsOffset(List testCaseSuppliers) {
+ return testCaseSuppliers.stream().map(supplier -> new TestCaseSupplier(supplier.name(), supplier.types(), () -> {
+ var testCase = supplier.supplier().get();
- /**
- * Evaluates a {@link Block} of values, all copied from the input pattern with
- * some null values inserted between, using the {@link CrankyCircuitBreakerService} which fails randomly.
- */
- public final void testCrankyEvaluateBlockWithNulls() {
- assumeTrue("sometimes the cranky breaker silences warnings, just skip these cases", testCase.getExpectedWarnings() == null);
- try {
- testEvaluateBlock(driverContext().blockFactory(), crankyContext(), true);
- } catch (CircuitBreakingException ex) {
- assertThat(ex.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE));
- }
- }
+ var newData = testCase.getData().stream().map(typedData -> {
+ if (typedData.data() instanceof BytesRef bytesRef) {
+ var offset = randomIntBetween(0, 10);
+ var extraLength = randomIntBetween(0, 10);
+ var newBytesArray = randomByteArrayOfLength(bytesRef.length + offset + extraLength);
- /**
- * Does the function produce the same output regardless of input?
- */
- protected Matcher allNullsMatcher() {
- return nullValue();
- }
+ System.arraycopy(bytesRef.bytes, bytesRef.offset, newBytesArray, offset, bytesRef.length);
- private void testEvaluateBlock(BlockFactory inputBlockFactory, DriverContext context, boolean insertNulls) {
- Expression expression = randomBoolean() ? buildDeepCopyOfFieldExpression(testCase) : buildFieldExpression(testCase);
- if (testCase.getExpectedTypeError() != null) {
- assertTypeResolutionFailure(expression);
- return;
- }
- assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
- assumeTrue("Expected type must be representable to build an evaluator", EsqlDataTypes.isRepresentable(testCase.expectedType()));
- int positions = between(1, 1024);
- List data = testCase.getData();
- Page onePositionPage = row(testCase.getDataValues());
- Block[] manyPositionsBlocks = new Block[Math.toIntExact(data.stream().filter(d -> d.isForceLiteral() == false).count())];
- Set nullPositions = insertNulls
- ? IntStream.range(0, positions).filter(i -> randomBoolean()).mapToObj(Integer::valueOf).collect(Collectors.toSet())
- : Set.of();
- if (nullPositions.size() == positions) {
- nullPositions = Set.of();
- }
- try {
- int b = 0;
- for (TestCaseSupplier.TypedData d : data) {
- if (d.isForceLiteral()) {
- continue;
- }
- ElementType elementType = PlannerUtils.toElementType(d.type());
- try (Block.Builder builder = elementType.newBlockBuilder(positions, inputBlockFactory)) {
- for (int p = 0; p < positions; p++) {
- if (nullPositions.contains(p)) {
- builder.appendNull();
- } else {
- builder.copyFrom(onePositionPage.getBlock(b), 0, 1);
- }
- }
- manyPositionsBlocks[b] = builder.build();
- }
- b++;
- }
- try (
- ExpressionEvaluator eval = evaluator(expression).get(context);
- Block block = eval.eval(new Page(positions, manyPositionsBlocks))
- ) {
- for (int p = 0; p < positions; p++) {
- if (nullPositions.contains(p)) {
- assertThat(toJavaObject(block, p), allNullsMatcher());
- continue;
- }
- assertThat(toJavaObjectUnsignedLongAware(block, p), testCase.getMatcher());
- }
- assertThat(
- "evaluates to tracked block",
- block.blockFactory(),
- either(sameInstance(context.blockFactory())).or(sameInstance(inputBlockFactory))
- );
- }
- } finally {
- Releasables.close(onePositionPage::releaseBlocks, Releasables.wrap(manyPositionsBlocks));
- }
- if (testCase.getExpectedWarnings() != null) {
- assertWarnings(testCase.getExpectedWarnings());
- }
- }
+ var newBytesRef = new BytesRef(newBytesArray, offset, bytesRef.length);
- public void testSimpleWithNulls() { // TODO replace this with nulls inserted into the test case like anyNullIsNull
- Expression expression = buildFieldExpression(testCase);
- if (testCase.getExpectedTypeError() != null) {
- assertTypeResolutionFailure(expression);
- return;
- }
- assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
- List simpleData = testCase.getDataValues();
- try (EvalOperator.ExpressionEvaluator eval = evaluator(expression).get(driverContext())) {
- BlockFactory blockFactory = TestBlockFactory.getNonBreakingInstance();
- Block[] orig = BlockUtils.fromListRow(blockFactory, simpleData);
- for (int i = 0; i < orig.length; i++) {
- List data = new ArrayList<>();
- Block[] blocks = new Block[orig.length];
- for (int b = 0; b < blocks.length; b++) {
- if (b == i) {
- blocks[b] = orig[b].elementType().newBlockBuilder(1, blockFactory).appendNull().build();
- data.add(null);
- } else {
- blocks[b] = orig[b];
- data.add(simpleData.get(b));
- }
+ return typedData.withData(newBytesRef);
}
- try (Block block = eval.eval(new Page(blocks))) {
- assertSimpleWithNulls(data, block, i);
- }
- }
-
- // Note: the null-in-fast-null-out handling prevents any exception from being thrown, so the warnings provided in some test
- // cases won't actually be registered. This isn't an issue for unary functions, but could be an issue for n-ary ones, if
- // function processing of the first parameter(s) could raise an exception/warning. (But hasn't been the case so far.)
- // N-ary non-MV functions dealing with one multivalue (before hitting the null parameter injected above) will now trigger
- // a warning ("SV-function encountered a MV") that thus needs to be checked.
- if (this instanceof AbstractMultivalueFunctionTestCase == false
- && simpleData.stream().anyMatch(List.class::isInstance)
- && testCase.getExpectedWarnings() != null) {
- assertWarnings(testCase.getExpectedWarnings());
- }
- }
- }
-
- protected void assertSimpleWithNulls(List data, Block value, int nullBlock) {
- // TODO remove me in favor of cases containing null
- assertTrue("argument " + nullBlock + " is null", value.isNull(0));
- }
-
- public final void testEvaluateInManyThreads() throws ExecutionException, InterruptedException {
- Expression expression = buildFieldExpression(testCase);
- if (testCase.getExpectedTypeError() != null) {
- assertTypeResolutionFailure(expression);
- return;
- }
- assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
- assumeTrue("Expected type must be representable to build an evaluator", EsqlDataTypes.isRepresentable(testCase.expectedType()));
- int count = 10_000;
- int threads = 5;
- var evalSupplier = evaluator(expression);
- ExecutorService exec = Executors.newFixedThreadPool(threads);
- try {
- List> futures = new ArrayList<>();
- for (int i = 0; i < threads; i++) {
- List simpleData = testCase.getDataValues();
- Page page = row(simpleData);
-
- futures.add(exec.submit(() -> {
- try (EvalOperator.ExpressionEvaluator eval = evalSupplier.get(driverContext())) {
- for (int c = 0; c < count; c++) {
- try (Block block = eval.eval(page)) {
- assertThat(toJavaObjectUnsignedLongAware(block, 0), testCase.getMatcher());
- }
- }
- }
- }));
- }
- for (Future> f : futures) {
- f.get();
- }
- } finally {
- exec.shutdown();
- }
- }
-
- public final void testEvaluatorToString() {
- Expression expression = buildFieldExpression(testCase);
- if (testCase.getExpectedTypeError() != null) {
- assertTypeResolutionFailure(expression);
- return;
- }
- assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
- var factory = evaluator(expression);
- try (ExpressionEvaluator ev = factory.get(driverContext())) {
- assertThat(ev.toString(), testCase.evaluatorToString());
- }
- }
-
- public final void testFactoryToString() {
- Expression expression = buildFieldExpression(testCase);
- if (testCase.getExpectedTypeError() != null) {
- assertTypeResolutionFailure(expression);
- return;
- }
- assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
- var factory = evaluator(buildFieldExpression(testCase));
- assertThat(factory.toString(), testCase.evaluatorToString());
- }
+ return typedData;
+ }).toList();
- public final void testFold() {
- Expression expression = buildLiteralExpression(testCase);
- if (testCase.getExpectedTypeError() != null) {
- assertTypeResolutionFailure(expression);
- return;
- }
- assertFalse(expression.typeResolved().unresolved());
- Expression nullOptimized = new FoldNull().rule(expression);
- assertThat(nullOptimized.dataType(), equalTo(testCase.expectedType()));
- assertTrue(nullOptimized.foldable());
- if (testCase.foldingExceptionClass() == null) {
- Object result = nullOptimized.fold();
- // Decode unsigned longs into BigIntegers
- if (testCase.expectedType() == DataType.UNSIGNED_LONG && result != null) {
- result = NumericUtils.unsignedLongAsBigInteger((Long) result);
- }
- assertThat(result, testCase.getMatcher());
- if (testCase.getExpectedWarnings() != null) {
- assertWarnings(testCase.getExpectedWarnings());
- }
- } else {
- Throwable t = expectThrows(testCase.foldingExceptionClass(), nullOptimized::fold);
- assertThat(t.getMessage(), equalTo(testCase.foldingExceptionMessage()));
- }
+ return new TestCaseSupplier.TestCase(
+ newData,
+ testCase.evaluatorToString(),
+ testCase.expectedType(),
+ testCase.getMatcher(),
+ testCase.getExpectedWarnings(),
+ testCase.getExpectedTypeError(),
+ testCase.foldingExceptionClass(),
+ testCase.foldingExceptionMessage()
+ );
+ })).toList();
}
public void testSerializationOfSimple() {
@@ -625,558 +349,6 @@ public static void testFunctionInfo() {
Set returnTypes = Arrays.stream(description.returnType()).collect(Collectors.toCollection(TreeSet::new));
assertEquals(returnFromSignature, returnTypes);
-
- }
-
- /**
- * Adds cases with {@code null} and asserts that the result is {@code null}.
- *
- * Note: This won't add more than a single null to any existing test case,
- * just to keep the number of test cases from exploding totally.
- *
- *
- * @param entirelyNullPreservesType should a test case that only contains parameters
- * with the {@code null} type keep it's expected type?
- * This is mostly going to be {@code true}
- * except for functions that base their type entirely
- * on input types like {@link Greatest} or {@link Coalesce}.
- */
- protected static List anyNullIsNull(boolean entirelyNullPreservesType, List testCaseSuppliers) {
- return anyNullIsNull(
- testCaseSuppliers,
- (nullPosition, nullValueDataType, original) -> entirelyNullPreservesType == false
- && nullValueDataType == DataType.NULL
- && original.getData().size() == 1 ? DataType.NULL : original.expectedType(),
- (nullPosition, nullData, original) -> original
- );
- }
-
- public interface ExpectedType {
- DataType expectedType(int nullPosition, DataType nullValueDataType, TestCaseSupplier.TestCase original);
- }
-
- public interface ExpectedEvaluatorToString {
- Matcher evaluatorToString(int nullPosition, TestCaseSupplier.TypedData nullData, Matcher original);
- }
-
- /**
- * Modifies suppliers to generate BytesRefs with random offsets.
- */
- protected static List randomizeBytesRefsOffset(List testCaseSuppliers) {
- return testCaseSuppliers.stream().map(supplier -> new TestCaseSupplier(supplier.name(), supplier.types(), () -> {
- var testCase = supplier.supplier().get();
-
- var newData = testCase.getData().stream().map(typedData -> {
- if (typedData.data() instanceof BytesRef bytesRef) {
- var offset = randomIntBetween(0, 10);
- var extraLength = randomIntBetween(0, 10);
- var newBytesArray = randomByteArrayOfLength(bytesRef.length + offset + extraLength);
-
- System.arraycopy(bytesRef.bytes, bytesRef.offset, newBytesArray, offset, bytesRef.length);
-
- var newBytesRef = new BytesRef(newBytesArray, offset, bytesRef.length);
- var newTypedData = new TestCaseSupplier.TypedData(newBytesRef, typedData.type(), typedData.name());
-
- if (typedData.isForceLiteral()) {
- newTypedData.forceLiteral();
- }
-
- return newTypedData;
- }
- return typedData;
- }).toList();
-
- return new TestCaseSupplier.TestCase(
- newData,
- testCase.evaluatorToString(),
- testCase.expectedType(),
- testCase.getMatcher(),
- testCase.getExpectedWarnings(),
- testCase.getExpectedTypeError(),
- testCase.foldingExceptionClass(),
- testCase.foldingExceptionMessage()
- );
- })).toList();
- }
-
- protected static List anyNullIsNull(
- List testCaseSuppliers,
- ExpectedType expectedType,
- ExpectedEvaluatorToString evaluatorToString
- ) {
- typesRequired(testCaseSuppliers);
- List suppliers = new ArrayList<>(testCaseSuppliers.size());
- suppliers.addAll(testCaseSuppliers);
-
- /*
- * For each original test case, add as many copies as there were
- * arguments, replacing one of the arguments with null and keeping
- * the others.
- *
- * Also, if this was the first time we saw the signature we copy it
- * *again*, replacing the argument with null, but annotating the
- * argument's type as `null` explicitly.
- */
- Set> uniqueSignatures = new HashSet<>();
- for (TestCaseSupplier original : testCaseSuppliers) {
- boolean firstTimeSeenSignature = uniqueSignatures.add(original.types());
- for (int nullPosition = 0; nullPosition < original.types().size(); nullPosition++) {
- int finalNullPosition = nullPosition;
- suppliers.add(new TestCaseSupplier(original.name() + " null in " + nullPosition, original.types(), () -> {
- TestCaseSupplier.TestCase oc = original.get();
- List data = IntStream.range(0, oc.getData().size()).mapToObj(i -> {
- TestCaseSupplier.TypedData od = oc.getData().get(i);
- return i == finalNullPosition ? od.forceValueToNull() : od;
- }).toList();
- TestCaseSupplier.TypedData nulledData = oc.getData().get(finalNullPosition);
- return new TestCaseSupplier.TestCase(
- data,
- evaluatorToString.evaluatorToString(finalNullPosition, nulledData, oc.evaluatorToString()),
- expectedType.expectedType(finalNullPosition, nulledData.type(), oc),
- nullValue(),
- null,
- oc.getExpectedTypeError(),
- null,
- null
- );
- }));
-
- if (firstTimeSeenSignature) {
- List typesWithNull = IntStream.range(0, original.types().size())
- .mapToObj(i -> i == finalNullPosition ? DataType.NULL : original.types().get(i))
- .toList();
- boolean newSignature = uniqueSignatures.add(typesWithNull);
- if (newSignature) {
- suppliers.add(new TestCaseSupplier(typesWithNull, () -> {
- TestCaseSupplier.TestCase oc = original.get();
- List data = IntStream.range(0, oc.getData().size())
- .mapToObj(i -> i == finalNullPosition ? TestCaseSupplier.TypedData.NULL : oc.getData().get(i))
- .toList();
- return new TestCaseSupplier.TestCase(
- data,
- equalTo("LiteralsEvaluator[lit=null]"),
- expectedType.expectedType(finalNullPosition, DataType.NULL, oc),
- nullValue(),
- null,
- oc.getExpectedTypeError(),
- null,
- null
- );
- }));
- }
- }
- }
- }
-
- return suppliers;
-
- }
-
- /**
- * Adds test cases containing unsupported parameter types that assert
- * that they throw type errors.
- */
- protected static List errorsForCasesWithoutExamples(List testCaseSuppliers) {
- return errorsForCasesWithoutExamples(testCaseSuppliers, AbstractFunctionTestCase::typeErrorMessage);
- }
-
- protected static List errorsForCasesWithoutExamples(
- List testCaseSuppliers,
- TypeErrorMessageSupplier typeErrorMessageSupplier
- ) {
- typesRequired(testCaseSuppliers);
- List suppliers = new ArrayList<>(testCaseSuppliers.size());
- suppliers.addAll(testCaseSuppliers);
-
- Set> valid = testCaseSuppliers.stream().map(TestCaseSupplier::types).collect(Collectors.toSet());
- List> validPerPosition = validPerPosition(valid);
-
- testCaseSuppliers.stream()
- .map(s -> s.types().size())
- .collect(Collectors.toSet())
- .stream()
- .flatMap(count -> allPermutations(count))
- .filter(types -> valid.contains(types) == false)
- /*
- * Skip any cases with more than one null. Our tests don't generate
- * the full combinatorial explosions of all nulls - just a single null.
- * Hopefully , cases will function the same as ,
- * cases.
- */.filter(types -> types.stream().filter(t -> t == DataType.NULL).count() <= 1)
- .map(types -> typeErrorSupplier(validPerPosition.size() != 1, validPerPosition, types, typeErrorMessageSupplier))
- .forEach(suppliers::add);
- return suppliers;
- }
-
- public static String errorMessageStringForBinaryOperators(
- boolean includeOrdinal,
- List> validPerPosition,
- List types
- ) {
- try {
- return typeErrorMessage(includeOrdinal, validPerPosition, types);
- } catch (IllegalStateException e) {
- // This means all the positional args were okay, so the expected error is from the combination
- if (types.get(0).equals(DataType.UNSIGNED_LONG)) {
- return "first argument of [] is [unsigned_long] and second is ["
- + types.get(1).typeName()
- + "]. [unsigned_long] can only be operated on together with another [unsigned_long]";
-
- }
- if (types.get(1).equals(DataType.UNSIGNED_LONG)) {
- return "first argument of [] is ["
- + types.get(0).typeName()
- + "] and second is [unsigned_long]. [unsigned_long] can only be operated on together with another [unsigned_long]";
- }
- return "first argument of [] is ["
- + (types.get(0).isNumeric() ? "numeric" : types.get(0).typeName())
- + "] so second argument must also be ["
- + (types.get(0).isNumeric() ? "numeric" : types.get(0).typeName())
- + "] but was ["
- + types.get(1).typeName()
- + "]";
-
- }
- }
-
- /**
- * Adds test cases containing unsupported parameter types that immediately fail.
- */
- protected static List failureForCasesWithoutExamples(List testCaseSuppliers) {
- typesRequired(testCaseSuppliers);
- List suppliers = new ArrayList<>(testCaseSuppliers.size());
- suppliers.addAll(testCaseSuppliers);
-
- Set> valid = testCaseSuppliers.stream().map(TestCaseSupplier::types).collect(Collectors.toSet());
- List> validPerPosition = validPerPosition(valid);
-
- testCaseSuppliers.stream()
- .map(s -> s.types().size())
- .collect(Collectors.toSet())
- .stream()
- .flatMap(count -> allPermutations(count))
- .filter(types -> valid.contains(types) == false)
- .map(types -> new TestCaseSupplier("type error for " + TestCaseSupplier.nameFromTypes(types), types, () -> {
- throw new IllegalStateException("must implement a case for " + types);
- }))
- .forEach(suppliers::add);
- return suppliers;
- }
-
- /**
- * Validate that we know the types for all the test cases already created
- * @param suppliers - list of suppliers before adding in the illegal type combinations
- */
- private static void typesRequired(List suppliers) {
- String bad = suppliers.stream().filter(s -> s.types() == null).map(s -> s.name()).collect(Collectors.joining("\n"));
- if (bad.equals("") == false) {
- throw new IllegalArgumentException("types required but not found for these tests:\n" + bad);
- }
- }
-
- private static List> validPerPosition(Set> valid) {
- int max = valid.stream().mapToInt(List::size).max().getAsInt();
- List> result = new ArrayList<>(max);
- for (int i = 0; i < max; i++) {
- result.add(new HashSet<>());
- }
- for (List signature : valid) {
- for (int i = 0; i < signature.size(); i++) {
- result.get(i).add(signature.get(i));
- }
- }
- return result;
- }
-
- private static Stream> allPermutations(int argumentCount) {
- if (argumentCount == 0) {
- return Stream.of(List.of());
- }
- if (argumentCount > 3) {
- throw new IllegalArgumentException("would generate too many combinations");
- }
- Stream> stream = representable().map(t -> List.of(t));
- for (int i = 1; i < argumentCount; i++) {
- stream = stream.flatMap(types -> representable().map(t -> append(types, t)));
- }
- return stream;
- }
-
- private static List append(List orig, DataType extra) {
- List longer = new ArrayList<>(orig.size() + 1);
- longer.addAll(orig);
- longer.add(extra);
- return longer;
- }
-
- @FunctionalInterface
- protected interface TypeErrorMessageSupplier {
- String apply(boolean includeOrdinal, List> validPerPosition, List types);
- }
-
- protected static TestCaseSupplier typeErrorSupplier(
- boolean includeOrdinal,
- List> validPerPosition,
- List types
- ) {
- return typeErrorSupplier(includeOrdinal, validPerPosition, types, AbstractFunctionTestCase::typeErrorMessage);
- }
-
- /**
- * Build a test case that asserts that the combination of parameter types is an error.
- */
- protected static TestCaseSupplier typeErrorSupplier(
- boolean includeOrdinal,
- List> validPerPosition,
- List types,
- TypeErrorMessageSupplier errorMessageSupplier
- ) {
- return new TestCaseSupplier(
- "type error for " + TestCaseSupplier.nameFromTypes(types),
- types,
- () -> TestCaseSupplier.TestCase.typeError(
- types.stream().map(type -> new TestCaseSupplier.TypedData(randomLiteral(type).value(), type, type.typeName())).toList(),
- errorMessageSupplier.apply(includeOrdinal, validPerPosition, types)
- )
- );
- }
-
- /**
- * Build the expected error message for an invalid type signature.
- */
- protected static String typeErrorMessage(boolean includeOrdinal, List> validPerPosition, List types) {
- int badArgPosition = -1;
- for (int i = 0; i < types.size(); i++) {
- if (validPerPosition.get(i).contains(types.get(i)) == false) {
- badArgPosition = i;
- break;
- }
- }
- if (badArgPosition == -1) {
- throw new IllegalStateException(
- "Can't generate error message for these types, you probably need a custom error message function"
- );
- }
- String ordinal = includeOrdinal ? TypeResolutions.ParamOrdinal.fromIndex(badArgPosition).name().toLowerCase(Locale.ROOT) + " " : "";
- String expectedType = expectedType(validPerPosition.get(badArgPosition));
- String name = types.get(badArgPosition).typeName();
- return ordinal + "argument of [] must be [" + expectedType + "], found value [" + name + "] type [" + name + "]";
- }
-
- private static final Map, String> NAMED_EXPECTED_TYPES = Map.ofEntries(
- Map.entry(
- Set.of(DataType.DATE_PERIOD, DataType.DOUBLE, DataType.INTEGER, DataType.LONG, DataType.TIME_DURATION, DataType.NULL),
- "numeric, date_period or time_duration"
- ),
- Map.entry(Set.of(DataType.DATETIME, DataType.NULL), "datetime"),
- Map.entry(Set.of(DataType.DOUBLE, DataType.NULL), "double"),
- Map.entry(Set.of(DataType.INTEGER, DataType.NULL), "integer"),
- Map.entry(Set.of(DataType.IP, DataType.NULL), "ip"),
- Map.entry(Set.of(DataType.LONG, DataType.INTEGER, DataType.UNSIGNED_LONG, DataType.DOUBLE, DataType.NULL), "numeric"),
- Map.entry(Set.of(DataType.LONG, DataType.INTEGER, DataType.UNSIGNED_LONG, DataType.DOUBLE), "numeric"),
- Map.entry(Set.of(DataType.KEYWORD, DataType.TEXT, DataType.VERSION, DataType.NULL), "string or version"),
- Map.entry(Set.of(DataType.KEYWORD, DataType.TEXT, DataType.NULL), "string"),
- Map.entry(Set.of(DataType.IP, DataType.KEYWORD, DataType.TEXT, DataType.NULL), "ip or string"),
- Map.entry(Set.copyOf(Arrays.asList(representableTypes())), "representable"),
- Map.entry(Set.copyOf(Arrays.asList(representableNonSpatialTypes())), "representableNonSpatial"),
- Map.entry(
- Set.of(
- DataType.BOOLEAN,
- DataType.DOUBLE,
- DataType.INTEGER,
- DataType.KEYWORD,
- DataType.LONG,
- DataType.TEXT,
- DataType.UNSIGNED_LONG,
- DataType.NULL
- ),
- "boolean or numeric or string"
- ),
- Map.entry(
- Set.of(
- DataType.DATETIME,
- DataType.DOUBLE,
- DataType.INTEGER,
- DataType.KEYWORD,
- DataType.LONG,
- DataType.TEXT,
- DataType.UNSIGNED_LONG,
- DataType.NULL
- ),
- "datetime or numeric or string"
- ),
- // What Add accepts
- Map.entry(
- Set.of(
- DataType.DATE_PERIOD,
- DataType.DATETIME,
- DataType.DOUBLE,
- DataType.INTEGER,
- DataType.LONG,
- DataType.NULL,
- DataType.TIME_DURATION,
- DataType.UNSIGNED_LONG
- ),
- "datetime or numeric"
- ),
- Map.entry(
- Set.of(
- DataType.BOOLEAN,
- DataType.DATETIME,
- DataType.DOUBLE,
- DataType.INTEGER,
- DataType.KEYWORD,
- DataType.LONG,
- DataType.TEXT,
- DataType.UNSIGNED_LONG,
- DataType.NULL
- ),
- "boolean or datetime or numeric or string"
- ),
- // to_int
- Map.entry(
- Set.of(
- DataType.BOOLEAN,
- DataType.COUNTER_INTEGER,
- DataType.DATETIME,
- DataType.DOUBLE,
- DataType.INTEGER,
- DataType.KEYWORD,
- DataType.LONG,
- DataType.TEXT,
- DataType.UNSIGNED_LONG,
- DataType.NULL
- ),
- "boolean or counter_integer or datetime or numeric or string"
- ),
- // to_long
- Map.entry(
- Set.of(
- DataType.BOOLEAN,
- DataType.COUNTER_INTEGER,
- DataType.COUNTER_LONG,
- DataType.DATETIME,
- DataType.DOUBLE,
- DataType.INTEGER,
- DataType.KEYWORD,
- DataType.LONG,
- DataType.TEXT,
- DataType.UNSIGNED_LONG,
- DataType.NULL
- ),
- "boolean or counter_integer or counter_long or datetime or numeric or string"
- ),
- // to_double
- Map.entry(
- Set.of(
- DataType.BOOLEAN,
- DataType.COUNTER_DOUBLE,
- DataType.COUNTER_INTEGER,
- DataType.COUNTER_LONG,
- DataType.DATETIME,
- DataType.DOUBLE,
- DataType.INTEGER,
- DataType.KEYWORD,
- DataType.LONG,
- DataType.TEXT,
- DataType.UNSIGNED_LONG,
- DataType.NULL
- ),
- "boolean or counter_double or counter_integer or counter_long or datetime or numeric or string"
- ),
- Map.entry(
- Set.of(
- DataType.BOOLEAN,
- DataType.CARTESIAN_POINT,
- DataType.DATETIME,
- DataType.DOUBLE,
- DataType.GEO_POINT,
- DataType.INTEGER,
- DataType.KEYWORD,
- DataType.LONG,
- DataType.TEXT,
- DataType.UNSIGNED_LONG,
- DataType.NULL
- ),
- "boolean or cartesian_point or datetime or geo_point or numeric or string"
- ),
- Map.entry(
- Set.of(
- DataType.DATETIME,
- DataType.DOUBLE,
- DataType.INTEGER,
- DataType.IP,
- DataType.KEYWORD,
- DataType.LONG,
- DataType.TEXT,
- DataType.UNSIGNED_LONG,
- DataType.VERSION,
- DataType.NULL
- ),
- "datetime, double, integer, ip, keyword, long, text, unsigned_long or version"
- ),
- Map.entry(
- Set.of(
- DataType.BOOLEAN,
- DataType.DATETIME,
- DataType.DOUBLE,
- DataType.GEO_POINT,
- DataType.GEO_SHAPE,
- DataType.INTEGER,
- DataType.IP,
- DataType.KEYWORD,
- DataType.LONG,
- DataType.TEXT,
- DataType.UNSIGNED_LONG,
- DataType.VERSION,
- DataType.NULL
- ),
- "cartesian_point or datetime or geo_point or numeric or string"
- ),
- Map.entry(Set.of(DataType.GEO_POINT, DataType.KEYWORD, DataType.TEXT, DataType.NULL), "geo_point or string"),
- Map.entry(Set.of(DataType.CARTESIAN_POINT, DataType.KEYWORD, DataType.TEXT, DataType.NULL), "cartesian_point or string"),
- Map.entry(
- Set.of(DataType.GEO_POINT, DataType.GEO_SHAPE, DataType.KEYWORD, DataType.TEXT, DataType.NULL),
- "geo_point or geo_shape or string"
- ),
- Map.entry(
- Set.of(DataType.CARTESIAN_POINT, DataType.CARTESIAN_SHAPE, DataType.KEYWORD, DataType.TEXT, DataType.NULL),
- "cartesian_point or cartesian_shape or string"
- ),
- Map.entry(Set.of(DataType.GEO_POINT, DataType.CARTESIAN_POINT, DataType.NULL), "geo_point or cartesian_point"),
- Map.entry(Set.of(DataType.DATE_PERIOD, DataType.TIME_DURATION, DataType.NULL), "dateperiod or timeduration")
- );
-
- // TODO: generate this message dynamically, a la AbstractConvertFunction#supportedTypesNames()?
- private static String expectedType(Set validTypes) {
- String named = NAMED_EXPECTED_TYPES.get(validTypes);
- if (named == null) {
- /*
- * Note for anyone who's test lands here - it's likely that you
- * don't have a test case covering explicit `null` arguments in
- * this position. Generally you can get that with anyNullIsNull.
- */
- throw new UnsupportedOperationException(
- "can't guess expected types for " + validTypes.stream().sorted(Comparator.comparing(t -> t.typeName())).toList()
- );
- }
- return named;
- }
-
- protected static Stream representable() {
- return DataType.types().stream().filter(EsqlDataTypes::isRepresentable);
- }
-
- protected static DataType[] representableTypes() {
- return representable().toArray(DataType[]::new);
- }
-
- protected static Stream representableNonSpatial() {
- return representable().filter(t -> isSpatial(t) == false);
- }
-
- protected static DataType[] representableNonSpatialTypes() {
- return representableNonSpatial().toArray(DataType[]::new);
}
protected final void assertTypeResolutionFailure(Expression expression) {
@@ -1468,7 +640,7 @@ private static void renderKibanaFunctionDefinition(
"comment",
"This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it."
);
- builder.field("type", "eval"); // TODO aggs in here too
+ builder.field("type", isAggregation() ? "agg" : "eval");
builder.field("name", name);
builder.field("description", removeAsciidocLinks(info.description()));
if (Strings.isNullOrEmpty(info.note()) == false) {
@@ -1661,4 +833,14 @@ static Version randomVersion() {
protected static DataType[] strings() {
return DataType.types().stream().filter(DataType::isString).toArray(DataType[]::new);
}
+
+ /**
+ * Returns true if the current test case is for an aggregation function.
+ *
+ * This method requires reflection, as it's called from a static context (@AfterClass documentation rendering).
+ *
+ */
+ private static boolean isAggregation() {
+ return AbstractAggregationTestCase.class.isAssignableFrom(getTestClass());
+ }
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java
new file mode 100644
index 0000000000000..1aa90d367099a
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractScalarFunctionTestCase.java
@@ -0,0 +1,884 @@
+/*
+ * 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;
+
+import org.elasticsearch.common.breaker.CircuitBreakingException;
+import org.elasticsearch.common.util.MockBigArrays;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.BlockFactory;
+import org.elasticsearch.compute.data.BlockUtils;
+import org.elasticsearch.compute.data.ElementType;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.compute.data.Vector;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.indices.CrankyCircuitBreakerService;
+import org.elasticsearch.xpack.esql.TestBlockFactory;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.expression.TypeResolutions;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.core.util.NumericUtils;
+import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest;
+import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.AbstractMultivalueFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce;
+import org.elasticsearch.xpack.esql.optimizer.FoldNull;
+import org.elasticsearch.xpack.esql.planner.PlannerUtils;
+import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
+import org.hamcrest.Matcher;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static org.elasticsearch.compute.data.BlockUtils.toJavaObject;
+import static org.elasticsearch.xpack.esql.type.EsqlDataTypes.isSpatial;
+import static org.hamcrest.Matchers.either;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.sameInstance;
+
+/**
+ * Base class for scalar function tests. Tests based on this class will generally build out a single example evaluation,
+ * which can be automatically tested against several scenarios (null handling, concurrency, etc).
+ */
+public abstract class AbstractScalarFunctionTestCase extends AbstractFunctionTestCase {
+
+ /**
+ * Converts a list of test cases into a list of parameter suppliers.
+ * Also, adds a default set of extra test cases.
+ *
+ * Use if possible, as this method may get updated with new checks in the future.
+ *
+ * Note that this'll sometimes be a {@link Vector} of values if the
+ * input pattern contained only a single value.
+ *
+ */
+ public final void testEvaluateBlockWithoutNulls() {
+ assumeTrue("no warning is expected", testCase.getExpectedWarnings() == null);
+ try {
+ testEvaluateBlock(driverContext().blockFactory(), driverContext(), false);
+ } catch (CircuitBreakingException ex) {
+ assertThat(ex.getMessage(), equalTo(MockBigArrays.ERROR_MESSAGE));
+ fail("Test data is too large to fit in the memory");
+ }
+ }
+
+ /**
+ * Evaluates a {@link Block} of values, all copied from the input pattern with
+ * some null values inserted between.
+ */
+ public final void testEvaluateBlockWithNulls() {
+ assumeTrue("no warning is expected", testCase.getExpectedWarnings() == null);
+ try {
+ testEvaluateBlock(driverContext().blockFactory(), driverContext(), true);
+ } catch (CircuitBreakingException ex) {
+ assertThat(ex.getMessage(), equalTo(MockBigArrays.ERROR_MESSAGE));
+ fail("Test data is too large to fit in the memory");
+ }
+ }
+
+ /**
+ * Evaluates a {@link Block} of values, all copied from the input pattern,
+ * using the {@link CrankyCircuitBreakerService} which fails randomly.
+ *
+ * Note that this'll sometimes be a {@link Vector} of values if the
+ * input pattern contained only a single value.
+ *
+ */
+ public final void testCrankyEvaluateBlockWithoutNulls() {
+ assumeTrue("sometimes the cranky breaker silences warnings, just skip these cases", testCase.getExpectedWarnings() == null);
+ try {
+ testEvaluateBlock(driverContext().blockFactory(), crankyContext(), false);
+ } catch (CircuitBreakingException ex) {
+ assertThat(ex.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE));
+ }
+ }
+
+ /**
+ * Evaluates a {@link Block} of values, all copied from the input pattern with
+ * some null values inserted between, using the {@link CrankyCircuitBreakerService} which fails randomly.
+ */
+ public final void testCrankyEvaluateBlockWithNulls() {
+ assumeTrue("sometimes the cranky breaker silences warnings, just skip these cases", testCase.getExpectedWarnings() == null);
+ try {
+ testEvaluateBlock(driverContext().blockFactory(), crankyContext(), true);
+ } catch (CircuitBreakingException ex) {
+ assertThat(ex.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE));
+ }
+ }
+
+ protected Matcher allNullsMatcher() {
+ return nullValue();
+ }
+
+ private void testEvaluateBlock(BlockFactory inputBlockFactory, DriverContext context, boolean insertNulls) {
+ Expression expression = randomBoolean() ? buildDeepCopyOfFieldExpression(testCase) : buildFieldExpression(testCase);
+ if (testCase.getExpectedTypeError() != null) {
+ assertTypeResolutionFailure(expression);
+ return;
+ }
+ assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
+ assumeTrue("Expected type must be representable to build an evaluator", EsqlDataTypes.isRepresentable(testCase.expectedType()));
+ int positions = between(1, 1024);
+ List data = testCase.getData();
+ Page onePositionPage = row(testCase.getDataValues());
+ Block[] manyPositionsBlocks = new Block[Math.toIntExact(data.stream().filter(d -> d.isForceLiteral() == false).count())];
+ Set nullPositions = insertNulls
+ ? IntStream.range(0, positions).filter(i -> randomBoolean()).mapToObj(Integer::valueOf).collect(Collectors.toSet())
+ : Set.of();
+ if (nullPositions.size() == positions) {
+ nullPositions = Set.of();
+ }
+ try {
+ int b = 0;
+ for (TestCaseSupplier.TypedData d : data) {
+ if (d.isForceLiteral()) {
+ continue;
+ }
+ ElementType elementType = PlannerUtils.toElementType(d.type());
+ try (Block.Builder builder = elementType.newBlockBuilder(positions, inputBlockFactory)) {
+ for (int p = 0; p < positions; p++) {
+ if (nullPositions.contains(p)) {
+ builder.appendNull();
+ } else {
+ builder.copyFrom(onePositionPage.getBlock(b), 0, 1);
+ }
+ }
+ manyPositionsBlocks[b] = builder.build();
+ }
+ b++;
+ }
+ try (
+ ExpressionEvaluator eval = evaluator(expression).get(context);
+ Block block = eval.eval(new Page(positions, manyPositionsBlocks))
+ ) {
+ for (int p = 0; p < positions; p++) {
+ if (nullPositions.contains(p)) {
+ assertThat(toJavaObject(block, p), allNullsMatcher());
+ continue;
+ }
+ assertThat(toJavaObjectUnsignedLongAware(block, p), testCase.getMatcher());
+ }
+ assertThat(
+ "evaluates to tracked block",
+ block.blockFactory(),
+ either(sameInstance(context.blockFactory())).or(sameInstance(inputBlockFactory))
+ );
+ }
+ } finally {
+ Releasables.close(onePositionPage::releaseBlocks, Releasables.wrap(manyPositionsBlocks));
+ }
+ if (testCase.getExpectedWarnings() != null) {
+ assertWarnings(testCase.getExpectedWarnings());
+ }
+ }
+
+ public void testSimpleWithNulls() { // TODO replace this with nulls inserted into the test case like anyNullIsNull
+ Expression expression = buildFieldExpression(testCase);
+ if (testCase.getExpectedTypeError() != null) {
+ assertTypeResolutionFailure(expression);
+ return;
+ }
+ assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
+ List simpleData = testCase.getDataValues();
+ try (EvalOperator.ExpressionEvaluator eval = evaluator(expression).get(driverContext())) {
+ BlockFactory blockFactory = TestBlockFactory.getNonBreakingInstance();
+ Block[] orig = BlockUtils.fromListRow(blockFactory, simpleData);
+ for (int i = 0; i < orig.length; i++) {
+ List data = new ArrayList<>();
+ Block[] blocks = new Block[orig.length];
+ for (int b = 0; b < blocks.length; b++) {
+ if (b == i) {
+ blocks[b] = orig[b].elementType().newBlockBuilder(1, blockFactory).appendNull().build();
+ data.add(null);
+ } else {
+ blocks[b] = orig[b];
+ data.add(simpleData.get(b));
+ }
+ }
+ try (Block block = eval.eval(new Page(blocks))) {
+ assertSimpleWithNulls(data, block, i);
+ }
+ }
+
+ // Note: the null-in-fast-null-out handling prevents any exception from being thrown, so the warnings provided in some test
+ // cases won't actually be registered. This isn't an issue for unary functions, but could be an issue for n-ary ones, if
+ // function processing of the first parameter(s) could raise an exception/warning. (But hasn't been the case so far.)
+ // N-ary non-MV functions dealing with one multivalue (before hitting the null parameter injected above) will now trigger
+ // a warning ("SV-function encountered a MV") that thus needs to be checked.
+ if (this instanceof AbstractMultivalueFunctionTestCase == false
+ && simpleData.stream().anyMatch(List.class::isInstance)
+ && testCase.getExpectedWarnings() != null) {
+ assertWarnings(testCase.getExpectedWarnings());
+ }
+ }
+ }
+
+ public final void testEvaluateInManyThreads() throws ExecutionException, InterruptedException {
+ Expression expression = buildFieldExpression(testCase);
+ if (testCase.getExpectedTypeError() != null) {
+ assertTypeResolutionFailure(expression);
+ return;
+ }
+ assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
+ assumeTrue("Expected type must be representable to build an evaluator", EsqlDataTypes.isRepresentable(testCase.expectedType()));
+ int count = 10_000;
+ int threads = 5;
+ var evalSupplier = evaluator(expression);
+ ExecutorService exec = Executors.newFixedThreadPool(threads);
+ try {
+ List> futures = new ArrayList<>();
+ for (int i = 0; i < threads; i++) {
+ List simpleData = testCase.getDataValues();
+ Page page = row(simpleData);
+
+ futures.add(exec.submit(() -> {
+ try (EvalOperator.ExpressionEvaluator eval = evalSupplier.get(driverContext())) {
+ for (int c = 0; c < count; c++) {
+ try (Block block = eval.eval(page)) {
+ assertThat(toJavaObjectUnsignedLongAware(block, 0), testCase.getMatcher());
+ }
+ }
+ }
+ }));
+ }
+ for (Future> f : futures) {
+ f.get();
+ }
+ } finally {
+ exec.shutdown();
+ }
+ }
+
+ public final void testEvaluatorToString() {
+ Expression expression = buildFieldExpression(testCase);
+ if (testCase.getExpectedTypeError() != null) {
+ assertTypeResolutionFailure(expression);
+ return;
+ }
+ assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
+ var factory = evaluator(expression);
+ try (ExpressionEvaluator ev = factory.get(driverContext())) {
+ assertThat(ev.toString(), testCase.evaluatorToString());
+ }
+ }
+
+ public final void testFactoryToString() {
+ Expression expression = buildFieldExpression(testCase);
+ if (testCase.getExpectedTypeError() != null) {
+ assertTypeResolutionFailure(expression);
+ return;
+ }
+ assumeTrue("Can't build evaluator", testCase.canBuildEvaluator());
+ var factory = evaluator(buildFieldExpression(testCase));
+ assertThat(factory.toString(), testCase.evaluatorToString());
+ }
+
+ public final void testFold() {
+ Expression expression = buildLiteralExpression(testCase);
+ if (testCase.getExpectedTypeError() != null) {
+ assertTypeResolutionFailure(expression);
+ return;
+ }
+ assertFalse(expression.typeResolved().unresolved());
+ Expression nullOptimized = new FoldNull().rule(expression);
+ assertThat(nullOptimized.dataType(), equalTo(testCase.expectedType()));
+ assertTrue(nullOptimized.foldable());
+ if (testCase.foldingExceptionClass() == null) {
+ Object result = nullOptimized.fold();
+ // Decode unsigned longs into BigIntegers
+ if (testCase.expectedType() == DataType.UNSIGNED_LONG && result != null) {
+ result = NumericUtils.unsignedLongAsBigInteger((Long) result);
+ }
+ assertThat(result, testCase.getMatcher());
+ if (testCase.getExpectedWarnings() != null) {
+ assertWarnings(testCase.getExpectedWarnings());
+ }
+ } else {
+ Throwable t = expectThrows(testCase.foldingExceptionClass(), nullOptimized::fold);
+ assertThat(t.getMessage(), equalTo(testCase.foldingExceptionMessage()));
+ }
+ }
+
+ /**
+ * Adds cases with {@code null} and asserts that the result is {@code null}.
+ *
+ * Note: This won't add more than a single null to any existing test case,
+ * just to keep the number of test cases from exploding totally.
+ *
+ *
+ * @param entirelyNullPreservesType should a test case that only contains parameters
+ * with the {@code null} type keep it's expected type?
+ * This is mostly going to be {@code true}
+ * except for functions that base their type entirely
+ * on input types like {@link Greatest} or {@link Coalesce}.
+ */
+ protected static List anyNullIsNull(boolean entirelyNullPreservesType, List testCaseSuppliers) {
+ return anyNullIsNull(
+ testCaseSuppliers,
+ (nullPosition, nullValueDataType, original) -> entirelyNullPreservesType == false
+ && nullValueDataType == DataType.NULL
+ && original.getData().size() == 1 ? DataType.NULL : original.expectedType(),
+ (nullPosition, nullData, original) -> original
+ );
+ }
+
+ public interface ExpectedType {
+ DataType expectedType(int nullPosition, DataType nullValueDataType, TestCaseSupplier.TestCase original);
+ }
+
+ public interface ExpectedEvaluatorToString {
+ Matcher evaluatorToString(int nullPosition, TestCaseSupplier.TypedData nullData, Matcher original);
+ }
+
+ protected static List anyNullIsNull(
+ List testCaseSuppliers,
+ ExpectedType expectedType,
+ ExpectedEvaluatorToString evaluatorToString
+ ) {
+ typesRequired(testCaseSuppliers);
+ List suppliers = new ArrayList<>(testCaseSuppliers.size());
+ suppliers.addAll(testCaseSuppliers);
+
+ /*
+ * For each original test case, add as many copies as there were
+ * arguments, replacing one of the arguments with null and keeping
+ * the others.
+ *
+ * Also, if this was the first time we saw the signature we copy it
+ * *again*, replacing the argument with null, but annotating the
+ * argument's type as `null` explicitly.
+ */
+ Set> uniqueSignatures = new HashSet<>();
+ for (TestCaseSupplier original : testCaseSuppliers) {
+ boolean firstTimeSeenSignature = uniqueSignatures.add(original.types());
+ for (int nullPosition = 0; nullPosition < original.types().size(); nullPosition++) {
+ int finalNullPosition = nullPosition;
+ suppliers.add(new TestCaseSupplier(original.name() + " null in " + nullPosition, original.types(), () -> {
+ TestCaseSupplier.TestCase oc = original.get();
+ List data = IntStream.range(0, oc.getData().size()).mapToObj(i -> {
+ TestCaseSupplier.TypedData od = oc.getData().get(i);
+ return i == finalNullPosition ? od.withData(null) : od;
+ }).toList();
+ TestCaseSupplier.TypedData nulledData = oc.getData().get(finalNullPosition);
+ return new TestCaseSupplier.TestCase(
+ data,
+ evaluatorToString.evaluatorToString(finalNullPosition, nulledData, oc.evaluatorToString()),
+ expectedType.expectedType(finalNullPosition, nulledData.type(), oc),
+ nullValue(),
+ null,
+ oc.getExpectedTypeError(),
+ null,
+ null
+ );
+ }));
+
+ if (firstTimeSeenSignature) {
+ List typesWithNull = IntStream.range(0, original.types().size())
+ .mapToObj(i -> i == finalNullPosition ? DataType.NULL : original.types().get(i))
+ .toList();
+ boolean newSignature = uniqueSignatures.add(typesWithNull);
+ if (newSignature) {
+ suppliers.add(new TestCaseSupplier(typesWithNull, () -> {
+ TestCaseSupplier.TestCase oc = original.get();
+ List data = IntStream.range(0, oc.getData().size())
+ .mapToObj(i -> i == finalNullPosition ? TestCaseSupplier.TypedData.NULL : oc.getData().get(i))
+ .toList();
+ return new TestCaseSupplier.TestCase(
+ data,
+ equalTo("LiteralsEvaluator[lit=null]"),
+ expectedType.expectedType(finalNullPosition, DataType.NULL, oc),
+ nullValue(),
+ null,
+ oc.getExpectedTypeError(),
+ null,
+ null
+ );
+ }));
+ }
+ }
+ }
+ }
+
+ return suppliers;
+
+ }
+
+ /**
+ * Adds test cases containing unsupported parameter types that assert
+ * that they throw type errors.
+ */
+ protected static List errorsForCasesWithoutExamples(List testCaseSuppliers) {
+ return errorsForCasesWithoutExamples(testCaseSuppliers, AbstractScalarFunctionTestCase::typeErrorMessage);
+ }
+
+ protected static List errorsForCasesWithoutExamples(
+ List testCaseSuppliers,
+ TypeErrorMessageSupplier typeErrorMessageSupplier
+ ) {
+ typesRequired(testCaseSuppliers);
+ List suppliers = new ArrayList<>(testCaseSuppliers.size());
+ suppliers.addAll(testCaseSuppliers);
+
+ Set> valid = testCaseSuppliers.stream().map(TestCaseSupplier::types).collect(Collectors.toSet());
+ List> validPerPosition = validPerPosition(valid);
+
+ testCaseSuppliers.stream()
+ .map(s -> s.types().size())
+ .collect(Collectors.toSet())
+ .stream()
+ .flatMap(count -> allPermutations(count))
+ .filter(types -> valid.contains(types) == false)
+ /*
+ * Skip any cases with more than one null. Our tests don't generate
+ * the full combinatorial explosions of all nulls - just a single null.
+ * Hopefully , cases will function the same as ,
+ * cases.
+ */.filter(types -> types.stream().filter(t -> t == DataType.NULL).count() <= 1)
+ .map(types -> typeErrorSupplier(validPerPosition.size() != 1, validPerPosition, types, typeErrorMessageSupplier))
+ .forEach(suppliers::add);
+ return suppliers;
+ }
+
+ public static String errorMessageStringForBinaryOperators(
+ boolean includeOrdinal,
+ List> validPerPosition,
+ List types
+ ) {
+ try {
+ return typeErrorMessage(includeOrdinal, validPerPosition, types);
+ } catch (IllegalStateException e) {
+ // This means all the positional args were okay, so the expected error is from the combination
+ if (types.get(0).equals(DataType.UNSIGNED_LONG)) {
+ return "first argument of [] is [unsigned_long] and second is ["
+ + types.get(1).typeName()
+ + "]. [unsigned_long] can only be operated on together with another [unsigned_long]";
+
+ }
+ if (types.get(1).equals(DataType.UNSIGNED_LONG)) {
+ return "first argument of [] is ["
+ + types.get(0).typeName()
+ + "] and second is [unsigned_long]. [unsigned_long] can only be operated on together with another [unsigned_long]";
+ }
+ return "first argument of [] is ["
+ + (types.get(0).isNumeric() ? "numeric" : types.get(0).typeName())
+ + "] so second argument must also be ["
+ + (types.get(0).isNumeric() ? "numeric" : types.get(0).typeName())
+ + "] but was ["
+ + types.get(1).typeName()
+ + "]";
+
+ }
+ }
+
+ /**
+ * Adds test cases containing unsupported parameter types that immediately fail.
+ */
+ protected static List failureForCasesWithoutExamples(List testCaseSuppliers) {
+ typesRequired(testCaseSuppliers);
+ List suppliers = new ArrayList<>(testCaseSuppliers.size());
+ suppliers.addAll(testCaseSuppliers);
+
+ Set> valid = testCaseSuppliers.stream().map(TestCaseSupplier::types).collect(Collectors.toSet());
+
+ testCaseSuppliers.stream()
+ .map(s -> s.types().size())
+ .collect(Collectors.toSet())
+ .stream()
+ .flatMap(count -> allPermutations(count))
+ .filter(types -> valid.contains(types) == false)
+ .map(types -> new TestCaseSupplier("type error for " + TestCaseSupplier.nameFromTypes(types), types, () -> {
+ throw new IllegalStateException("must implement a case for " + types);
+ }))
+ .forEach(suppliers::add);
+ return suppliers;
+ }
+
+ /**
+ * Validate that we know the types for all the test cases already created
+ * @param suppliers - list of suppliers before adding in the illegal type combinations
+ */
+ private static void typesRequired(List suppliers) {
+ String bad = suppliers.stream().filter(s -> s.types() == null).map(s -> s.name()).collect(Collectors.joining("\n"));
+ if (bad.equals("") == false) {
+ throw new IllegalArgumentException("types required but not found for these tests:\n" + bad);
+ }
+ }
+
+ private static List> validPerPosition(Set> valid) {
+ int max = valid.stream().mapToInt(List::size).max().getAsInt();
+ List> result = new ArrayList<>(max);
+ for (int i = 0; i < max; i++) {
+ result.add(new HashSet<>());
+ }
+ for (List signature : valid) {
+ for (int i = 0; i < signature.size(); i++) {
+ result.get(i).add(signature.get(i));
+ }
+ }
+ return result;
+ }
+
+ private static Stream> allPermutations(int argumentCount) {
+ if (argumentCount == 0) {
+ return Stream.of(List.of());
+ }
+ if (argumentCount > 3) {
+ throw new IllegalArgumentException("would generate too many combinations");
+ }
+ Stream> stream = representable().map(t -> List.of(t));
+ for (int i = 1; i < argumentCount; i++) {
+ stream = stream.flatMap(types -> representable().map(t -> append(types, t)));
+ }
+ return stream;
+ }
+
+ private static List append(List orig, DataType extra) {
+ List longer = new ArrayList<>(orig.size() + 1);
+ longer.addAll(orig);
+ longer.add(extra);
+ return longer;
+ }
+
+ @FunctionalInterface
+ protected interface TypeErrorMessageSupplier {
+ String apply(boolean includeOrdinal, List> validPerPosition, List types);
+ }
+
+ protected static TestCaseSupplier typeErrorSupplier(
+ boolean includeOrdinal,
+ List> validPerPosition,
+ List types
+ ) {
+ return typeErrorSupplier(includeOrdinal, validPerPosition, types, AbstractScalarFunctionTestCase::typeErrorMessage);
+ }
+
+ /**
+ * Build a test case that asserts that the combination of parameter types is an error.
+ */
+ protected static TestCaseSupplier typeErrorSupplier(
+ boolean includeOrdinal,
+ List> validPerPosition,
+ List types,
+ TypeErrorMessageSupplier errorMessageSupplier
+ ) {
+ return new TestCaseSupplier(
+ "type error for " + TestCaseSupplier.nameFromTypes(types),
+ types,
+ () -> TestCaseSupplier.TestCase.typeError(
+ types.stream().map(type -> new TestCaseSupplier.TypedData(randomLiteral(type).value(), type, type.typeName())).toList(),
+ errorMessageSupplier.apply(includeOrdinal, validPerPosition, types)
+ )
+ );
+ }
+
+ /**
+ * Build the expected error message for an invalid type signature.
+ */
+ protected static String typeErrorMessage(boolean includeOrdinal, List> validPerPosition, List types) {
+ int badArgPosition = -1;
+ for (int i = 0; i < types.size(); i++) {
+ if (validPerPosition.get(i).contains(types.get(i)) == false) {
+ badArgPosition = i;
+ break;
+ }
+ }
+ if (badArgPosition == -1) {
+ throw new IllegalStateException(
+ "Can't generate error message for these types, you probably need a custom error message function"
+ );
+ }
+ String ordinal = includeOrdinal ? TypeResolutions.ParamOrdinal.fromIndex(badArgPosition).name().toLowerCase(Locale.ROOT) + " " : "";
+ String expectedType = expectedType(validPerPosition.get(badArgPosition));
+ String name = types.get(badArgPosition).typeName();
+ return ordinal + "argument of [] must be [" + expectedType + "], found value [" + name + "] type [" + name + "]";
+ }
+
+ private static final Map, String> NAMED_EXPECTED_TYPES = Map.ofEntries(
+ Map.entry(
+ Set.of(DataType.DATE_PERIOD, DataType.DOUBLE, DataType.INTEGER, DataType.LONG, DataType.TIME_DURATION, DataType.NULL),
+ "numeric, date_period or time_duration"
+ ),
+ Map.entry(Set.of(DataType.DATETIME, DataType.NULL), "datetime"),
+ Map.entry(Set.of(DataType.DOUBLE, DataType.NULL), "double"),
+ Map.entry(Set.of(DataType.INTEGER, DataType.NULL), "integer"),
+ Map.entry(Set.of(DataType.IP, DataType.NULL), "ip"),
+ Map.entry(Set.of(DataType.LONG, DataType.INTEGER, DataType.UNSIGNED_LONG, DataType.DOUBLE, DataType.NULL), "numeric"),
+ Map.entry(Set.of(DataType.LONG, DataType.INTEGER, DataType.UNSIGNED_LONG, DataType.DOUBLE), "numeric"),
+ Map.entry(Set.of(DataType.KEYWORD, DataType.TEXT, DataType.VERSION, DataType.NULL), "string or version"),
+ Map.entry(Set.of(DataType.KEYWORD, DataType.TEXT, DataType.NULL), "string"),
+ Map.entry(Set.of(DataType.IP, DataType.KEYWORD, DataType.TEXT, DataType.NULL), "ip or string"),
+ Map.entry(Set.copyOf(Arrays.asList(representableTypes())), "representable"),
+ Map.entry(Set.copyOf(Arrays.asList(representableNonSpatialTypes())), "representableNonSpatial"),
+ Map.entry(
+ Set.of(
+ DataType.BOOLEAN,
+ DataType.DOUBLE,
+ DataType.INTEGER,
+ DataType.KEYWORD,
+ DataType.LONG,
+ DataType.TEXT,
+ DataType.UNSIGNED_LONG,
+ DataType.NULL
+ ),
+ "boolean or numeric or string"
+ ),
+ Map.entry(
+ Set.of(
+ DataType.DATETIME,
+ DataType.DOUBLE,
+ DataType.INTEGER,
+ DataType.KEYWORD,
+ DataType.LONG,
+ DataType.TEXT,
+ DataType.UNSIGNED_LONG,
+ DataType.NULL
+ ),
+ "datetime or numeric or string"
+ ),
+ // What Add accepts
+ Map.entry(
+ Set.of(
+ DataType.DATE_PERIOD,
+ DataType.DATETIME,
+ DataType.DOUBLE,
+ DataType.INTEGER,
+ DataType.LONG,
+ DataType.NULL,
+ DataType.TIME_DURATION,
+ DataType.UNSIGNED_LONG
+ ),
+ "datetime or numeric"
+ ),
+ Map.entry(
+ Set.of(
+ DataType.BOOLEAN,
+ DataType.DATETIME,
+ DataType.DOUBLE,
+ DataType.INTEGER,
+ DataType.KEYWORD,
+ DataType.LONG,
+ DataType.TEXT,
+ DataType.UNSIGNED_LONG,
+ DataType.NULL
+ ),
+ "boolean or datetime or numeric or string"
+ ),
+ // to_int
+ Map.entry(
+ Set.of(
+ DataType.BOOLEAN,
+ DataType.COUNTER_INTEGER,
+ DataType.DATETIME,
+ DataType.DOUBLE,
+ DataType.INTEGER,
+ DataType.KEYWORD,
+ DataType.LONG,
+ DataType.TEXT,
+ DataType.UNSIGNED_LONG,
+ DataType.NULL
+ ),
+ "boolean or counter_integer or datetime or numeric or string"
+ ),
+ // to_long
+ Map.entry(
+ Set.of(
+ DataType.BOOLEAN,
+ DataType.COUNTER_INTEGER,
+ DataType.COUNTER_LONG,
+ DataType.DATETIME,
+ DataType.DOUBLE,
+ DataType.INTEGER,
+ DataType.KEYWORD,
+ DataType.LONG,
+ DataType.TEXT,
+ DataType.UNSIGNED_LONG,
+ DataType.NULL
+ ),
+ "boolean or counter_integer or counter_long or datetime or numeric or string"
+ ),
+ // to_double
+ Map.entry(
+ Set.of(
+ DataType.BOOLEAN,
+ DataType.COUNTER_DOUBLE,
+ DataType.COUNTER_INTEGER,
+ DataType.COUNTER_LONG,
+ DataType.DATETIME,
+ DataType.DOUBLE,
+ DataType.INTEGER,
+ DataType.KEYWORD,
+ DataType.LONG,
+ DataType.TEXT,
+ DataType.UNSIGNED_LONG,
+ DataType.NULL
+ ),
+ "boolean or counter_double or counter_integer or counter_long or datetime or numeric or string"
+ ),
+ Map.entry(
+ Set.of(
+ DataType.BOOLEAN,
+ DataType.CARTESIAN_POINT,
+ DataType.DATETIME,
+ DataType.DOUBLE,
+ DataType.GEO_POINT,
+ DataType.INTEGER,
+ DataType.KEYWORD,
+ DataType.LONG,
+ DataType.TEXT,
+ DataType.UNSIGNED_LONG,
+ DataType.NULL
+ ),
+ "boolean or cartesian_point or datetime or geo_point or numeric or string"
+ ),
+ Map.entry(
+ Set.of(
+ DataType.DATETIME,
+ DataType.DOUBLE,
+ DataType.INTEGER,
+ DataType.IP,
+ DataType.KEYWORD,
+ DataType.LONG,
+ DataType.TEXT,
+ DataType.UNSIGNED_LONG,
+ DataType.VERSION,
+ DataType.NULL
+ ),
+ "datetime, double, integer, ip, keyword, long, text, unsigned_long or version"
+ ),
+ Map.entry(
+ Set.of(
+ DataType.BOOLEAN,
+ DataType.DATETIME,
+ DataType.DOUBLE,
+ DataType.GEO_POINT,
+ DataType.GEO_SHAPE,
+ DataType.INTEGER,
+ DataType.IP,
+ DataType.KEYWORD,
+ DataType.LONG,
+ DataType.TEXT,
+ DataType.UNSIGNED_LONG,
+ DataType.VERSION,
+ DataType.NULL
+ ),
+ "cartesian_point or datetime or geo_point or numeric or string"
+ ),
+ Map.entry(Set.of(DataType.GEO_POINT, DataType.KEYWORD, DataType.TEXT, DataType.NULL), "geo_point or string"),
+ Map.entry(Set.of(DataType.CARTESIAN_POINT, DataType.KEYWORD, DataType.TEXT, DataType.NULL), "cartesian_point or string"),
+ Map.entry(
+ Set.of(DataType.GEO_POINT, DataType.GEO_SHAPE, DataType.KEYWORD, DataType.TEXT, DataType.NULL),
+ "geo_point or geo_shape or string"
+ ),
+ Map.entry(
+ Set.of(DataType.CARTESIAN_POINT, DataType.CARTESIAN_SHAPE, DataType.KEYWORD, DataType.TEXT, DataType.NULL),
+ "cartesian_point or cartesian_shape or string"
+ ),
+ Map.entry(Set.of(DataType.GEO_POINT, DataType.CARTESIAN_POINT, DataType.NULL), "geo_point or cartesian_point"),
+ Map.entry(Set.of(DataType.DATE_PERIOD, DataType.TIME_DURATION, DataType.NULL), "dateperiod or timeduration")
+ );
+
+ // TODO: generate this message dynamically, a la AbstractConvertFunction#supportedTypesNames()?
+ private static String expectedType(Set validTypes) {
+ String named = NAMED_EXPECTED_TYPES.get(validTypes);
+ if (named == null) {
+ /*
+ * Note for anyone who's test lands here - it's likely that you
+ * don't have a test case covering explicit `null` arguments in
+ * this position. Generally you can get that with anyNullIsNull.
+ */
+ throw new UnsupportedOperationException(
+ "can't guess expected types for " + validTypes.stream().sorted(Comparator.comparing(t -> t.typeName())).toList()
+ );
+ }
+ return named;
+ }
+
+ protected static Stream representable() {
+ return DataType.types().stream().filter(EsqlDataTypes::isRepresentable);
+ }
+
+ protected static DataType[] representableTypes() {
+ return representable().toArray(DataType[]::new);
+ }
+
+ protected static Stream representableNonSpatial() {
+ return representable().filter(t -> isSpatial(t) == false);
+ }
+
+ protected static DataType[] representableNonSpatialTypes() {
+ return representableNonSpatial().toArray(DataType[]::new);
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/FunctionName.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/FunctionName.java
index b4a5d3bdc2b92..9807cb5365e54 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/FunctionName.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/FunctionName.java
@@ -13,7 +13,7 @@
import java.lang.annotation.Target;
/**
- * Tests that extend AbstractFunctionTestCase can use this annotation to specify the name of the function
+ * Tests that extend {@link AbstractScalarFunctionTestCase} can use this annotation to specify the name of the function
* to use when generating documentation files while running tests.
* If this is not used, the name will be deduced from the test class name, by removing the "Test" suffix, and converting
* the class name to snake case. This annotation can be used to override that behavior, for cases where the deduced name
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java
index 7eadad58ec09b..9095f5da63bf3 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/TestCaseSupplier.java
@@ -1301,6 +1301,14 @@ public List getDataValues() {
return data.stream().filter(d -> d.forceLiteral == false).map(TypedData::data).collect(Collectors.toList());
}
+ public List> getMultiRowDataValues() {
+ return data.stream().filter(TypedData::isMultiRow).map(TypedData::multiRowData).collect(Collectors.toList());
+ }
+
+ public boolean canGetDataAsLiterals() {
+ return data.stream().noneMatch(d -> d.isMultiRow() && d.multiRowData().size() != 1);
+ }
+
public boolean canBuildEvaluator() {
return canBuildEvaluator;
}
@@ -1363,14 +1371,18 @@ public Matcher evaluatorToString() {
* exists because we can't generate random values from the test parameter generation functions, and instead need to return
* suppliers which generate the random values at test execution time.
*/
- public record TypedDataSupplier(String name, Supplier supplier, DataType type, boolean forceLiteral) {
+ public record TypedDataSupplier(String name, Supplier supplier, DataType type, boolean forceLiteral, boolean multiRow) {
+
+ public TypedDataSupplier(String name, Supplier supplier, DataType type, boolean forceLiteral) {
+ this(name, supplier, type, forceLiteral, false);
+ }
public TypedDataSupplier(String name, Supplier supplier, DataType type) {
- this(name, supplier, type, false);
+ this(name, supplier, type, false, false);
}
public TypedData get() {
- return new TypedData(supplier.get(), type, name, forceLiteral);
+ return new TypedData(supplier.get(), type, name, forceLiteral, multiRow);
}
}
@@ -1384,14 +1396,19 @@ public static class TypedData {
private final DataType type;
private final String name;
private final boolean forceLiteral;
+ private final boolean multiRow;
/**
* @param data value to test against
* @param type type of the value, for building expressions
* @param name a name for the value, used for generating test case names
* @param forceLiteral should this data always be converted to a literal and never to a field reference?
+ * @param multiRow if true, data is expected to be a List of values, one per row
*/
- private TypedData(Object data, DataType type, String name, boolean forceLiteral) {
+ private TypedData(Object data, DataType type, String name, boolean forceLiteral, boolean multiRow) {
+ assert multiRow == false || data instanceof List : "multiRow data must be a List";
+ assert multiRow == false || forceLiteral == false : "multiRow data can't be converted to a literal";
+
if (type == DataType.UNSIGNED_LONG && data instanceof BigInteger b) {
this.data = NumericUtils.asLongUnsigned(b);
} else {
@@ -1400,6 +1417,7 @@ private TypedData(Object data, DataType type, String name, boolean forceLiteral)
this.type = type;
this.name = name;
this.forceLiteral = forceLiteral;
+ this.multiRow = multiRow;
}
/**
@@ -1408,7 +1426,7 @@ private TypedData(Object data, DataType type, String name, boolean forceLiteral)
* @param name a name for the value, used for generating test case names
*/
public TypedData(Object data, DataType type, String name) {
- this(data, type, name, false);
+ this(data, type, name, false, false);
}
/**
@@ -1420,13 +1438,23 @@ public TypedData(Object data, String name) {
this(data, DataType.fromJava(data), name);
}
+ /**
+ * Create a TypedData object for field to be aggregated.
+ * @param data values to test against, one per row
+ * @param type type of the value, for building expressions
+ * @param name a name for the value, used for generating test case names
+ */
+ public static TypedData multiRow(List> data, DataType type, String name) {
+ return new TypedData(data, type, name, false, true);
+ }
+
/**
* Return a {@link TypedData} that always returns a {@link Literal} from
* {@link #asField} and {@link #asDeepCopyOfField}. Use this for things that
* must be constants.
*/
public TypedData forceLiteral() {
- return new TypedData(data, type, name, true);
+ return new TypedData(data, type, name, true, multiRow);
}
/**
@@ -1437,11 +1465,19 @@ public boolean isForceLiteral() {
}
/**
- * Return a {@link TypedData} that always returns {@code null} for it's
- * value without modifying anything else in the supplier.
+ * If true, the data is expected to be a List of values, one per row.
*/
- public TypedData forceValueToNull() {
- return new TypedData(null, type, name, forceLiteral);
+ public boolean isMultiRow() {
+ return multiRow;
+ }
+
+ /**
+ * Return a {@link TypedData} with the new data.
+ *
+ * @param data The new data for the {@link TypedData}.
+ */
+ public TypedData withData(Object data) {
+ return new TypedData(data, type, name, forceLiteral, multiRow);
}
@Override
@@ -1476,6 +1512,15 @@ public Expression asDeepCopyOfField() {
* Convert this into a {@link Literal}.
*/
public Literal asLiteral() {
+ if (multiRow) {
+ var values = multiRowData();
+
+ if (values.size() != 1) {
+ throw new IllegalStateException("Multirow values require exactly 1 element to be a literal, got " + values.size());
+ }
+
+ return new Literal(Source.synthetic(name), values, type);
+ }
return new Literal(Source.synthetic(name), data, type);
}
@@ -1486,6 +1531,14 @@ public Object data() {
return data;
}
+ /**
+ * Values to test against.
+ */
+ @SuppressWarnings("unchecked")
+ public List multiRowData() {
+ return (List) data;
+ }
+
/**
* @return the data value being supplied, casting unsigned longs into BigIntegers correctly
*/
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/TopListTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/TopListTests.java
new file mode 100644
index 0000000000000..33770ff2467ef
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/TopListTests.java
@@ -0,0 +1,249 @@
+/*
+ * 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.aggregate;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
+import org.apache.lucene.util.BytesRef;
+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.AbstractAggregationTestCase;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class TopListTests extends AbstractAggregationTestCase {
+ public TopListTests(@Name("TestCase") Supplier testCaseSupplier) {
+ this.testCase = testCaseSupplier.get();
+ }
+
+ @ParametersFactory
+ public static Iterable parameters() {
+ var suppliers = List.of(
+ // All types
+ new TestCaseSupplier(List.of(DataType.INTEGER, DataType.INTEGER, DataType.KEYWORD), () -> {
+ var limit = randomIntBetween(2, 4);
+ return new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5, 8, -2, 0, 200), DataType.INTEGER, "field"),
+ new TestCaseSupplier.TypedData(limit, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.INTEGER,
+ equalTo(List.of(200, 8, 5, 0).subList(0, limit))
+ );
+ }),
+ new TestCaseSupplier(List.of(DataType.LONG, DataType.INTEGER, DataType.KEYWORD), () -> {
+ var limit = randomIntBetween(2, 4);
+ return new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5L, 8L, -2L, 0L, 200L), DataType.LONG, "field"),
+ new TestCaseSupplier.TypedData(limit, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.LONG,
+ equalTo(List.of(200L, 8L, 5L, 0L).subList(0, limit))
+ );
+ }),
+ new TestCaseSupplier(List.of(DataType.DOUBLE, DataType.INTEGER, DataType.KEYWORD), () -> {
+ var limit = randomIntBetween(2, 4);
+ return new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5., 8., -2., 0., 200.), DataType.DOUBLE, "field"),
+ new TestCaseSupplier.TypedData(limit, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.DOUBLE,
+ equalTo(List.of(200., 8., 5., 0.).subList(0, limit))
+ );
+ }),
+ new TestCaseSupplier(List.of(DataType.DATETIME, DataType.INTEGER, DataType.KEYWORD), () -> {
+ var limit = randomIntBetween(2, 4);
+ return new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5L, 8L, -2L, 0L, 200L), DataType.DATETIME, "field"),
+ new TestCaseSupplier.TypedData(limit, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.DATETIME,
+ equalTo(List.of(200L, 8L, 5L, 0L).subList(0, limit))
+ );
+ }),
+
+ // Surrogates
+ new TestCaseSupplier(
+ List.of(DataType.INTEGER, DataType.INTEGER, DataType.KEYWORD),
+ () -> new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5, 8, -2, 0, 200), DataType.INTEGER, "field"),
+ new TestCaseSupplier.TypedData(1, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.INTEGER,
+ equalTo(200)
+ )
+ ),
+ new TestCaseSupplier(
+ List.of(DataType.LONG, DataType.INTEGER, DataType.KEYWORD),
+ () -> new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5L, 8L, -2L, 0L, 200L), DataType.LONG, "field"),
+ new TestCaseSupplier.TypedData(1, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.LONG,
+ equalTo(200L)
+ )
+ ),
+ new TestCaseSupplier(
+ List.of(DataType.DOUBLE, DataType.INTEGER, DataType.KEYWORD),
+ () -> new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5., 8., -2., 0., 200.), DataType.DOUBLE, "field"),
+ new TestCaseSupplier.TypedData(1, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.DOUBLE,
+ equalTo(200.)
+ )
+ ),
+ new TestCaseSupplier(
+ List.of(DataType.DATETIME, DataType.INTEGER, DataType.KEYWORD),
+ () -> new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5L, 8L, 2L, 0L, 200L), DataType.DATETIME, "field"),
+ new TestCaseSupplier.TypedData(1, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.DATETIME,
+ equalTo(200L)
+ )
+ ),
+
+ // Folding
+ new TestCaseSupplier(
+ List.of(DataType.INTEGER, DataType.INTEGER, DataType.KEYWORD),
+ () -> new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(200), DataType.INTEGER, "field"),
+ new TestCaseSupplier.TypedData(1, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.INTEGER,
+ equalTo(200)
+ )
+ ),
+ new TestCaseSupplier(
+ List.of(DataType.LONG, DataType.INTEGER, DataType.KEYWORD),
+ () -> new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(200L), DataType.LONG, "field"),
+ new TestCaseSupplier.TypedData(1, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.LONG,
+ equalTo(200L)
+ )
+ ),
+ new TestCaseSupplier(
+ List.of(DataType.DOUBLE, DataType.INTEGER, DataType.KEYWORD),
+ () -> new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(200.), DataType.DOUBLE, "field"),
+ new TestCaseSupplier.TypedData(1, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.DOUBLE,
+ equalTo(200.)
+ )
+ ),
+ new TestCaseSupplier(
+ List.of(DataType.DATETIME, DataType.INTEGER, DataType.KEYWORD),
+ () -> new TestCaseSupplier.TestCase(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(200L), DataType.DATETIME, "field"),
+ new TestCaseSupplier.TypedData(1, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "TopList[field=Attribute[channel=0], limit=Attribute[channel=1], order=Attribute[channel=2]]",
+ DataType.DATETIME,
+ equalTo(200L)
+ )
+ ),
+
+ // Resolution errors
+ new TestCaseSupplier(
+ List.of(DataType.LONG, DataType.INTEGER, DataType.KEYWORD),
+ () -> TestCaseSupplier.TestCase.typeError(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5L, 8L, 2L, 0L, 200L), DataType.LONG, "field"),
+ new TestCaseSupplier.TypedData(0, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "Limit must be greater than 0 in [], found [0]"
+ )
+ ),
+ new TestCaseSupplier(
+ List.of(DataType.LONG, DataType.INTEGER, DataType.KEYWORD),
+ () -> TestCaseSupplier.TestCase.typeError(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5L, 8L, 2L, 0L, 200L), DataType.LONG, "field"),
+ new TestCaseSupplier.TypedData(2, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("wrong-order"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "Invalid order value in [], expected [ASC, DESC] but got [wrong-order]"
+ )
+ ),
+ new TestCaseSupplier(
+ List.of(DataType.LONG, DataType.INTEGER, DataType.KEYWORD),
+ () -> TestCaseSupplier.TestCase.typeError(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5L, 8L, 2L, 0L, 200L), DataType.LONG, "field"),
+ new TestCaseSupplier.TypedData(null, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(new BytesRef("desc"), DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "second argument of [] cannot be null, received [limit]"
+ )
+ ),
+ new TestCaseSupplier(
+ List.of(DataType.LONG, DataType.INTEGER, DataType.KEYWORD),
+ () -> TestCaseSupplier.TestCase.typeError(
+ List.of(
+ TestCaseSupplier.TypedData.multiRow(List.of(5L, 8L, 2L, 0L, 200L), DataType.LONG, "field"),
+ new TestCaseSupplier.TypedData(1, DataType.INTEGER, "limit").forceLiteral(),
+ new TestCaseSupplier.TypedData(null, DataType.KEYWORD, "order").forceLiteral()
+ ),
+ "third argument of [] cannot be null, received [order]"
+ )
+ )
+ );
+
+ return parameterSuppliersFromTypedDataWithDefaultChecks(suppliers);
+ }
+
+ @Override
+ protected Expression build(Source source, List args) {
+ return new TopList(source, args.get(0), args.get(1), args.get(2));
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/grouping/BucketTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/grouping/BucketTests.java
index aaa6fe7d45c83..9100e71de76df 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/grouping/BucketTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/grouping/BucketTests.java
@@ -16,7 +16,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -29,7 +29,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class BucketTests extends AbstractFunctionTestCase {
+public class BucketTests extends AbstractScalarFunctionTestCase {
public BucketTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/AbstractConfigurationFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/AbstractConfigurationFunctionTestCase.java
index 074fe9e159023..760c57f6570bb 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/AbstractConfigurationFunctionTestCase.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/AbstractConfigurationFunctionTestCase.java
@@ -12,7 +12,7 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.util.StringUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.plugin.EsqlPlugin;
import org.elasticsearch.xpack.esql.plugin.QueryPragmas;
import org.elasticsearch.xpack.esql.session.EsqlConfiguration;
@@ -22,7 +22,7 @@
import static org.elasticsearch.xpack.esql.SerializationTestUtils.assertSerialization;
-public abstract class AbstractConfigurationFunctionTestCase extends AbstractFunctionTestCase {
+public abstract class AbstractConfigurationFunctionTestCase extends AbstractScalarFunctionTestCase {
protected abstract Expression buildWithConfiguration(Source source, List args, EsqlConfiguration configuration);
@Override
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java
index 02da8ea22a6a0..0a03af206b846 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/CaseTests.java
@@ -20,7 +20,7 @@
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -33,7 +33,7 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
-public class CaseTests extends AbstractFunctionTestCase {
+public class CaseTests extends AbstractScalarFunctionTestCase {
public CaseTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/GreatestTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/GreatestTests.java
index 9376849d8136c..7cc03be7d6273 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/GreatestTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/GreatestTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.expression.function.scalar.VaragsTestCaseBuilder;
@@ -26,7 +26,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class GreatestTests extends AbstractFunctionTestCase {
+public class GreatestTests extends AbstractScalarFunctionTestCase {
public GreatestTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/LeastTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/LeastTests.java
index 0881b871c30f6..aa475f05ebe69 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/LeastTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/conditional/LeastTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.expression.function.scalar.VaragsTestCaseBuilder;
@@ -25,7 +25,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class LeastTests extends AbstractFunctionTestCase {
+public class LeastTests extends AbstractScalarFunctionTestCase {
public LeastTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/FromBase64Tests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/FromBase64Tests.java
index d97f070275617..e08da9850b555 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/FromBase64Tests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/FromBase64Tests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
@@ -27,7 +27,7 @@
import static org.hamcrest.Matchers.equalTo;
@FunctionName("from_base64")
-public class FromBase64Tests extends AbstractFunctionTestCase {
+public class FromBase64Tests extends AbstractScalarFunctionTestCase {
public FromBase64Tests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBase64Tests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBase64Tests.java
index 4c9175e4906bf..88ca7d0452b3e 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBase64Tests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBase64Tests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
@@ -27,7 +27,7 @@
import static org.hamcrest.Matchers.equalTo;
@FunctionName("to_base64")
-public class ToBase64Tests extends AbstractFunctionTestCase {
+public class ToBase64Tests extends AbstractScalarFunctionTestCase {
public ToBase64Tests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanTests.java
index c4e53d922ac60..c5b9b2501aeae 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -24,7 +24,7 @@
import static java.util.Collections.emptyList;
-public class ToBooleanTests extends AbstractFunctionTestCase {
+public class ToBooleanTests extends AbstractScalarFunctionTestCase {
public ToBooleanTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToCartesianPointTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToCartesianPointTests.java
index 1c1431fe3b7ea..a59e7b0085e4c 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToCartesianPointTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToCartesianPointTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
@@ -27,7 +27,7 @@
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.CARTESIAN;
@FunctionName("to_cartesianpoint")
-public class ToCartesianPointTests extends AbstractFunctionTestCase {
+public class ToCartesianPointTests extends AbstractScalarFunctionTestCase {
public ToCartesianPointTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToCartesianShapeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToCartesianShapeTests.java
index 48a610804845d..973431d676b82 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToCartesianShapeTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToCartesianShapeTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
@@ -27,7 +27,7 @@
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.CARTESIAN;
@FunctionName("to_cartesianshape")
-public class ToCartesianShapeTests extends AbstractFunctionTestCase {
+public class ToCartesianShapeTests extends AbstractScalarFunctionTestCase {
public ToCartesianShapeTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeTests.java
index 6aef91be43088..e512334391bed 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -26,7 +26,7 @@
import static java.util.Collections.emptyList;
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.DEFAULT_DATE_TIME_FORMATTER;
-public class ToDatetimeTests extends AbstractFunctionTestCase {
+public class ToDatetimeTests extends AbstractScalarFunctionTestCase {
public ToDatetimeTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegreesTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegreesTests.java
index fc45c8b26a869..bd07141009d3e 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegreesTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegreesTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -22,7 +22,7 @@
import java.util.function.Function;
import java.util.function.Supplier;
-public class ToDegreesTests extends AbstractFunctionTestCase {
+public class ToDegreesTests extends AbstractScalarFunctionTestCase {
public ToDegreesTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleTests.java
index 5f45cc11d9c5a..d4d20629da09e 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleTests.java
@@ -16,7 +16,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
@@ -26,7 +26,7 @@
import java.util.function.Function;
import java.util.function.Supplier;
-public class ToDoubleTests extends AbstractFunctionTestCase {
+public class ToDoubleTests extends AbstractScalarFunctionTestCase {
public ToDoubleTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToGeoPointTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToGeoPointTests.java
index 2b5dc453acc23..7a3b83f3ab113 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToGeoPointTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToGeoPointTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
@@ -27,7 +27,7 @@
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO;
@FunctionName("to_geopoint")
-public class ToGeoPointTests extends AbstractFunctionTestCase {
+public class ToGeoPointTests extends AbstractScalarFunctionTestCase {
public ToGeoPointTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToGeoShapeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToGeoShapeTests.java
index bca8dc822052f..831539852846c 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToGeoShapeTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToGeoShapeTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
@@ -27,7 +27,7 @@
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO;
@FunctionName("to_geoshape")
-public class ToGeoShapeTests extends AbstractFunctionTestCase {
+public class ToGeoShapeTests extends AbstractScalarFunctionTestCase {
public ToGeoShapeTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIPTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIPTests.java
index 20b48d24f8211..ffa94548f0a23 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIPTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIPTests.java
@@ -16,7 +16,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -26,7 +26,7 @@
import static java.util.Collections.emptyList;
import static org.elasticsearch.xpack.esql.core.util.StringUtils.parseIP;
-public class ToIPTests extends AbstractFunctionTestCase {
+public class ToIPTests extends AbstractScalarFunctionTestCase {
public ToIPTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerTests.java
index 45837c2110ff3..7984c1e04effc 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -26,7 +26,7 @@
import static org.elasticsearch.xpack.esql.core.type.DataTypeConverter.safeToInt;
-public class ToIntegerTests extends AbstractFunctionTestCase {
+public class ToIntegerTests extends AbstractScalarFunctionTestCase {
public ToIntegerTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongTests.java
index 565562b8574d2..27c69ae977f6b 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -25,7 +25,7 @@
import java.util.function.Function;
import java.util.function.Supplier;
-public class ToLongTests extends AbstractFunctionTestCase {
+public class ToLongTests extends AbstractScalarFunctionTestCase {
public ToLongTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadiansTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadiansTests.java
index 3f6e28c65142f..33e8eee7a8de4 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadiansTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadiansTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -22,7 +22,7 @@
import java.util.function.Function;
import java.util.function.Supplier;
-public class ToRadiansTests extends AbstractFunctionTestCase {
+public class ToRadiansTests extends AbstractScalarFunctionTestCase {
public ToRadiansTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringTests.java
index 0556742b55b3c..809b4ddaa78a4 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringTests.java
@@ -16,7 +16,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -27,7 +27,7 @@
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.CARTESIAN;
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO;
-public class ToStringTests extends AbstractFunctionTestCase {
+public class ToStringTests extends AbstractScalarFunctionTestCase {
public ToStringTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongTests.java
index 44092db499d2d..a1fccac8edfd1 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigDecimal;
@@ -27,7 +27,7 @@
import static org.elasticsearch.xpack.esql.core.type.DataTypeConverter.safeToUnsignedLong;
import static org.elasticsearch.xpack.esql.core.util.NumericUtils.UNSIGNED_LONG_MAX_AS_DOUBLE;
-public class ToUnsignedLongTests extends AbstractFunctionTestCase {
+public class ToUnsignedLongTests extends AbstractScalarFunctionTestCase {
public ToUnsignedLongTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersionTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersionTests.java
index 34281442872a5..1c37afc1c0722 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersionTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersionTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.versionfield.Version;
@@ -22,7 +22,7 @@
import java.util.List;
import java.util.function.Supplier;
-public class ToVersionTests extends AbstractFunctionTestCase {
+public class ToVersionTests extends AbstractScalarFunctionTestCase {
public ToVersionTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffTests.java
index 89cfda5c4bce5..4af2ce1b7cb00 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateDiffTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.time.ZonedDateTime;
@@ -25,7 +25,7 @@
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
-public class DateDiffTests extends AbstractFunctionTestCase {
+public class DateDiffTests extends AbstractScalarFunctionTestCase {
public DateDiffTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateParseTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateParseTests.java
index 161b338cc85b2..f0aa766fb1bf9 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateParseTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateParseTests.java
@@ -17,7 +17,7 @@
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
@@ -28,7 +28,7 @@
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
-public class DateParseTests extends AbstractFunctionTestCase {
+public class DateParseTests extends AbstractScalarFunctionTestCase {
public DateParseTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTruncTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTruncTests.java
index 4c5a7d3734ce3..17d8cd6a57223 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTruncTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DateTruncTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.time.Duration;
@@ -28,7 +28,7 @@
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
-public class DateTruncTests extends AbstractFunctionTestCase {
+public class DateTruncTests extends AbstractScalarFunctionTestCase {
public DateTruncTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/CIDRMatchTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/CIDRMatchTests.java
index 0d8f4bc7ea115..3cdc54f240a96 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/CIDRMatchTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/ip/CIDRMatchTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
@@ -25,7 +25,7 @@
import static org.hamcrest.Matchers.equalTo;
@FunctionName("cidr_match")
-public class CIDRMatchTests extends AbstractFunctionTestCase {
+public class CIDRMatchTests extends AbstractScalarFunctionTestCase {
public CIDRMatchTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
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
index a575eb48c4bd7..298bcb3f49466 100644
--- 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
@@ -16,7 +16,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
@@ -25,7 +25,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class IpPrefixTests extends AbstractFunctionTestCase {
+public class IpPrefixTests extends AbstractScalarFunctionTestCase {
public IpPrefixTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AbsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AbsTests.java
index 7bd195ab86389..b5923c7a5b214 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AbsTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AbsTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -23,7 +23,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class AbsTests extends AbstractFunctionTestCase {
+public class AbsTests extends AbstractScalarFunctionTestCase {
@ParametersFactory
public static Iterable parameters() {
List suppliers = new ArrayList<>();
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AcosTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AcosTests.java
index 02974c10480d2..7c5cd87dfee39 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AcosTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AcosTests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class AcosTests extends AbstractFunctionTestCase {
+public class AcosTests extends AbstractScalarFunctionTestCase {
public AcosTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AsinTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AsinTests.java
index d4d13c2054fcd..38e210d81e5fd 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AsinTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AsinTests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class AsinTests extends AbstractFunctionTestCase {
+public class AsinTests extends AbstractScalarFunctionTestCase {
public AsinTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Atan2Tests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Atan2Tests.java
index 3b81316da5676..1144919094812 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Atan2Tests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Atan2Tests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class Atan2Tests extends AbstractFunctionTestCase {
+public class Atan2Tests extends AbstractScalarFunctionTestCase {
public Atan2Tests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AtanTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AtanTests.java
index c92c626a5601b..c9f7a1baeadbe 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AtanTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/AtanTests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class AtanTests extends AbstractFunctionTestCase {
+public class AtanTests extends AbstractScalarFunctionTestCase {
public AtanTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtTests.java
index 14d6075f5cbe3..f644d8bc72dce 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CbrtTests.java
@@ -14,7 +14,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -24,7 +24,7 @@
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.unsignedLongToDouble;
-public class CbrtTests extends AbstractFunctionTestCase {
+public class CbrtTests extends AbstractScalarFunctionTestCase {
public CbrtTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CeilTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CeilTests.java
index ff61ecfa39687..1572b928a0d75 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CeilTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CeilTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -23,7 +23,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class CeilTests extends AbstractFunctionTestCase {
+public class CeilTests extends AbstractScalarFunctionTestCase {
public CeilTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CosTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CosTests.java
index 61e7a1f051905..dc5eec4f90d32 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CosTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CosTests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class CosTests extends AbstractFunctionTestCase {
+public class CosTests extends AbstractScalarFunctionTestCase {
public CosTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CoshTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CoshTests.java
index 1ea63cc006e9c..79557b15be08a 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CoshTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/CoshTests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class CoshTests extends AbstractFunctionTestCase {
+public class CoshTests extends AbstractScalarFunctionTestCase {
public CoshTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ETests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ETests.java
index 8eb0b80fc21d7..763ad3a2b49c9 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ETests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/ETests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -24,7 +24,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class ETests extends AbstractFunctionTestCase {
+public class ETests extends AbstractScalarFunctionTestCase {
public ETests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/FloorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/FloorTests.java
index f0c990ec64af1..269dabcc6b6b8 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/FloorTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/FloorTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -21,7 +21,7 @@
import java.util.List;
import java.util.function.Supplier;
-public class FloorTests extends AbstractFunctionTestCase {
+public class FloorTests extends AbstractScalarFunctionTestCase {
public FloorTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Log10Tests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Log10Tests.java
index 64329d7824b74..ca0c8718f5ac0 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Log10Tests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/Log10Tests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -24,7 +24,7 @@
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.bigIntegerToUnsignedLong;
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.unsignedLongToDouble;
-public class Log10Tests extends AbstractFunctionTestCase {
+public class Log10Tests extends AbstractScalarFunctionTestCase {
public Log10Tests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/LogTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/LogTests.java
index ce53fdbfc1851..1c002e111e575 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/LogTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/LogTests.java
@@ -13,13 +13,13 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class LogTests extends AbstractFunctionTestCase {
+public class LogTests extends AbstractScalarFunctionTestCase {
public LogTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PiTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PiTests.java
index c21082b905962..8e427fcbae2b8 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PiTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PiTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -24,7 +24,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class PiTests extends AbstractFunctionTestCase {
+public class PiTests extends AbstractScalarFunctionTestCase {
public PiTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PowTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PowTests.java
index 545e7c14ff2b2..bea0f399233fd 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PowTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/PowTests.java
@@ -13,13 +13,13 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class PowTests extends AbstractFunctionTestCase {
+public class PowTests extends AbstractScalarFunctionTestCase {
public PowTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundTests.java
index 5e19d5f606034..c05388a9708da 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/RoundTests.java
@@ -16,7 +16,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -29,7 +29,7 @@
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
-public class RoundTests extends AbstractFunctionTestCase {
+public class RoundTests extends AbstractScalarFunctionTestCase {
public RoundTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumTests.java
index 89c2d07c4470a..21b44134458b7 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SignumTests.java
@@ -14,7 +14,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -22,7 +22,7 @@
import java.util.List;
import java.util.function.Supplier;
-public class SignumTests extends AbstractFunctionTestCase {
+public class SignumTests extends AbstractScalarFunctionTestCase {
public SignumTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SinTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SinTests.java
index 0d9bd6bcae64a..7a1190d86c2bf 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SinTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SinTests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class SinTests extends AbstractFunctionTestCase {
+public class SinTests extends AbstractScalarFunctionTestCase {
public SinTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SinhTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SinhTests.java
index 8f78e8ee67106..b83519c6d1299 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SinhTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SinhTests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class SinhTests extends AbstractFunctionTestCase {
+public class SinhTests extends AbstractScalarFunctionTestCase {
public SinhTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SqrtTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SqrtTests.java
index a1d5b8523175c..9c81bbdc3cd49 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SqrtTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/SqrtTests.java
@@ -14,7 +14,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -24,7 +24,7 @@
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.unsignedLongToDouble;
-public class SqrtTests extends AbstractFunctionTestCase {
+public class SqrtTests extends AbstractScalarFunctionTestCase {
public SqrtTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TanTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TanTests.java
index 86c59a7a06cf4..369c33a1291f1 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TanTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TanTests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class TanTests extends AbstractFunctionTestCase {
+public class TanTests extends AbstractScalarFunctionTestCase {
public TanTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TanhTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TanhTests.java
index 1f4fef4ab15c8..14fdcdca2fe96 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TanhTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TanhTests.java
@@ -12,13 +12,13 @@
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.List;
import java.util.function.Supplier;
-public class TanhTests extends AbstractFunctionTestCase {
+public class TanhTests extends AbstractScalarFunctionTestCase {
public TanhTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TauTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TauTests.java
index aa64dfc6af90d..959db368ce348 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TauTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/math/TauTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -24,7 +24,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class TauTests extends AbstractFunctionTestCase {
+public class TauTests extends AbstractScalarFunctionTestCase {
public TauTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunctionTestCase.java
index 2ea79d8a165c6..212b66027d455 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunctionTestCase.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunctionTestCase.java
@@ -18,7 +18,7 @@
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
import org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
import org.hamcrest.Matcher;
@@ -39,7 +39,7 @@
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.CARTESIAN;
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO;
-public abstract class AbstractMultivalueFunctionTestCase extends AbstractFunctionTestCase {
+public abstract class AbstractMultivalueFunctionTestCase extends AbstractScalarFunctionTestCase {
/**
* Build many test cases with {@code boolean} values.
*/
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendTests.java
index f95747618dd28..7039d9edf794b 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvAppendTests.java
@@ -16,7 +16,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -27,7 +27,7 @@
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.GEO;
import static org.hamcrest.Matchers.equalTo;
-public class MvAppendTests extends AbstractFunctionTestCase {
+public class MvAppendTests extends AbstractScalarFunctionTestCase {
public MvAppendTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatTests.java
index ba4ddb1be84cc..0277093152cba 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvConcatTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
@@ -24,7 +24,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class MvConcatTests extends AbstractFunctionTestCase {
+public class MvConcatTests extends AbstractScalarFunctionTestCase {
public MvConcatTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceTests.java
index 3f6fb841f006f..5684c68051446 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSliceTests.java
@@ -16,7 +16,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -28,7 +28,7 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
-public class MvSliceTests extends AbstractFunctionTestCase {
+public class MvSliceTests extends AbstractScalarFunctionTestCase {
public MvSliceTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortTests.java
index 7c6413e590bfe..a085c0acfa25d 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvSortTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -25,7 +25,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class MvSortTests extends AbstractFunctionTestCase {
+public class MvSortTests extends AbstractScalarFunctionTestCase {
public MvSortTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipTests.java
index 30fe420f29960..e9f0fd5b51516 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvZipTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -25,7 +25,7 @@
import static java.lang.Math.max;
import static org.hamcrest.Matchers.equalTo;
-public class MvZipTests extends AbstractFunctionTestCase {
+public class MvZipTests extends AbstractScalarFunctionTestCase {
public MvZipTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/CoalesceTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/CoalesceTests.java
index 83f5a621c93a5..c779fa9e2789f 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/CoalesceTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/CoalesceTests.java
@@ -22,7 +22,7 @@
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.type.EsField;
import org.elasticsearch.xpack.esql.evaluator.EvalMapper;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.expression.function.scalar.VaragsTestCaseBuilder;
import org.elasticsearch.xpack.esql.expression.function.scalar.spatial.SpatialRelatesFunctionTestCase;
@@ -39,7 +39,7 @@
import static org.elasticsearch.compute.data.BlockUtils.toJavaObject;
import static org.hamcrest.Matchers.equalTo;
-public class CoalesceTests extends AbstractFunctionTestCase {
+public class CoalesceTests extends AbstractScalarFunctionTestCase {
public CoalesceTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/IsNotNullTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/IsNotNullTests.java
index 299b66433dcd0..b99b47b6f505a 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/IsNotNullTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/IsNotNullTests.java
@@ -16,7 +16,7 @@
import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNotNull;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
import org.hamcrest.Matcher;
@@ -27,7 +27,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class IsNotNullTests extends AbstractFunctionTestCase {
+public class IsNotNullTests extends AbstractScalarFunctionTestCase {
public IsNotNullTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/IsNullTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/IsNullTests.java
index 606e9598bda63..7abfad39967a5 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/IsNullTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/nulls/IsNullTests.java
@@ -16,7 +16,7 @@
import org.elasticsearch.xpack.esql.core.expression.predicate.nulls.IsNull;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
import org.hamcrest.Matcher;
@@ -27,7 +27,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class IsNullTests extends AbstractFunctionTestCase {
+public class IsNullTests extends AbstractScalarFunctionTestCase {
public IsNullTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/BinarySpatialFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/BinarySpatialFunctionTestCase.java
index 37e09caf0d105..a30cce9f765ed 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/BinarySpatialFunctionTestCase.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/BinarySpatialFunctionTestCase.java
@@ -13,7 +13,7 @@
import org.elasticsearch.xpack.esql.core.expression.TypeResolutions;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -33,7 +33,7 @@
import static org.elasticsearch.xpack.esql.type.EsqlDataTypes.isString;
import static org.hamcrest.Matchers.equalTo;
-public abstract class BinarySpatialFunctionTestCase extends AbstractFunctionTestCase {
+public abstract class BinarySpatialFunctionTestCase extends AbstractScalarFunctionTestCase {
private static String getFunctionClassName() {
Class> testClass = getTestClass();
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StXTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StXTests.java
index fa0fc8465ce7a..71e73398ddcd4 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StXTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StXTests.java
@@ -13,7 +13,7 @@
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
@@ -25,7 +25,7 @@
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED;
@FunctionName("st_x")
-public class StXTests extends AbstractFunctionTestCase {
+public class StXTests extends AbstractScalarFunctionTestCase {
public StXTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StYTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StYTests.java
index 15f34271be779..a30ae924754d6 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StYTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/spatial/StYTests.java
@@ -13,7 +13,7 @@
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.tree.Source;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
@@ -25,7 +25,7 @@
import static org.elasticsearch.xpack.esql.core.util.SpatialCoordinateTypes.UNSPECIFIED;
@FunctionName("st_y")
-public class StYTests extends AbstractFunctionTestCase {
+public class StYTests extends AbstractScalarFunctionTestCase {
public StYTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/AbstractTrimTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/AbstractTrimTests.java
index 27e3fc8684efc..a92f3ffb49533 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/AbstractTrimTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/AbstractTrimTests.java
@@ -9,7 +9,7 @@
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -18,7 +18,7 @@
import static org.hamcrest.Matchers.equalTo;
-public abstract class AbstractTrimTests extends AbstractFunctionTestCase {
+public abstract class AbstractTrimTests extends AbstractScalarFunctionTestCase {
static Iterable parameters(String name, boolean trimLeading, boolean trimTrailing) {
List suppliers = new ArrayList<>();
for (DataType type : strings()) {
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatTests.java
index f46ae25fddfc7..c398faacb90d0 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ConcatTests.java
@@ -18,7 +18,7 @@
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
@@ -35,7 +35,7 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
-public class ConcatTests extends AbstractFunctionTestCase {
+public class ConcatTests extends AbstractScalarFunctionTestCase {
public ConcatTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/EndsWithTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/EndsWithTests.java
index 863243a352bb0..5ae69b03ae882 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/EndsWithTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/EndsWithTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -24,7 +24,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class EndsWithTests extends AbstractFunctionTestCase {
+public class EndsWithTests extends AbstractScalarFunctionTestCase {
public EndsWithTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LeftTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LeftTests.java
index 7d6e3439c8063..88ee7881e128a 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LeftTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LeftTests.java
@@ -17,7 +17,7 @@
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -28,7 +28,7 @@
import static org.elasticsearch.compute.data.BlockUtils.toJavaObject;
import static org.hamcrest.Matchers.equalTo;
-public class LeftTests extends AbstractFunctionTestCase {
+public class LeftTests extends AbstractScalarFunctionTestCase {
public LeftTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LengthTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LengthTests.java
index 4a7e6b3a0996d..a1451b6bedf7a 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LengthTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LengthTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -25,7 +25,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class LengthTests extends AbstractFunctionTestCase {
+public class LengthTests extends AbstractScalarFunctionTestCase {
public LengthTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LocateTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LocateTests.java
index 011252a3f7e14..13d8edf489a66 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LocateTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/LocateTests.java
@@ -15,7 +15,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.nio.charset.StandardCharsets;
@@ -30,7 +30,7 @@
/**
* Tests for {@link Locate} function.
*/
-public class LocateTests extends AbstractFunctionTestCase {
+public class LocateTests extends AbstractScalarFunctionTestCase {
public LocateTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeTests.java
index e673be2ad5290..0074f83b3bbce 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RLikeTests.java
@@ -17,7 +17,7 @@
import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLikePattern;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.elasticsearch.xpack.esql.type.EsqlDataTypes;
@@ -30,7 +30,7 @@
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
-public class RLikeTests extends AbstractFunctionTestCase {
+public class RLikeTests extends AbstractScalarFunctionTestCase {
public RLikeTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatStaticTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatStaticTests.java
index dc266066bd424..7c8426a5fe3fc 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatStaticTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatStaticTests.java
@@ -24,7 +24,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.type.EsField;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.junit.After;
import java.util.ArrayList;
@@ -36,7 +36,7 @@
/**
* These tests create rows that are 1MB in size. Test classes
- * which extend AbstractFunctionTestCase rerun test cases with
+ * which extend AbstractScalarFunctionTestCase rerun test cases with
* many randomized inputs. Unfortunately, tests are run with
* limited memory, and instantiating many copies of these
* tests with large rows causes out of memory.
@@ -63,7 +63,7 @@ public void testTooBig() {
public String process(String str, int number) {
try (
- var eval = AbstractFunctionTestCase.evaluator(
+ var eval = AbstractScalarFunctionTestCase.evaluator(
new Repeat(Source.EMPTY, field("string", DataType.KEYWORD), field("number", DataType.INTEGER))
).get(driverContext());
Block block = eval.eval(row(List.of(new BytesRef(str), number)));
@@ -73,7 +73,7 @@ public String process(String str, int number) {
}
/**
- * The following fields and methods were borrowed from AbstractFunctionTestCase
+ * The following fields and methods were borrowed from AbstractScalarFunctionTestCase
*/
private final List breakers = Collections.synchronizedList(new ArrayList<>());
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatTests.java
index cb89dc168b928..8d0368d1c618f 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RepeatTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -24,7 +24,7 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
-public class RepeatTests extends AbstractFunctionTestCase {
+public class RepeatTests extends AbstractScalarFunctionTestCase {
public RepeatTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ReplaceTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ReplaceTests.java
index 82581b69f8713..fe77b9dcdb075 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ReplaceTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ReplaceTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -24,7 +24,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class ReplaceTests extends AbstractFunctionTestCase {
+public class ReplaceTests extends AbstractScalarFunctionTestCase {
public ReplaceTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RightTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RightTests.java
index 9d2b55e02fff7..cc98edb85f547 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RightTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/RightTests.java
@@ -17,7 +17,7 @@
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -28,7 +28,7 @@
import static org.elasticsearch.compute.data.BlockUtils.toJavaObject;
import static org.hamcrest.Matchers.equalTo;
-public class RightTests extends AbstractFunctionTestCase {
+public class RightTests extends AbstractScalarFunctionTestCase {
public RightTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/SplitTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/SplitTests.java
index bf2dd0359a352..dd28b43bd66ed 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/SplitTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/SplitTests.java
@@ -21,7 +21,7 @@
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -34,7 +34,7 @@
import static org.elasticsearch.compute.data.BlockUtils.toJavaObject;
import static org.hamcrest.Matchers.equalTo;
-public class SplitTests extends AbstractFunctionTestCase {
+public class SplitTests extends AbstractScalarFunctionTestCase {
public SplitTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/StartsWithTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/StartsWithTests.java
index f0c51a9b22e55..bd01f926d1571 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/StartsWithTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/StartsWithTests.java
@@ -14,7 +14,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -23,7 +23,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class StartsWithTests extends AbstractFunctionTestCase {
+public class StartsWithTests extends AbstractScalarFunctionTestCase {
public StartsWithTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/SubstringTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/SubstringTests.java
index 0ee60cfc77d2f..1c49d3b408ad6 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/SubstringTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/SubstringTests.java
@@ -17,7 +17,7 @@
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -29,7 +29,7 @@
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
-public class SubstringTests extends AbstractFunctionTestCase {
+public class SubstringTests extends AbstractScalarFunctionTestCase {
public SubstringTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeTests.java
index 3aee4a92e9570..06736db28b2cc 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/WildcardLikeTests.java
@@ -17,7 +17,7 @@
import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPattern;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.FunctionName;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
@@ -29,7 +29,7 @@
import static org.hamcrest.Matchers.startsWith;
@FunctionName("like")
-public class WildcardLikeTests extends AbstractFunctionTestCase {
+public class WildcardLikeTests extends AbstractScalarFunctionTestCase {
public WildcardLikeTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/AbstractBinaryOperatorTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/AbstractBinaryOperatorTestCase.java
index 7e803ea2f84a0..a9663f9e37852 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/AbstractBinaryOperatorTestCase.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/AbstractBinaryOperatorTestCase.java
@@ -16,7 +16,7 @@
import org.elasticsearch.xpack.esql.core.tree.Location;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -33,7 +33,7 @@
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
-public abstract class AbstractBinaryOperatorTestCase extends AbstractFunctionTestCase {
+public abstract class AbstractBinaryOperatorTestCase extends AbstractScalarFunctionTestCase {
protected abstract Matcher resultsMatcher(List typedData);
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/BreakerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/BreakerTests.java
index b5bea7d858187..a5408cdb971c4 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/BreakerTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/BreakerTests.java
@@ -26,6 +26,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Div;
import org.junit.After;
@@ -63,7 +64,7 @@ public BreakerTests(ByteSizeValue limit, Expression expression) {
public void testBreaker() {
DriverContext unlimited = driverContext(ByteSizeValue.ofGb(1));
DriverContext context = driverContext(limit);
- EvalOperator.ExpressionEvaluator eval = AbstractFunctionTestCase.evaluator(expression).get(context);
+ EvalOperator.ExpressionEvaluator eval = AbstractScalarFunctionTestCase.evaluator(expression).get(context);
try (Block b = unlimited.blockFactory().newConstantNullBlock(1)) {
Exception e = expectThrows(CircuitBreakingException.class, () -> eval.eval(new Page(b)));
assertThat(e.getMessage(), equalTo("over test limit"));
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/AddTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/AddTests.java
index 26a5d58b33900..c8a2511e34211 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/AddTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/AddTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -36,7 +36,7 @@
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
-public class AddTests extends AbstractFunctionTestCase {
+public class AddTests extends AbstractScalarFunctionTestCase {
public AddTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/DivTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/DivTests.java
index a50d44822a4e3..7bc5b24651218 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/DivTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/DivTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -26,7 +26,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class DivTests extends AbstractFunctionTestCase {
+public class DivTests extends AbstractScalarFunctionTestCase {
public DivTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/ModTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/ModTests.java
index ce67f6453362b..133324bafd134 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/ModTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/ModTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import org.hamcrest.Matcher;
@@ -26,7 +26,7 @@
import static org.hamcrest.Matchers.equalTo;
-public class ModTests extends AbstractFunctionTestCase {
+public class ModTests extends AbstractScalarFunctionTestCase {
public ModTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/MulTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/MulTests.java
index 8b4dfa88415be..7472636611063 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/MulTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/MulTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.util.ArrayList;
@@ -24,7 +24,7 @@
import static org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.AbstractArithmeticTestCase.arithmeticExceptionOverflowCase;
import static org.hamcrest.Matchers.equalTo;
-public class MulTests extends AbstractFunctionTestCase {
+public class MulTests extends AbstractScalarFunctionTestCase {
public MulTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/NegTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/NegTests.java
index a628416ecc4b7..7eadd74eaeb9e 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/NegTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/NegTests.java
@@ -16,7 +16,7 @@
import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.time.Duration;
@@ -28,7 +28,7 @@
import static org.elasticsearch.compute.data.BlockUtils.toJavaObject;
import static org.hamcrest.Matchers.equalTo;
-public class NegTests extends AbstractFunctionTestCase {
+public class NegTests extends AbstractScalarFunctionTestCase {
public NegTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/SubTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/SubTests.java
index e75ee9333ba54..9dc024ac1e8ff 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/SubTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/arithmetic/SubTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.time.Duration;
@@ -29,7 +29,7 @@
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
-public class SubTests extends AbstractFunctionTestCase {
+public class SubTests extends AbstractScalarFunctionTestCase {
public SubTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/EqualsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/EqualsTests.java
index 3817bbe9cc74c..d3539f4a56fe9 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/EqualsTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/EqualsTests.java
@@ -14,7 +14,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -22,7 +22,7 @@
import java.util.List;
import java.util.function.Supplier;
-public class EqualsTests extends AbstractFunctionTestCase {
+public class EqualsTests extends AbstractScalarFunctionTestCase {
public EqualsTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
@@ -196,7 +196,10 @@ public static Iterable parameters() {
);
return parameterSuppliersFromTypedData(
- errorsForCasesWithoutExamples(anyNullIsNull(true, suppliers), AbstractFunctionTestCase::errorMessageStringForBinaryOperators)
+ errorsForCasesWithoutExamples(
+ anyNullIsNull(true, suppliers),
+ AbstractScalarFunctionTestCase::errorMessageStringForBinaryOperators
+ )
);
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/GreaterThanOrEqualTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/GreaterThanOrEqualTests.java
index f25638b482817..b2174f7be1593 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/GreaterThanOrEqualTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/GreaterThanOrEqualTests.java
@@ -15,7 +15,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -23,7 +23,7 @@
import java.util.List;
import java.util.function.Supplier;
-public class GreaterThanOrEqualTests extends AbstractFunctionTestCase {
+public class GreaterThanOrEqualTests extends AbstractScalarFunctionTestCase {
public GreaterThanOrEqualTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
@@ -131,7 +131,10 @@ public static Iterable parameters() {
);
return parameterSuppliersFromTypedData(
- errorsForCasesWithoutExamples(anyNullIsNull(true, suppliers), AbstractFunctionTestCase::errorMessageStringForBinaryOperators)
+ errorsForCasesWithoutExamples(
+ anyNullIsNull(true, suppliers),
+ AbstractScalarFunctionTestCase::errorMessageStringForBinaryOperators
+ )
);
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/GreaterThanTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/GreaterThanTests.java
index 0735e0dfd64f2..edb276e16dd99 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/GreaterThanTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/GreaterThanTests.java
@@ -15,7 +15,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -23,7 +23,7 @@
import java.util.List;
import java.util.function.Supplier;
-public class GreaterThanTests extends AbstractFunctionTestCase {
+public class GreaterThanTests extends AbstractScalarFunctionTestCase {
public GreaterThanTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
@@ -131,7 +131,10 @@ public static Iterable parameters() {
);
return parameterSuppliersFromTypedData(
- errorsForCasesWithoutExamples(anyNullIsNull(true, suppliers), AbstractFunctionTestCase::errorMessageStringForBinaryOperators)
+ errorsForCasesWithoutExamples(
+ anyNullIsNull(true, suppliers),
+ AbstractScalarFunctionTestCase::errorMessageStringForBinaryOperators
+ )
);
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/LessThanOrEqualTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/LessThanOrEqualTests.java
index 4a802dfcaf975..d89421f579b08 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/LessThanOrEqualTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/LessThanOrEqualTests.java
@@ -15,7 +15,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -23,7 +23,7 @@
import java.util.List;
import java.util.function.Supplier;
-public class LessThanOrEqualTests extends AbstractFunctionTestCase {
+public class LessThanOrEqualTests extends AbstractScalarFunctionTestCase {
public LessThanOrEqualTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
@@ -131,7 +131,10 @@ public static Iterable parameters() {
);
return parameterSuppliersFromTypedData(
- errorsForCasesWithoutExamples(anyNullIsNull(true, suppliers), AbstractFunctionTestCase::errorMessageStringForBinaryOperators)
+ errorsForCasesWithoutExamples(
+ anyNullIsNull(true, suppliers),
+ AbstractScalarFunctionTestCase::errorMessageStringForBinaryOperators
+ )
);
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/LessThanTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/LessThanTests.java
index 6f3f2441c6d00..9487d774ff221 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/LessThanTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/LessThanTests.java
@@ -15,7 +15,7 @@
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.NumericUtils;
-import org.elasticsearch.xpack.esql.expression.function.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -23,7 +23,7 @@
import java.util.List;
import java.util.function.Supplier;
-public class LessThanTests extends AbstractFunctionTestCase {
+public class LessThanTests extends AbstractScalarFunctionTestCase {
public LessThanTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
@@ -131,7 +131,10 @@ public static Iterable parameters() {
);
return parameterSuppliersFromTypedData(
- errorsForCasesWithoutExamples(anyNullIsNull(true, suppliers), AbstractFunctionTestCase::errorMessageStringForBinaryOperators)
+ errorsForCasesWithoutExamples(
+ anyNullIsNull(true, suppliers),
+ AbstractScalarFunctionTestCase::errorMessageStringForBinaryOperators
+ )
);
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/NotEqualsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/NotEqualsTests.java
index 174e2457eb0a5..e7d8c680ba5cc 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/NotEqualsTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/predicate/operator/comparison/NotEqualsTests.java
@@ -13,7 +13,7 @@
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.AbstractFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
import java.math.BigInteger;
@@ -21,7 +21,7 @@
import java.util.List;
import java.util.function.Supplier;
-public class NotEqualsTests extends AbstractFunctionTestCase {
+public class NotEqualsTests extends AbstractScalarFunctionTestCase {
public NotEqualsTests(@Name("TestCase") Supplier testCaseSupplier) {
this.testCase = testCaseSupplier.get();
}
@@ -190,7 +190,10 @@ public static Iterable parameters() {
)
);
return parameterSuppliersFromTypedData(
- errorsForCasesWithoutExamples(anyNullIsNull(true, suppliers), AbstractFunctionTestCase::errorMessageStringForBinaryOperators)
+ errorsForCasesWithoutExamples(
+ anyNullIsNull(true, suppliers),
+ AbstractScalarFunctionTestCase::errorMessageStringForBinaryOperators
+ )
);
}
From 10ad8a642eaa33533d5e013a2c7f9be09990b40f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20Fred=C3=A9n?=
<109296772+jfreden@users.noreply.github.com>
Date: Thu, 27 Jun 2024 13:58:50 +0200
Subject: [PATCH 008/216] Make await security migrations more robust (#109854)
* Make await security migrations more robust
---
muted-tests.yml | 2 -
.../org/elasticsearch/TransportVersions.java | 1 +
.../elasticsearch/index/IndexVersions.java | 1 +
.../support/SecurityMigrationTaskParams.java | 22 ++++++-
.../test/SecuritySingleNodeTestCase.java | 13 +++-
.../store/NativePrivilegeStoreCacheTests.java | 2 -
.../xpack/security/Security.java | 61 +++++++++++--------
.../support/SecurityIndexManager.java | 35 ++++++++++-
.../support/SecurityMigrationExecutor.java | 18 +++++-
.../security/support/SecurityMigrations.java | 4 ++
.../authc/AuthenticationServiceTests.java | 1 +
.../authc/esnative/NativeRealmTests.java | 1 +
.../mapper/NativeRoleMappingStoreTests.java | 1 +
.../authz/store/CompositeRolesStoreTests.java | 1 +
.../store/NativePrivilegeStoreTests.java | 1 +
.../CacheInvalidatorRegistryTests.java | 1 +
.../SecurityMigrationExecutorTests.java | 25 +++-----
17 files changed, 136 insertions(+), 54 deletions(-)
diff --git a/muted-tests.yml b/muted-tests.yml
index 748f6f463f345..d5e603bbed2f0 100644
--- a/muted-tests.yml
+++ b/muted-tests.yml
@@ -65,8 +65,6 @@ tests:
- class: "org.elasticsearch.xpack.esql.action.AsyncEsqlQueryActionIT"
issue: "https://github.com/elastic/elasticsearch/issues/109944"
method: "testBasicAsyncExecution"
-- class: "org.elasticsearch.xpack.security.authz.store.NativePrivilegeStoreCacheTests"
- issue: "https://github.com/elastic/elasticsearch/issues/110015"
- class: "org.elasticsearch.action.admin.indices.rollover.RolloverIT"
issue: "https://github.com/elastic/elasticsearch/issues/110034"
method: "testRolloverWithClosedWriteIndex"
diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java
index 767ba71cd262f..0a75ccfbbedf3 100644
--- a/server/src/main/java/org/elasticsearch/TransportVersions.java
+++ b/server/src/main/java/org/elasticsearch/TransportVersions.java
@@ -203,6 +203,7 @@ static TransportVersion def(int id) {
public static final TransportVersion ML_INFERENCE_GOOGLE_VERTEX_AI_EMBEDDINGS_ADDED = def(8_694_00_0);
public static final TransportVersion EVENT_INGESTED_RANGE_IN_CLUSTER_STATE = def(8_695_00_0);
public static final TransportVersion ESQL_ADD_AGGREGATE_TYPE = def(8_696_00_0);
+ public static final TransportVersion SECURITY_MIGRATIONS_MIGRATION_NEEDED_ADDED = def(8_697_00_0);
/*
* STOP! READ THIS FIRST! No, really,
diff --git a/server/src/main/java/org/elasticsearch/index/IndexVersions.java b/server/src/main/java/org/elasticsearch/index/IndexVersions.java
index 6dd87dedf24f7..f08b97cd7033e 100644
--- a/server/src/main/java/org/elasticsearch/index/IndexVersions.java
+++ b/server/src/main/java/org/elasticsearch/index/IndexVersions.java
@@ -109,6 +109,7 @@ private static IndexVersion def(int id, Version luceneVersion) {
public static final IndexVersion SEMANTIC_TEXT_FIELD_TYPE = def(8_507_00_0, Version.LUCENE_9_10_0);
public static final IndexVersion UPGRADE_TO_LUCENE_9_11 = def(8_508_00_0, Version.LUCENE_9_11_0);
public static final IndexVersion UNIQUE_TOKEN_FILTER_POS_FIX = def(8_509_00_0, Version.LUCENE_9_11_0);
+ public static final IndexVersion ADD_SECURITY_MIGRATION = def(8_510_00_0, Version.LUCENE_9_11_0);
/*
* STOP! READ THIS FIRST! No, really,
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/SecurityMigrationTaskParams.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/SecurityMigrationTaskParams.java
index d54f3098fead9..14cc4d3d6f5b9 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/SecurityMigrationTaskParams.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/SecurityMigrationTaskParams.java
@@ -21,33 +21,46 @@
import java.io.IOException;
import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg;
+import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
public class SecurityMigrationTaskParams implements PersistentTaskParams {
public static final String TASK_NAME = "security-migration";
private final int migrationVersion;
+ private final boolean migrationNeeded;
+
public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(
TASK_NAME,
true,
- (arr) -> new SecurityMigrationTaskParams((int) arr[0])
+ (arr) -> new SecurityMigrationTaskParams((int) arr[0], arr[1] == null || (boolean) arr[1])
);
static {
PARSER.declareInt(constructorArg(), new ParseField("migration_version"));
+ PARSER.declareBoolean(optionalConstructorArg(), new ParseField("migration_needed"));
}
- public SecurityMigrationTaskParams(int migrationVersion) {
+ public SecurityMigrationTaskParams(int migrationVersion, boolean migrationNeeded) {
this.migrationVersion = migrationVersion;
+ this.migrationNeeded = migrationNeeded;
}
public SecurityMigrationTaskParams(StreamInput in) throws IOException {
this.migrationVersion = in.readInt();
+ if (in.getTransportVersion().onOrAfter(TransportVersions.SECURITY_MIGRATIONS_MIGRATION_NEEDED_ADDED)) {
+ this.migrationNeeded = in.readBoolean();
+ } else {
+ this.migrationNeeded = true;
+ }
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeInt(migrationVersion);
+ if (out.getTransportVersion().onOrAfter(TransportVersions.SECURITY_MIGRATIONS_MIGRATION_NEEDED_ADDED)) {
+ out.writeBoolean(migrationNeeded);
+ }
}
@Override
@@ -64,6 +77,7 @@ public TransportVersion getMinimalSupportedVersion() {
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.field("migration_version", migrationVersion);
+ builder.field("migration_needed", migrationNeeded);
builder.endObject();
return builder;
}
@@ -75,4 +89,8 @@ public static SecurityMigrationTaskParams fromXContent(XContentParser parser) {
public int getMigrationVersion() {
return migrationVersion;
}
+
+ public boolean isMigrationNeeded() {
+ return migrationNeeded;
+ }
}
diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/test/SecuritySingleNodeTestCase.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/test/SecuritySingleNodeTestCase.java
index 2eb45021a5bfe..07bdd83c9a144 100644
--- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/test/SecuritySingleNodeTestCase.java
+++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/test/SecuritySingleNodeTestCase.java
@@ -15,6 +15,7 @@
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.internal.Client;
import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.network.NetworkAddress;
import org.elasticsearch.common.settings.MockSecureSettings;
@@ -26,7 +27,9 @@
import org.elasticsearch.license.LicenseSettings;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
+import org.elasticsearch.xpack.core.security.test.TestRestrictedIndices;
import org.elasticsearch.xpack.security.LocalStateSecurity;
+import org.elasticsearch.xpack.security.support.SecurityMigrations;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -41,10 +44,9 @@
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
-import static org.elasticsearch.persistent.PersistentTasksCustomMetadata.getTaskWithId;
import static org.elasticsearch.test.SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING;
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
-import static org.elasticsearch.xpack.core.security.support.SecurityMigrationTaskParams.TASK_NAME;
+import static org.elasticsearch.xpack.security.support.SecurityIndexManager.getMigrationVersionFromIndexMetadata;
import static org.hamcrest.Matchers.hasItem;
/**
@@ -90,7 +92,12 @@ public void tearDown() throws Exception {
}
private boolean isMigrationComplete(ClusterState state) {
- return getTaskWithId(state, TASK_NAME) == null;
+ IndexMetadata indexMetadata = state.metadata().index(TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_7);
+ if (indexMetadata == null) {
+ // If index doesn't exist, no migration needed
+ return true;
+ }
+ return getMigrationVersionFromIndexMetadata(indexMetadata) == SecurityMigrations.MIGRATIONS_BY_VERSION.lastKey();
}
private void awaitSecurityMigration() {
diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreCacheTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreCacheTests.java
index 3094a10b1572d..d11ca70744b7b 100644
--- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreCacheTests.java
+++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreCacheTests.java
@@ -116,7 +116,6 @@ public void configureApplicationPrivileges() {
assertEquals(6, putPrivilegesResponse.created().values().stream().mapToInt(List::size).sum());
}
- @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/109894")
public void testGetPrivilegesUsesCache() {
final Client client = client();
@@ -205,7 +204,6 @@ public void testPopulationOfCacheWhenLoadingPrivilegesForAllApplications() {
assertEquals(1, new GetPrivilegesRequestBuilder(client).application("app-1").privileges("write").get().privileges().length);
}
- @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/109895")
public void testSuffixWildcard() {
final Client client = client();
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java
index 404b9b85e2b24..bbb1feeef8d44 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java
@@ -788,7 +788,8 @@ Collection createComponents(
this.persistentTasksService.set(persistentTasksService);
systemIndices.getMainIndexManager().addStateListener((oldState, newState) -> {
- if (clusterService.state().nodes().isLocalNodeElectedMaster()) {
+ // Only consider applying migrations if it's the master node and the security index exists
+ if (clusterService.state().nodes().isLocalNodeElectedMaster() && newState.indexExists()) {
applyPendingSecurityMigrations(newState);
}
});
@@ -1203,43 +1204,53 @@ Collection createComponents(
}
private void applyPendingSecurityMigrations(SecurityIndexManager.State newState) {
+ // If no migrations have been applied and the security index is on the latest version (new index), all migrations can be skipped
+ if (newState.migrationsVersion == 0 && newState.createdOnLatestVersion) {
+ submitPersistentMigrationTask(SecurityMigrations.MIGRATIONS_BY_VERSION.lastKey(), false);
+ return;
+ }
+
Map.Entry nextMigration = SecurityMigrations.MIGRATIONS_BY_VERSION.higherEntry(
newState.migrationsVersion
);
- if (nextMigration == null) {
- return;
- }
-
// Check if next migration that has not been applied is eligible to run on the current cluster
- if (systemIndices.getMainIndexManager().isEligibleSecurityMigration(nextMigration.getValue()) == false) {
+ if (nextMigration == null || systemIndices.getMainIndexManager().isEligibleSecurityMigration(nextMigration.getValue()) == false) {
// Reset retry counter if all eligible migrations have been applied successfully
nodeLocalMigrationRetryCount.set(0);
} else if (nodeLocalMigrationRetryCount.get() > MAX_SECURITY_MIGRATION_RETRY_COUNT) {
logger.warn("Security migration failed [" + nodeLocalMigrationRetryCount.get() + "] times, restart node to retry again.");
} else if (systemIndices.getMainIndexManager().isReadyForSecurityMigration(nextMigration.getValue())) {
- nodeLocalMigrationRetryCount.incrementAndGet();
- persistentTasksService.get()
- .sendStartRequest(
- SecurityMigrationTaskParams.TASK_NAME,
- SecurityMigrationTaskParams.TASK_NAME,
- new SecurityMigrationTaskParams(newState.migrationsVersion),
- null,
- ActionListener.wrap((response) -> {
- logger.debug("Security migration task submitted");
- }, (exception) -> {
- // Do nothing if the task is already in progress
- if (ExceptionsHelper.unwrapCause(exception) instanceof ResourceAlreadyExistsException) {
- // Do not count ResourceAlreadyExistsException as failure
- nodeLocalMigrationRetryCount.decrementAndGet();
- } else {
- logger.warn("Submit security migration task failed: " + exception.getCause());
- }
- })
- );
+ submitPersistentMigrationTask(newState.migrationsVersion);
}
}
+ private void submitPersistentMigrationTask(int migrationsVersion) {
+ submitPersistentMigrationTask(migrationsVersion, true);
+ }
+
+ private void submitPersistentMigrationTask(int migrationsVersion, boolean securityMigrationNeeded) {
+ nodeLocalMigrationRetryCount.incrementAndGet();
+ persistentTasksService.get()
+ .sendStartRequest(
+ SecurityMigrationTaskParams.TASK_NAME,
+ SecurityMigrationTaskParams.TASK_NAME,
+ new SecurityMigrationTaskParams(migrationsVersion, securityMigrationNeeded),
+ null,
+ ActionListener.wrap((response) -> {
+ logger.debug("Security migration task submitted");
+ }, (exception) -> {
+ // Do nothing if the task is already in progress
+ if (ExceptionsHelper.unwrapCause(exception) instanceof ResourceAlreadyExistsException) {
+ // Do not count ResourceAlreadyExistsException as failure
+ nodeLocalMigrationRetryCount.decrementAndGet();
+ } else {
+ logger.warn("Submit security migration task failed: " + exception.getCause());
+ }
+ })
+ );
+ }
+
private AuthorizationEngine getAuthorizationEngine() {
return findValueFromExtensions("authorization engine", extension -> extension.getAuthorizationEngine(settings));
}
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java
index 1ac22bfd21883..1796b43b0726f 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java
@@ -38,6 +38,7 @@
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexNotFoundException;
+import org.elasticsearch.index.IndexVersion;
import org.elasticsearch.indices.IndexClosedException;
import org.elasticsearch.indices.SystemIndexDescriptor;
import org.elasticsearch.rest.RestStatus;
@@ -55,6 +56,7 @@
import java.util.stream.Collectors;
import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_FORMAT_SETTING;
+import static org.elasticsearch.cluster.metadata.IndexMetadata.SETTING_INDEX_VERSION_CREATED;
import static org.elasticsearch.indices.SystemIndexDescriptor.VERSION_META_KEY;
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
import static org.elasticsearch.xpack.core.security.action.UpdateIndexMigrationVersionAction.MIGRATION_VERSION_CUSTOM_DATA_KEY;
@@ -244,6 +246,19 @@ private SystemIndexDescriptor.MappingsVersion getMinSecurityIndexMappingVersion(
return mappingsVersion == null ? new SystemIndexDescriptor.MappingsVersion(1, 0) : mappingsVersion;
}
+ /**
+ * Check if the index was created on the latest index version available in the cluster
+ */
+ private static boolean isCreatedOnLatestVersion(IndexMetadata indexMetadata, ClusterState clusterState) {
+ final IndexVersion indexVersionCreated = indexMetadata != null
+ ? SETTING_INDEX_VERSION_CREATED.get(indexMetadata.getSettings())
+ : null;
+ return indexVersionCreated != null
+ && indexVersionCreated.onOrAfter(
+ IndexVersion.min(IndexVersion.current(), clusterState.nodes().getMaxDataNodeCompatibleIndexVersion())
+ );
+ }
+
@Override
public void clusterChanged(ClusterChangedEvent event) {
if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
@@ -254,7 +269,7 @@ public void clusterChanged(ClusterChangedEvent event) {
}
final State previousState = state;
final IndexMetadata indexMetadata = resolveConcreteIndex(systemIndexDescriptor.getAliasName(), event.state().metadata());
- final Map customMetadata = indexMetadata == null ? null : indexMetadata.getCustomData(MIGRATION_VERSION_CUSTOM_KEY);
+ final boolean createdOnLatestVersion = isCreatedOnLatestVersion(indexMetadata, event.state());
final Instant creationTime = indexMetadata != null ? Instant.ofEpochMilli(indexMetadata.getCreationDate()) : null;
final boolean isIndexUpToDate = indexMetadata == null
|| INDEX_FORMAT_SETTING.get(indexMetadata.getSettings()) == systemIndexDescriptor.getIndexFormat();
@@ -262,7 +277,7 @@ public void clusterChanged(ClusterChangedEvent event) {
final boolean indexAvailableForWrite = available.v1();
final boolean indexAvailableForSearch = available.v2();
final boolean mappingIsUpToDate = indexMetadata == null || checkIndexMappingUpToDate(event.state());
- final int migrationsVersion = customMetadata == null ? 0 : Integer.parseInt(customMetadata.get(MIGRATION_VERSION_CUSTOM_DATA_KEY));
+ final int migrationsVersion = getMigrationVersionFromIndexMetadata(indexMetadata);
final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion = getMinSecurityIndexMappingVersion(event.state());
final int indexMappingVersion = loadIndexMappingVersion(systemIndexDescriptor.getAliasName(), event.state());
final String concreteIndexName = indexMetadata == null
@@ -290,6 +305,7 @@ public void clusterChanged(ClusterChangedEvent event) {
indexAvailableForSearch,
indexAvailableForWrite,
mappingIsUpToDate,
+ createdOnLatestVersion,
migrationsVersion,
minClusterMappingVersion,
indexMappingVersion,
@@ -310,6 +326,15 @@ public void clusterChanged(ClusterChangedEvent event) {
}
}
+ public static int getMigrationVersionFromIndexMetadata(IndexMetadata indexMetadata) {
+ Map customMetadata = indexMetadata == null ? null : indexMetadata.getCustomData(MIGRATION_VERSION_CUSTOM_KEY);
+ if (customMetadata == null) {
+ return 0;
+ }
+ String migrationVersion = customMetadata.get(MIGRATION_VERSION_CUSTOM_DATA_KEY);
+ return migrationVersion == null ? 0 : Integer.parseInt(migrationVersion);
+ }
+
public void onStateRecovered(Consumer recoveredStateConsumer) {
BiConsumer stateChangeListener = (previousState, nextState) -> {
boolean stateJustRecovered = previousState == UNRECOVERED_STATE && nextState != UNRECOVERED_STATE;
@@ -588,6 +613,7 @@ public static class State {
false,
false,
false,
+ false,
null,
null,
null,
@@ -602,6 +628,7 @@ public static class State {
public final boolean indexAvailableForSearch;
public final boolean indexAvailableForWrite;
public final boolean mappingUpToDate;
+ public final boolean createdOnLatestVersion;
public final Integer migrationsVersion;
// Min mapping version supported by the descriptors in the cluster
public final SystemIndexDescriptor.MappingsVersion minClusterMappingVersion;
@@ -619,6 +646,7 @@ public State(
boolean indexAvailableForSearch,
boolean indexAvailableForWrite,
boolean mappingUpToDate,
+ boolean createdOnLatestVersion,
Integer migrationsVersion,
SystemIndexDescriptor.MappingsVersion minClusterMappingVersion,
Integer indexMappingVersion,
@@ -634,6 +662,7 @@ public State(
this.indexAvailableForWrite = indexAvailableForWrite;
this.mappingUpToDate = mappingUpToDate;
this.migrationsVersion = migrationsVersion;
+ this.createdOnLatestVersion = createdOnLatestVersion;
this.minClusterMappingVersion = minClusterMappingVersion;
this.indexMappingVersion = indexMappingVersion;
this.concreteIndexName = concreteIndexName;
@@ -653,6 +682,7 @@ public boolean equals(Object o) {
&& indexAvailableForSearch == state.indexAvailableForSearch
&& indexAvailableForWrite == state.indexAvailableForWrite
&& mappingUpToDate == state.mappingUpToDate
+ && createdOnLatestVersion == state.createdOnLatestVersion
&& Objects.equals(indexMappingVersion, state.indexMappingVersion)
&& Objects.equals(migrationsVersion, state.migrationsVersion)
&& Objects.equals(minClusterMappingVersion, state.minClusterMappingVersion)
@@ -674,6 +704,7 @@ public int hashCode() {
indexAvailableForSearch,
indexAvailableForWrite,
mappingUpToDate,
+ createdOnLatestVersion,
migrationsVersion,
minClusterMappingVersion,
indexMappingVersion,
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityMigrationExecutor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityMigrationExecutor.java
index bd5d0fb5a8ef5..0f895a2db17e0 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityMigrationExecutor.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityMigrationExecutor.java
@@ -46,10 +46,24 @@ public SecurityMigrationExecutor(
@Override
protected void nodeOperation(AllocatedPersistentTask task, SecurityMigrationTaskParams params, PersistentTaskState state) {
- applyOutstandingMigrations(task, params.getMigrationVersion(), ActionListener.wrap((res) -> task.markAsCompleted(), (exception) -> {
+ ActionListener listener = ActionListener.wrap((res) -> task.markAsCompleted(), (exception) -> {
logger.warn("Security migration failed: " + exception);
task.markAsFailed(exception);
- }));
+ });
+
+ if (params.isMigrationNeeded() == false) {
+ updateMigrationVersion(
+ params.getMigrationVersion(),
+ securityIndexManager.getConcreteIndexName(),
+ ActionListener.wrap(response -> {
+ logger.info("Security migration not needed. Setting current version to: [" + params.getMigrationVersion() + "]");
+ listener.onResponse(response);
+ }, listener::onFailure)
+ );
+ return;
+ }
+
+ applyOutstandingMigrations(task, params.getMigrationVersion(), listener);
}
private void applyOutstandingMigrations(AllocatedPersistentTask task, int currentMigrationVersion, ActionListener listener) {
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityMigrations.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityMigrations.java
index 8ef132ad0ed34..f7ca72cd89eba 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityMigrations.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityMigrations.java
@@ -28,6 +28,10 @@
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SecurityMainIndexMappingVersion.ADD_REMOTE_CLUSTER_AND_DESCRIPTION_FIELDS;
+/**
+ * Interface for creating SecurityMigrations that will be automatically applied once to existing .security indices
+ * IMPORTANT: A new index version needs to be added to {@link org.elasticsearch.index.IndexVersions} for the migration to be triggered
+ */
public class SecurityMigrations {
public interface SecurityMigration {
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java
index 330eecc1563e2..62b72b4f9750c 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java
@@ -2512,6 +2512,7 @@ private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) {
true,
true,
true,
+ true,
null,
null,
null,
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmTests.java
index 37a4cd4f783e4..2254c78a2910c 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmTests.java
@@ -39,6 +39,7 @@ private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) {
true,
true,
true,
+ true,
null,
null,
null,
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java
index c860ceeafc0f4..2a084bacfaf76 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java
@@ -411,6 +411,7 @@ private SecurityIndexManager.State indexState(boolean isUpToDate, ClusterHealthS
true,
true,
true,
+ true,
null,
null,
null,
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java
index 5b28c3dc39cfe..693bd9b868ede 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java
@@ -1617,6 +1617,7 @@ public SecurityIndexManager.State dummyIndexState(boolean isIndexUpToDate, Clust
true,
true,
true,
+ true,
null,
null,
null,
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java
index 6a2ac7721c9a1..d3b75210a5cbe 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java
@@ -792,6 +792,7 @@ private SecurityIndexManager.State dummyState(
true,
true,
true,
+ true,
null,
null,
null,
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/CacheInvalidatorRegistryTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/CacheInvalidatorRegistryTests.java
index 698809beb6d30..e3b00dfbcc6b8 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/CacheInvalidatorRegistryTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/CacheInvalidatorRegistryTests.java
@@ -61,6 +61,7 @@ public void testSecurityIndexStateChangeWillInvalidateAllRegisteredInvalidators(
true,
true,
true,
+ true,
null,
new SystemIndexDescriptor.MappingsVersion(SecurityMainIndexMappingVersion.latest().id(), 0),
null,
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityMigrationExecutorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityMigrationExecutorTests.java
index 3c3b322c28a2f..0f63e5302a5f1 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityMigrationExecutorTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityMigrationExecutorTests.java
@@ -43,6 +43,8 @@ public class SecurityMigrationExecutorTests extends ESTestCase {
private boolean clientShouldThrowException = false;
+ private AllocatedPersistentTask mockTask = mock(AllocatedPersistentTask.class);
+
@Before
public void setUpMocks() {
threadPool = mock(ThreadPool.class);
@@ -78,8 +80,8 @@ public void testSuccessfulMigration() {
client,
new TreeMap<>(Map.of(1, generateMigration(migrateInvocations, true), 2, generateMigration(migrateInvocations, true)))
);
- AllocatedPersistentTask mockTask = mock(AllocatedPersistentTask.class);
- securityMigrationExecutor.nodeOperation(mockTask, mock(SecurityMigrationTaskParams.class), mock(PersistentTaskState.class));
+
+ securityMigrationExecutor.nodeOperation(mockTask, new SecurityMigrationTaskParams(0, true), mock(PersistentTaskState.class));
verify(mockTask, times(1)).markAsCompleted();
verify(mockTask, times(0)).markAsFailed(any());
assertEquals(2, updateIndexMigrationVersionActionInvocations);
@@ -105,8 +107,7 @@ public void testNoMigrationMeetsRequirements() {
)
);
- AllocatedPersistentTask mockTask = mock(AllocatedPersistentTask.class);
- securityMigrationExecutor.nodeOperation(mockTask, mock(SecurityMigrationTaskParams.class), mock(PersistentTaskState.class));
+ securityMigrationExecutor.nodeOperation(mockTask, new SecurityMigrationTaskParams(0, true), mock(PersistentTaskState.class));
verify(mockTask, times(1)).markAsCompleted();
verify(mockTask, times(0)).markAsFailed(any());
assertEquals(0, updateIndexMigrationVersionActionInvocations);
@@ -136,8 +137,7 @@ public void testPartialMigration() {
)
);
- AllocatedPersistentTask mockTask = mock(AllocatedPersistentTask.class);
- securityMigrationExecutor.nodeOperation(mockTask, mock(SecurityMigrationTaskParams.class), mock(PersistentTaskState.class));
+ securityMigrationExecutor.nodeOperation(mockTask, new SecurityMigrationTaskParams(0, true), mock(PersistentTaskState.class));
verify(mockTask, times(1)).markAsCompleted();
verify(mockTask, times(0)).markAsFailed(any());
assertEquals(2, updateIndexMigrationVersionActionInvocations);
@@ -154,11 +154,7 @@ public void testNoMigrationNeeded() {
new TreeMap<>(Map.of(1, generateMigration(migrateInvocations, true), 2, generateMigration(migrateInvocations, true)))
);
- AllocatedPersistentTask mockTask = mock(AllocatedPersistentTask.class);
- SecurityMigrationTaskParams taskParams = mock(SecurityMigrationTaskParams.class);
- when(taskParams.getMigrationVersion()).thenReturn(7);
-
- securityMigrationExecutor.nodeOperation(mockTask, taskParams, mock(PersistentTaskState.class));
+ securityMigrationExecutor.nodeOperation(mockTask, new SecurityMigrationTaskParams(7, true), mock(PersistentTaskState.class));
verify(mockTask, times(1)).markAsCompleted();
verify(mockTask, times(0)).markAsFailed(any());
assertEquals(0, updateIndexMigrationVersionActionInvocations);
@@ -190,13 +186,11 @@ public int minMappingVersion() {
}))
);
- AllocatedPersistentTask mockTask = mock(AllocatedPersistentTask.class);
-
assertThrows(
IllegalStateException.class,
() -> securityMigrationExecutor.nodeOperation(
mockTask,
- mock(SecurityMigrationTaskParams.class),
+ new SecurityMigrationTaskParams(0, true),
mock(PersistentTaskState.class)
)
);
@@ -212,8 +206,7 @@ public void testUpdateMigrationVersionThrowsException() {
new TreeMap<>(Map.of(1, generateMigration(migrateInvocations, true), 2, generateMigration(migrateInvocations, true)))
);
clientShouldThrowException = true;
- AllocatedPersistentTask mockTask = mock(AllocatedPersistentTask.class);
- securityMigrationExecutor.nodeOperation(mockTask, mock(SecurityMigrationTaskParams.class), mock(PersistentTaskState.class));
+ securityMigrationExecutor.nodeOperation(mockTask, new SecurityMigrationTaskParams(0, true), mock(PersistentTaskState.class));
verify(mockTask, times(1)).markAsFailed(any());
verify(mockTask, times(0)).markAsCompleted();
}
From 41141fc31e4e243fba65640b44811d2cc4932c93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lorenzo=20Dematt=C3=A9?=
Date: Thu, 27 Jun 2024 14:12:29 +0200
Subject: [PATCH 009/216] AwaitsFix:
https://github.com/elastic/elasticsearch/issues/110225
---
muted-tests.yml | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/muted-tests.yml b/muted-tests.yml
index d5e603bbed2f0..ebf59855d93e1 100644
--- a/muted-tests.yml
+++ b/muted-tests.yml
@@ -10,7 +10,8 @@ tests:
method: "testGuessIsDayFirstFromLocale"
- class: "org.elasticsearch.test.rest.ClientYamlTestSuiteIT"
issue: "https://github.com/elastic/elasticsearch/issues/108857"
- method: "test {yaml=search/180_locale_dependent_mapping/Test Index and Search locale dependent mappings / dates}"
+ method: "test {yaml=search/180_locale_dependent_mapping/Test Index and Search locale\
+ \ dependent mappings / dates}"
- class: "org.elasticsearch.upgrades.SearchStatesIT"
issue: "https://github.com/elastic/elasticsearch/issues/108991"
method: "testCanMatch"
@@ -19,7 +20,8 @@ tests:
method: "testTrainedModelInference"
- class: "org.elasticsearch.xpack.security.CoreWithSecurityClientYamlTestSuiteIT"
issue: "https://github.com/elastic/elasticsearch/issues/109188"
- method: "test {yaml=search/180_locale_dependent_mapping/Test Index and Search locale dependent mappings / dates}"
+ method: "test {yaml=search/180_locale_dependent_mapping/Test Index and Search locale\
+ \ dependent mappings / dates}"
- class: "org.elasticsearch.xpack.esql.qa.mixed.EsqlClientYamlIT"
issue: "https://github.com/elastic/elasticsearch/issues/109189"
method: "test {p0=esql/70_locale/Date format with Italian locale}"
@@ -34,7 +36,8 @@ tests:
method: "testTimestampFieldTypeExposedByAllIndicesServices"
- class: "org.elasticsearch.analysis.common.CommonAnalysisClientYamlTestSuiteIT"
issue: "https://github.com/elastic/elasticsearch/issues/109318"
- method: "test {yaml=analysis-common/50_char_filters/pattern_replace error handling (too complex pattern)}"
+ method: "test {yaml=analysis-common/50_char_filters/pattern_replace error handling\
+ \ (too complex pattern)}"
- class: "org.elasticsearch.xpack.ml.integration.ClassificationHousePricingIT"
issue: "https://github.com/elastic/elasticsearch/issues/101598"
method: "testFeatureImportanceValues"
@@ -80,6 +83,8 @@ tests:
- class: org.elasticsearch.synonyms.SynonymsManagementAPIServiceIT
method: testUpdateRuleWithMaxSynonyms
issue: https://github.com/elastic/elasticsearch/issues/110212
+- class: "org.elasticsearch.rest.RestControllerIT"
+ issue: "https://github.com/elastic/elasticsearch/issues/110225"
# Examples:
#
From ca2ea690a60d48af83ee56e61e594e4ddd354d21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Francisco=20Fern=C3=A1ndez=20Casta=C3=B1o?=
Date: Thu, 27 Jun 2024 14:26:17 +0200
Subject: [PATCH 010/216] Update checkpoints after post-replication actions,
even on failure (#109908)
A failed post write refresh should not prevent advancing the local checkpoint if the translog operations have been fsynced correctly, hence we should update the checkpoints in all situations. On the other hand, if the fsync failed the local checkpoint won't advance anyway and the engine will fail during the next indexing operation.
Closes #108190
---
docs/changelog/109908.yaml | 5 +
.../bulk/BulkAfterWriteFsyncFailureIT.java | 119 ++++++++++++++++++
.../replication/ReplicationOperation.java | 5 +-
3 files changed, 128 insertions(+), 1 deletion(-)
create mode 100644 docs/changelog/109908.yaml
create mode 100644 server/src/internalClusterTest/java/org/elasticsearch/action/bulk/BulkAfterWriteFsyncFailureIT.java
diff --git a/docs/changelog/109908.yaml b/docs/changelog/109908.yaml
new file mode 100644
index 0000000000000..cdf2acf17096c
--- /dev/null
+++ b/docs/changelog/109908.yaml
@@ -0,0 +1,5 @@
+pr: 109908
+summary: "Update checkpoints after post-replication actions, even on failure"
+area: CRUD
+type: bug
+issues: []
diff --git a/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/BulkAfterWriteFsyncFailureIT.java b/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/BulkAfterWriteFsyncFailureIT.java
new file mode 100644
index 0000000000000..5adc0b090ed37
--- /dev/null
+++ b/server/src/internalClusterTest/java/org/elasticsearch/action/bulk/BulkAfterWriteFsyncFailureIT.java
@@ -0,0 +1,119 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.action.bulk;
+
+import org.apache.lucene.tests.mockfile.FilterFileChannel;
+import org.apache.lucene.tests.mockfile.FilterFileSystemProvider;
+import org.elasticsearch.cluster.metadata.IndexMetadata;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.core.PathUtils;
+import org.elasticsearch.core.PathUtilsForTesting;
+import org.elasticsearch.indices.IndicesService;
+import org.elasticsearch.test.ESSingleNodeTestCase;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.file.FileSystem;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileAttribute;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.elasticsearch.index.IndexSettings.INDEX_REFRESH_INTERVAL_SETTING;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+
+public class BulkAfterWriteFsyncFailureIT extends ESSingleNodeTestCase {
+ private static FSyncFailureFileSystemProvider fsyncFailureFileSystemProvider;
+
+ @BeforeClass
+ public static void installDisruptFSyncFS() {
+ FileSystem current = PathUtils.getDefaultFileSystem();
+ fsyncFailureFileSystemProvider = new FSyncFailureFileSystemProvider(current);
+ PathUtilsForTesting.installMock(fsyncFailureFileSystemProvider.getFileSystem(null));
+ }
+
+ @AfterClass
+ public static void removeDisruptFSyncFS() {
+ PathUtilsForTesting.teardown();
+ }
+
+ public void testFsyncFailureDoesNotAdvanceLocalCheckpoints() {
+ String indexName = randomIdentifier();
+ client().admin()
+ .indices()
+ .prepareCreate(indexName)
+ .setSettings(
+ Settings.builder()
+ .put(INDEX_REFRESH_INTERVAL_SETTING.getKey(), -1)
+ .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
+ .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
+ .build()
+ )
+ .setMapping("key", "type=keyword", "val", "type=long")
+ .get();
+ ensureGreen(indexName);
+
+ var localCheckpointBeforeBulk = getLocalCheckpointForShard(indexName, 0);
+ fsyncFailureFileSystemProvider.failFSyncOnce(true);
+ var bulkResponse = client().prepareBulk().add(prepareIndex(indexName).setId("1").setSource("key", "foo", "val", 10)).get();
+ assertTrue(bulkResponse.hasFailures());
+ var localCheckpointAfterFailedBulk = getLocalCheckpointForShard(indexName, 0);
+ // fsync for the translog failed, hence the checkpoint doesn't advance
+ assertThat(localCheckpointBeforeBulk, equalTo(localCheckpointAfterFailedBulk));
+
+ // Since background refreshes are disabled, the shard is considered green until the next operation is appended into the translog
+ ensureGreen(indexName);
+
+ // If the after write fsync fails, it'll fail the TranslogWriter but not the Engine, we'll need to try to append a new operation
+ // into the translog so the exception bubbles up and fails the engine. On the other hand, the TranslogReplicationAction will retry
+ // this action on AlreadyClosedExceptions, that's why the operation ends up succeeding even after the engine failed.
+ var bulkResponse2 = client().prepareBulk().add(prepareIndex(indexName).setId("2").setSource("key", "bar", "val", 20)).get();
+ assertFalse(bulkResponse2.hasFailures());
+
+ var localCheckpointAfterSuccessfulBulk = getLocalCheckpointForShard(indexName, 0);
+ assertThat(localCheckpointAfterSuccessfulBulk, is(greaterThan(localCheckpointAfterFailedBulk)));
+ }
+
+ long getLocalCheckpointForShard(String index, int shardId) {
+ var indicesService = getInstanceFromNode(IndicesService.class);
+ var indexShard = indicesService.indexServiceSafe(resolveIndex(index)).getShard(shardId);
+ return indexShard.getLocalCheckpoint();
+ }
+
+ public static class FSyncFailureFileSystemProvider extends FilterFileSystemProvider {
+ private final AtomicBoolean failFSyncs = new AtomicBoolean();
+
+ public FSyncFailureFileSystemProvider(FileSystem delegate) {
+ super("fsyncfailure://", delegate);
+ }
+
+ public void failFSyncOnce(boolean shouldFail) {
+ failFSyncs.set(shouldFail);
+ }
+
+ @Override
+ public FileChannel newFileChannel(Path path, Set extends OpenOption> options, FileAttribute>... attrs) throws IOException {
+ return new FilterFileChannel(super.newFileChannel(path, options, attrs)) {
+
+ @Override
+ public void force(boolean metaData) throws IOException {
+ if (failFSyncs.compareAndSet(true, false)) {
+ throw new IOException("simulated");
+ }
+ super.force(metaData);
+ }
+ };
+ }
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/action/support/replication/ReplicationOperation.java b/server/src/main/java/org/elasticsearch/action/support/replication/ReplicationOperation.java
index 04ba462523f5f..b38a067e8b316 100644
--- a/server/src/main/java/org/elasticsearch/action/support/replication/ReplicationOperation.java
+++ b/server/src/main/java/org/elasticsearch/action/support/replication/ReplicationOperation.java
@@ -189,7 +189,10 @@ public void onFailure(Exception e) {
logger.trace("[{}] op [{}] post replication actions failed for [{}]", primary.routingEntry().shardId(), opType, request);
// TODO: fail shard? This will otherwise have the local / global checkpoint info lagging, or possibly have replicas
// go out of sync with the primary
- finishAsFailed(e);
+ // We update the checkpoints since a refresh might fail but the operations could be safely persisted, in the case that the
+ // fsync failed the local checkpoint won't advance and the engine will be marked as failed when the next indexing operation
+ // is appended into the translog.
+ updateCheckPoints(primary.routingEntry(), primary::localCheckpoint, primary::globalCheckpoint, () -> finishAsFailed(e));
}
});
}
From 280fd2c68e811178893b7c5765fdfe92efad15c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20Fred=C3=A9n?=
<109296772+jfreden@users.noreply.github.com>
Date: Thu, 27 Jun 2024 14:48:52 +0200
Subject: [PATCH 011/216] AwaitsFix:
https://github.com/elastic/elasticsearch/issues/110227
---
muted-tests.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/muted-tests.yml b/muted-tests.yml
index ebf59855d93e1..6b28a297b546c 100644
--- a/muted-tests.yml
+++ b/muted-tests.yml
@@ -85,6 +85,9 @@ tests:
issue: https://github.com/elastic/elasticsearch/issues/110212
- class: "org.elasticsearch.rest.RestControllerIT"
issue: "https://github.com/elastic/elasticsearch/issues/110225"
+- class: "org.elasticsearch.xpack.security.authz.store.NativePrivilegeStoreCacheTests"
+ issue: "https://github.com/elastic/elasticsearch/issues/110227"
+ method: "testGetPrivilegesUsesCache"
# Examples:
#
From 446d04e75cd52d7a5c974e695ebfc2e2ee3e7611 Mon Sep 17 00:00:00 2001
From: Tim Grein
Date: Thu, 27 Jun 2024 15:01:30 +0200
Subject: [PATCH 012/216] [Inference API] Make error messages consistent in
InferenceAction (#110220)
---
.../xpack/core/inference/action/InferenceAction.java | 7 +++++--
.../core/inference/action/InferenceActionRequestTests.java | 4 ++--
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/inference/action/InferenceAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/inference/action/InferenceAction.java
index 16d0b940d40e6..229285510249c 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/inference/action/InferenceAction.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/inference/action/InferenceAction.java
@@ -167,14 +167,16 @@ public TimeValue getInferenceTimeout() {
public ActionRequestValidationException validate() {
if (input == null) {
var e = new ActionRequestValidationException();
- e.addValidationError("missing input");
+ e.addValidationError("Field [input] cannot be null");
return e;
}
+
if (input.isEmpty()) {
var e = new ActionRequestValidationException();
- e.addValidationError("input array is empty");
+ e.addValidationError("Field [input] cannot be an empty array");
return e;
}
+
if (taskType.equals(TaskType.RERANK)) {
if (query == null) {
var e = new ActionRequestValidationException();
@@ -187,6 +189,7 @@ public ActionRequestValidationException validate() {
return e;
}
}
+
return null;
}
diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/inference/action/InferenceActionRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/inference/action/InferenceActionRequestTests.java
index fa7044ffd8c8b..d4d4146c6a5ba 100644
--- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/inference/action/InferenceActionRequestTests.java
+++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/inference/action/InferenceActionRequestTests.java
@@ -112,7 +112,7 @@ public void testValidation_TextEmbedding_Null() {
);
ActionRequestValidationException inputNullError = inputNullRequest.validate();
assertNotNull(inputNullError);
- assertThat(inputNullError.getMessage(), is("Validation Failed: 1: missing input;"));
+ assertThat(inputNullError.getMessage(), is("Validation Failed: 1: Field [input] cannot be null;"));
}
public void testValidation_TextEmbedding_Empty() {
@@ -127,7 +127,7 @@ public void testValidation_TextEmbedding_Empty() {
);
ActionRequestValidationException inputEmptyError = inputEmptyRequest.validate();
assertNotNull(inputEmptyError);
- assertThat(inputEmptyError.getMessage(), is("Validation Failed: 1: input array is empty;"));
+ assertThat(inputEmptyError.getMessage(), is("Validation Failed: 1: Field [input] cannot be an empty array;"));
}
public void testValidation_Rerank_Null() {
From fa23fbb4146834ae23b740d639d9d75ecd28b539 Mon Sep 17 00:00:00 2001
From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com>
Date: Thu, 27 Jun 2024 09:08:37 -0400
Subject: [PATCH 013/216] Fixing bug with allocations (#110204)
Co-authored-by: Elastic Machine
---
...RestStartTrainedModelDeploymentAction.java | 85 +-----
...tartTrainedModelDeploymentActionTests.java | 277 ++++++++++++++----
2 files changed, 236 insertions(+), 126 deletions(-)
diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/inference/RestStartTrainedModelDeploymentAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/inference/RestStartTrainedModelDeploymentAction.java
index 40cf7d531d5ee..1a9fc6ce99823 100644
--- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/inference/RestStartTrainedModelDeploymentAction.java
+++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/inference/RestStartTrainedModelDeploymentAction.java
@@ -7,15 +7,11 @@
package org.elasticsearch.xpack.ml.rest.inference;
-import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.client.internal.node.NodeClient;
import org.elasticsearch.common.unit.ByteSizeValue;
-import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.RestApiVersion;
-import org.elasticsearch.core.TimeValue;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.Scope;
import org.elasticsearch.rest.ServerlessScope;
import org.elasticsearch.rest.action.RestToXContentListener;
@@ -27,7 +23,6 @@
import java.io.IOException;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import static org.elasticsearch.rest.RestRequest.Method.POST;
import static org.elasticsearch.xpack.core.ml.action.StartTrainedModelDeploymentAction.Request.CACHE_SIZE;
@@ -87,22 +82,11 @@ protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient
}
if (restRequest.hasParam(TIMEOUT.getPreferredName())) {
- TimeValue openTimeout = validateParameters(
- request.getTimeout(),
- restRequest.paramAsTime(TIMEOUT.getPreferredName(), StartTrainedModelDeploymentAction.DEFAULT_TIMEOUT),
- StartTrainedModelDeploymentAction.DEFAULT_TIMEOUT
- ); // hasParam, so never default
- request.setTimeout(openTimeout);
+ request.setTimeout(restRequest.paramAsTime(TIMEOUT.getPreferredName(), request.getTimeout()));
}
request.setWaitForState(
- validateParameters(
- request.getWaitForState(),
- AllocationStatus.State.fromString(
- restRequest.param(WAIT_FOR.getPreferredName(), StartTrainedModelDeploymentAction.DEFAULT_WAITFOR_STATE.toString())
- ),
- StartTrainedModelDeploymentAction.DEFAULT_WAITFOR_STATE
- )
+ AllocationStatus.State.fromString(restRequest.param(WAIT_FOR.getPreferredName(), request.getWaitForState().toString()))
);
RestCompatibilityChecker.checkAndSetDeprecatedParam(
@@ -110,84 +94,33 @@ protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient
NUMBER_OF_ALLOCATIONS.getPreferredName(),
RestApiVersion.V_8,
restRequest,
- (r, s) -> validateParameters(
- request.getNumberOfAllocations(),
- r.paramAsInt(s, StartTrainedModelDeploymentAction.DEFAULT_NUM_ALLOCATIONS),
- StartTrainedModelDeploymentAction.DEFAULT_NUM_ALLOCATIONS
- ),
+ (r, s) -> r.paramAsInt(s, request.getNumberOfAllocations()),
request::setNumberOfAllocations
);
+
RestCompatibilityChecker.checkAndSetDeprecatedParam(
THREADS_PER_ALLOCATION.getDeprecatedNames()[0],
THREADS_PER_ALLOCATION.getPreferredName(),
RestApiVersion.V_8,
restRequest,
- (r, s) -> validateParameters(
- request.getThreadsPerAllocation(),
- r.paramAsInt(s, StartTrainedModelDeploymentAction.DEFAULT_NUM_THREADS),
- StartTrainedModelDeploymentAction.DEFAULT_NUM_THREADS
- ),
+ (r, s) -> r.paramAsInt(s, request.getThreadsPerAllocation()),
request::setThreadsPerAllocation
);
- request.setQueueCapacity(
- validateParameters(
- request.getQueueCapacity(),
- restRequest.paramAsInt(QUEUE_CAPACITY.getPreferredName(), StartTrainedModelDeploymentAction.DEFAULT_QUEUE_CAPACITY),
- StartTrainedModelDeploymentAction.DEFAULT_QUEUE_CAPACITY
- )
- );
+
+ request.setQueueCapacity(restRequest.paramAsInt(QUEUE_CAPACITY.getPreferredName(), request.getQueueCapacity()));
if (restRequest.hasParam(CACHE_SIZE.getPreferredName())) {
request.setCacheSize(
- validateParameters(
- request.getCacheSize(),
- ByteSizeValue.parseBytesSizeValue(restRequest.param(CACHE_SIZE.getPreferredName()), CACHE_SIZE.getPreferredName()),
- null
- )
+ ByteSizeValue.parseBytesSizeValue(restRequest.param(CACHE_SIZE.getPreferredName()), CACHE_SIZE.getPreferredName())
);
} else if (defaultCacheSize != null && request.getCacheSize() == null) {
request.setCacheSize(defaultCacheSize);
}
request.setPriority(
- validateParameters(
- request.getPriority().toString(),
- restRequest.param(StartTrainedModelDeploymentAction.TaskParams.PRIORITY.getPreferredName()),
- StartTrainedModelDeploymentAction.DEFAULT_PRIORITY.toString()
- )
+ restRequest.param(StartTrainedModelDeploymentAction.TaskParams.PRIORITY.getPreferredName(), request.getPriority().toString())
);
return channel -> client.execute(StartTrainedModelDeploymentAction.INSTANCE, request, new RestToXContentListener<>(channel));
}
-
- /**
- * This function validates that the body and query parameters don't conflict, and returns the value that should be used.
- * When using this function, the body parameter should already have been set to the default value in
- * {@link StartTrainedModelDeploymentAction}, or, set to a different value from the rest request.
- *
- * @param paramDefault (from {@link StartTrainedModelDeploymentAction})
- * @return the parameter to use
- * @throws ElasticsearchStatusException if the parameters don't match
- */
- private static T validateParameters(@Nullable T bodyParam, @Nullable T queryParam, @Nullable T paramDefault)
- throws ElasticsearchStatusException {
- if (Objects.equals(bodyParam, paramDefault) && queryParam != null) {
- // the body param is the same as the default for this value. We cannot tell if this was set intentionally, or if it was just the
- // default, thus we will assume it was the default
- return queryParam;
- }
-
- if (Objects.equals(bodyParam, queryParam)) {
- return bodyParam;
- } else if (bodyParam == null) {
- return queryParam;
- } else if (queryParam == null) {
- return bodyParam;
- } else {
- throw new ElasticsearchStatusException(
- "The parameter " + bodyParam + " in the body is different from the parameter " + queryParam + " in the query",
- RestStatus.BAD_REQUEST
- );
- }
- }
}
diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/rest/inference/RestStartTrainedModelDeploymentActionTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/rest/inference/RestStartTrainedModelDeploymentActionTests.java
index 7c1f499640e64..b6c450c84d596 100644
--- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/rest/inference/RestStartTrainedModelDeploymentActionTests.java
+++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/rest/inference/RestStartTrainedModelDeploymentActionTests.java
@@ -7,12 +7,18 @@
package org.elasticsearch.xpack.ml.rest.inference;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.core.Nullable;
+import org.elasticsearch.core.TimeValue;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.test.rest.RestActionTestCase;
+import org.elasticsearch.xcontent.NamedXContentRegistry;
+import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory;
import org.elasticsearch.xcontent.XContentType;
import org.elasticsearch.xpack.core.ml.action.CreateTrainedModelAssignmentAction;
@@ -21,81 +27,252 @@
import java.io.IOException;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import static java.util.stream.Collectors.toList;
+import static org.elasticsearch.xpack.core.ml.inference.assignment.AllocationStatus.State.FULLY_ALLOCATED;
+import static org.elasticsearch.xpack.core.ml.inference.assignment.AllocationStatus.State.STARTING;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
public class RestStartTrainedModelDeploymentActionTests extends RestActionTestCase {
+ private final TestCase testCase;
- public void testCacheDisabled() {
- final boolean disableInferenceProcessCache = true;
- controller().registerHandler(new RestStartTrainedModelDeploymentAction(disableInferenceProcessCache));
- SetOnce executeCalled = new SetOnce<>();
- verifyingClient.setExecuteVerifier(((actionType, actionRequest) -> {
- assertThat(actionRequest, instanceOf(StartTrainedModelDeploymentAction.Request.class));
-
- var request = (StartTrainedModelDeploymentAction.Request) actionRequest;
- assertThat(request.getCacheSize(), is(ByteSizeValue.ZERO));
+ public RestStartTrainedModelDeploymentActionTests(TestCase testCase) {
+ this.testCase = testCase;
+ }
- executeCalled.set(true);
- return createResponse();
- }));
+ @ParametersFactory(shuffle = false)
+ public static Iterable parameters() throws Exception {
+ List