Skip to content

Commit 3fcb48d

Browse files
Merge branch 'main' into esql/project_routing
2 parents b879a93 + 3bcf194 commit 3fcb48d

File tree

45 files changed

+779
-344
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+779
-344
lines changed

docs/changelog/133675.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 133675
2+
summary: Support using the semantic query across multiple inference IDs
3+
area: Vector Search
4+
type: enhancement
5+
issues: []

server/src/main/java/org/elasticsearch/TransportVersions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ static TransportVersion def(int id) {
356356
public static final TransportVersion PROJECT_RESERVED_STATE_MOVE_TO_REGISTRY = def(9_147_0_00);
357357
public static final TransportVersion STREAMS_ENDPOINT_PARAM_RESTRICTIONS = def(9_148_0_00);
358358
public static final TransportVersion RESOLVE_INDEX_MODE_FILTER = def(9_149_0_00);
359+
public static final TransportVersion SEMANTIC_QUERY_MULTIPLE_INFERENCE_IDS = def(9_150_0_00);
359360

360361
/*
361362
* STOP! READ THIS FIRST! No, really,

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import org.elasticsearch.watcher.ResourceWatcherService;
1717
import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler;
1818
import org.elasticsearch.xpack.core.security.authc.Realm;
19-
import org.elasticsearch.xpack.core.security.authc.apikey.CustomApiKeyAuthenticator;
19+
import org.elasticsearch.xpack.core.security.authc.apikey.CustomAuthenticator;
2020
import org.elasticsearch.xpack.core.security.authc.service.NodeLocalServiceAccountTokenStore;
2121
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore;
2222
import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper;
@@ -129,7 +129,7 @@ default ServiceAccountTokenStore getServiceAccountTokenStore(SecurityComponents
129129
return null;
130130
}
131131

132-
default CustomApiKeyAuthenticator getCustomApiKeyAuthenticator(SecurityComponents components) {
132+
default List<CustomAuthenticator> getCustomAuthenticators(SecurityComponents components) {
133133
return null;
134134
}
135135

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,18 @@ public static Authentication newRealmAuthentication(User user, RealmRef realmRef
13761376
return authentication;
13771377
}
13781378

1379+
public static Authentication newCloudAccessTokenAuthentication(
1380+
AuthenticationResult<User> authResult,
1381+
Authentication.RealmRef realmRef
1382+
) {
1383+
assert authResult.isAuthenticated() : "cloud access token authn result must be successful";
1384+
final User user = authResult.getValue();
1385+
return new Authentication(
1386+
new Subject(user, realmRef, TransportVersion.current(), authResult.getMetadata()),
1387+
AuthenticationType.TOKEN
1388+
);
1389+
}
1390+
13791391
public static Authentication newCloudApiKeyAuthentication(AuthenticationResult<User> authResult, String nodeName) {
13801392
assert authResult.isAuthenticated() : "cloud API Key authn result must be successful";
13811393
final User apiKeyUser = authResult.getValue();

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/apikey/CustomApiKeyAuthenticator.java

Lines changed: 0 additions & 51 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core.security.authc.apikey;
9+
10+
import org.elasticsearch.action.ActionListener;
11+
import org.elasticsearch.common.util.concurrent.ThreadContext;
12+
import org.elasticsearch.core.Nullable;
13+
import org.elasticsearch.xpack.core.security.authc.Authentication;
14+
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
15+
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
16+
17+
/**
18+
* An extension point to provide a custom authenticator implementation. For example, a custom API key or a custom OAuth2
19+
* token implementation. The implementation is wrapped by a core `Authenticator` class and included in the authenticator chain
20+
* _before_ the respective "standard" authenticator(s).
21+
*/
22+
public interface CustomAuthenticator {
23+
24+
boolean supports(AuthenticationToken token);
25+
26+
@Nullable
27+
AuthenticationToken extractToken(ThreadContext context);
28+
29+
void authenticate(@Nullable AuthenticationToken token, ActionListener<AuthenticationResult<Authentication>> listener);
30+
31+
}

x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/SemanticMatchTestCase.java

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,28 @@ public abstract class SemanticMatchTestCase extends ESRestTestCase {
3434
public void testWithMultipleInferenceIds() throws IOException {
3535
assumeTrue("semantic text capability not available", EsqlCapabilities.Cap.SEMANTIC_TEXT_FIELD_CAPS.isEnabled());
3636

37+
var request1 = new Request("POST", "/test-semantic1/_doc/id-1");
38+
request1.addParameter("refresh", "true");
39+
request1.setJsonEntity("{\"semantic_text_field\": \"inference test 1\"}");
40+
assertEquals(201, adminClient().performRequest(request1).getStatusLine().getStatusCode());
41+
42+
var request2 = new Request("POST", "/test-semantic2/_doc/id-2");
43+
request2.addParameter("refresh", "true");
44+
request2.setJsonEntity("{\"semantic_text_field\": \"inference test 2\"}");
45+
assertEquals(201, adminClient().performRequest(request2).getStatusLine().getStatusCode());
46+
3747
String query = """
3848
from test-semantic1,test-semantic2
3949
| where match(semantic_text_field, "something")
50+
| SORT semantic_text_field ASC
4051
""";
41-
ResponseException re = expectThrows(ResponseException.class, () -> runEsqlQuery(query));
42-
43-
assertThat(re.getMessage(), containsString("Field [semantic_text_field] has multiple inference IDs associated with it"));
52+
Map<String, Object> result = runEsqlQuery(query);
4453

45-
assertEquals(400, re.getResponse().getStatusLine().getStatusCode());
54+
assertResultMap(
55+
result,
56+
matchesList().item(matchesMap().entry("name", "semantic_text_field").entry("type", "text")),
57+
matchesList(List.of(List.of("inference test 1"), List.of("inference test 2")))
58+
);
4659
}
4760

4861
public void testWithInferenceNotConfigured() {
@@ -128,6 +141,28 @@ public void setUpIndices() throws IOException {
128141
createIndex(adminClient(), "test-semantic4", settings, mapping4);
129142
}
130143

144+
@Before
145+
public void setUpSparseEmbeddingInferenceEndpoint() throws IOException {
146+
Request request = new Request("PUT", "_inference/sparse_embedding/test_sparse_inference");
147+
request.setJsonEntity("""
148+
{
149+
"service": "test_service",
150+
"service_settings": {
151+
"model": "my_model",
152+
"api_key": "abc64"
153+
},
154+
"task_settings": {
155+
}
156+
}
157+
""");
158+
try {
159+
adminClient().performRequest(request);
160+
} catch (ResponseException exc) {
161+
// in case the removal failed
162+
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(400));
163+
}
164+
}
165+
131166
@Before
132167
public void setUpTextEmbeddingInferenceEndpoint() throws IOException {
133168
Request request = new Request("PUT", "_inference/text_embedding/test_dense_inference");
@@ -155,6 +190,15 @@ public void setUpTextEmbeddingInferenceEndpoint() throws IOException {
155190
public void wipeData() throws IOException {
156191
adminClient().performRequest(new Request("DELETE", "*"));
157192

193+
try {
194+
adminClient().performRequest(new Request("DELETE", "_inference/test_sparse_inference"));
195+
} catch (ResponseException e) {
196+
// 404 here means the endpoint was not created
197+
if (e.getResponse().getStatusLine().getStatusCode() != 404) {
198+
throw e;
199+
}
200+
}
201+
158202
try {
159203
adminClient().performRequest(new Request("DELETE", "_inference/test_dense_inference"));
160204
} catch (ResponseException e) {

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/First.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ public First withFilter(Expression filter) {
9696
return new First(source(), field(), filter, sort);
9797
}
9898

99+
public Expression sort() {
100+
return sort;
101+
}
102+
99103
@Override
100104
public DataType dataType() {
101105
return field().dataType().noText();

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Last.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ public Last withFilter(Expression filter) {
9696
return new Last(source(), field(), filter, sort);
9797
}
9898

99+
public Expression sort() {
100+
return sort;
101+
}
102+
99103
@Override
100104
public DataType dataType() {
101105
return field().dataType().noText();

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/AbstractAggregationTestCase.java

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -506,41 +506,46 @@ private void processPageGrouping(GroupingAggregator aggregator, Page inputPage,
506506
}
507507

508508
private void assertAggregatorToString(Object aggregator) {
509-
if (optIntoToAggregatorToStringChecks() == false) {
510-
return;
511-
}
512509
String expectedStart = switch (aggregator) {
513510
case Aggregator a -> "Aggregator[aggregatorFunction=";
514511
case GroupingAggregator a -> "GroupingAggregator[aggregatorFunction=";
515512
default -> throw new UnsupportedOperationException("can't check toString for [" + aggregator.getClass() + "]");
516513
};
514+
String channels = initialInputChannels().stream().map(Object::toString).collect(Collectors.joining(", "));
517515
String expectedEnd = switch (aggregator) {
518-
case Aggregator a -> "AggregatorFunction[channels=[0]], mode=SINGLE]";
519-
case GroupingAggregator a -> "GroupingAggregatorFunction[channels=[0]], mode=SINGLE]";
516+
case Aggregator a -> "AggregatorFunction[channels=[" + channels + "]], mode=SINGLE]";
517+
case GroupingAggregator a -> "GroupingAggregatorFunction[channels=[" + channels + "]], mode=SINGLE]";
520518
default -> throw new UnsupportedOperationException("can't check toString for [" + aggregator.getClass() + "]");
521519
};
522520

523521
String toString = aggregator.toString();
524522
assertThat(toString, startsWith(expectedStart));
525-
assertThat(toString.substring(expectedStart.length(), toString.length() - expectedEnd.length()), testCase.evaluatorToString());
526523
assertThat(toString, endsWith(expectedEnd));
527-
}
528-
529-
protected boolean optIntoToAggregatorToStringChecks() {
530-
// TODO remove this when everyone has opted in
531-
return false;
524+
assertThat(toString.substring(expectedStart.length(), toString.length() - expectedEnd.length()), testCase.evaluatorToString());
532525
}
533526

534527
protected static String standardAggregatorName(String prefix, DataType type) {
535528
String typeName = switch (type) {
536529
case BOOLEAN -> "Boolean";
530+
case CARTESIAN_POINT -> "CartesianPoint";
531+
case CARTESIAN_SHAPE -> "CartesianShape";
532+
case GEO_POINT -> "GeoPoint";
533+
case GEO_SHAPE -> "GeoShape";
537534
case KEYWORD, TEXT, VERSION -> "BytesRef";
538-
case DOUBLE -> "Double";
539-
case INTEGER -> "Int";
535+
case DOUBLE, COUNTER_DOUBLE -> "Double";
536+
case INTEGER, COUNTER_INTEGER -> "Int";
540537
case IP -> "Ip";
541-
case DATETIME, DATE_NANOS, LONG, UNSIGNED_LONG -> "Long";
538+
case DATETIME, DATE_NANOS, LONG, COUNTER_LONG, UNSIGNED_LONG -> "Long";
539+
case NULL -> "Null";
542540
default -> throw new UnsupportedOperationException("name for [" + type + "]");
543541
};
544542
return prefix + typeName;
545543
}
544+
545+
protected static String standardAggregatorNameAllBytesTheSame(String prefix, DataType type) {
546+
return standardAggregatorName(prefix, switch (type) {
547+
case CARTESIAN_POINT, CARTESIAN_SHAPE, GEO_POINT, GEO_SHAPE, IP -> DataType.KEYWORD;
548+
default -> type;
549+
});
550+
}
546551
}

0 commit comments

Comments
 (0)