Skip to content

Commit 738ee54

Browse files
authored
Support for SQL.
Original Pull Request: #2949 Closes: #2683
1 parent 03992ba commit 738ee54

15 files changed

+1180
-21
lines changed

Diff for: src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java

+19
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
2424
import co.elastic.clients.elasticsearch.core.msearch.MultiSearchResponseItem;
2525
import co.elastic.clients.elasticsearch.core.search.ResponseBody;
26+
import co.elastic.clients.elasticsearch.sql.ElasticsearchSqlClient;
27+
import co.elastic.clients.elasticsearch.sql.QueryResponse;
2628
import co.elastic.clients.json.JsonpMapper;
2729
import co.elastic.clients.transport.Version;
2830

@@ -56,6 +58,7 @@
5658
import org.springframework.data.elasticsearch.core.reindex.ReindexRequest;
5759
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
5860
import org.springframework.data.elasticsearch.core.script.Script;
61+
import org.springframework.data.elasticsearch.core.sql.SqlResponse;
5962
import org.springframework.lang.Nullable;
6063
import org.springframework.util.Assert;
6164

@@ -74,6 +77,7 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
7477
private static final Log LOGGER = LogFactory.getLog(ElasticsearchTemplate.class);
7578

7679
private final ElasticsearchClient client;
80+
private final ElasticsearchSqlClient sqlClient;
7781
private final RequestConverter requestConverter;
7882
private final ResponseConverter responseConverter;
7983
private final JsonpMapper jsonpMapper;
@@ -85,6 +89,7 @@ public ElasticsearchTemplate(ElasticsearchClient client) {
8589
Assert.notNull(client, "client must not be null");
8690

8791
this.client = client;
92+
this.sqlClient = client.sql();
8893
this.jsonpMapper = client._transport().jsonpMapper();
8994
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
9095
responseConverter = new ResponseConverter(jsonpMapper);
@@ -97,6 +102,7 @@ public ElasticsearchTemplate(ElasticsearchClient client, ElasticsearchConverter
97102
Assert.notNull(client, "client must not be null");
98103

99104
this.client = client;
105+
this.sqlClient = client.sql();
100106
this.jsonpMapper = client._transport().jsonpMapper();
101107
requestConverter = new RequestConverter(elasticsearchConverter, jsonpMapper);
102108
responseConverter = new ResponseConverter(jsonpMapper);
@@ -656,6 +662,19 @@ public boolean deleteScript(String name) {
656662
DeleteScriptRequest request = requestConverter.scriptDelete(name);
657663
return execute(client -> client.deleteScript(request)).acknowledged();
658664
}
665+
666+
@Override
667+
public SqlResponse search(SqlQuery query) {
668+
Assert.notNull(query, "Query must not be null.");
669+
670+
try {
671+
QueryResponse response = sqlClient.query(requestConverter.sqlQueryRequest(query));
672+
673+
return responseConverter.sqlResponse(response);
674+
} catch (IOException e) {
675+
throw exceptionTranslator.translateException(e);
676+
}
677+
}
659678
// endregion
660679

661680
// region client callback

Diff for: src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchClient.java

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public ReactiveElasticsearchIndicesClient indices() {
6969
return new ReactiveElasticsearchIndicesClient(transport, transportOptions);
7070
}
7171

72+
public ReactiveElasticsearchSqlClient sql() {
73+
return new ReactiveElasticsearchSqlClient(transport, transportOptions);
74+
}
75+
7276
// endregion
7377
// region info
7478

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.client.elc;
17+
18+
import java.io.IOException;
19+
import java.util.function.Function;
20+
21+
import org.jetbrains.annotations.Nullable;
22+
23+
import co.elastic.clients.ApiClient;
24+
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
25+
import co.elastic.clients.elasticsearch.sql.QueryRequest;
26+
import co.elastic.clients.elasticsearch.sql.QueryResponse;
27+
import co.elastic.clients.transport.ElasticsearchTransport;
28+
import co.elastic.clients.transport.TransportOptions;
29+
import co.elastic.clients.util.ObjectBuilder;
30+
import reactor.core.publisher.Mono;
31+
32+
/**
33+
* Reactive version of {@link co.elastic.clients.elasticsearch.sql.ElasticsearchSqlClient}.
34+
*
35+
* @author Aouichaoui Youssef
36+
* @since 5.4
37+
*/
38+
public class ReactiveElasticsearchSqlClient extends ApiClient<ElasticsearchTransport, ReactiveElasticsearchSqlClient> {
39+
public ReactiveElasticsearchSqlClient(ElasticsearchTransport transport, @Nullable TransportOptions transportOptions) {
40+
super(transport, transportOptions);
41+
}
42+
43+
@Override
44+
public ReactiveElasticsearchSqlClient withTransportOptions(@Nullable TransportOptions transportOptions) {
45+
return new ReactiveElasticsearchSqlClient(transport, transportOptions);
46+
}
47+
48+
/**
49+
* Executes a SQL request
50+
*
51+
* @param fn a function that initializes a builder to create the {@link QueryRequest}.
52+
*/
53+
public final Mono<QueryResponse> query(Function<QueryRequest.Builder, ObjectBuilder<QueryRequest>> fn)
54+
throws IOException, ElasticsearchException {
55+
return query(fn.apply(new QueryRequest.Builder()).build());
56+
}
57+
58+
/**
59+
* Executes a SQL request.
60+
*/
61+
public Mono<QueryResponse> query(QueryRequest query) {
62+
return Mono.fromFuture(transport.performRequestAsync(query, QueryRequest._ENDPOINT, transportOptions));
63+
}
64+
65+
/**
66+
* Executes a SQL request.
67+
*/
68+
public Mono<QueryResponse> query() {
69+
return Mono.fromFuture(
70+
transport.performRequestAsync(new QueryRequest.Builder().build(), QueryRequest._ENDPOINT, transportOptions));
71+
}
72+
}

Diff for: src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java

+12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import co.elastic.clients.json.JsonpMapper;
2626
import co.elastic.clients.transport.Version;
2727
import co.elastic.clients.transport.endpoints.BooleanResponse;
28+
import org.springframework.data.elasticsearch.core.query.SqlQuery;
29+
import org.springframework.data.elasticsearch.core.sql.SqlResponse;
2830
import reactor.core.publisher.Flux;
2931
import reactor.core.publisher.Mono;
3032
import reactor.util.function.Tuple2;
@@ -88,6 +90,7 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
8890
private static final Log LOGGER = LogFactory.getLog(ReactiveElasticsearchTemplate.class);
8991

9092
private final ReactiveElasticsearchClient client;
93+
private final ReactiveElasticsearchSqlClient sqlClient;
9194
private final RequestConverter requestConverter;
9295
private final ResponseConverter responseConverter;
9396
private final JsonpMapper jsonpMapper;
@@ -99,6 +102,7 @@ public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, Elastic
99102
Assert.notNull(client, "client must not be null");
100103

101104
this.client = client;
105+
this.sqlClient = client.sql();
102106
this.jsonpMapper = client._transport().jsonpMapper();
103107
requestConverter = new RequestConverter(converter, jsonpMapper);
104108
responseConverter = new ResponseConverter(jsonpMapper);
@@ -646,6 +650,14 @@ public BaseQueryBuilder queryBuilderWithIds(List<String> ids) {
646650
return NativeQuery.builder().withIds(ids);
647651
}
648652

653+
@Override
654+
public Mono<SqlResponse> search(SqlQuery query) {
655+
Assert.notNull(query, "Query must not be null.");
656+
657+
co.elastic.clients.elasticsearch.sql.QueryRequest request = requestConverter.sqlQueryRequest(query);
658+
return sqlClient.query(request).onErrorMap(this::translateException).map(responseConverter::sqlResponse);
659+
}
660+
649661
/**
650662
* Callback interface to be used with {@link #execute(ReactiveElasticsearchTemplate.ClientCallback<>)} for operating
651663
* directly on {@link ReactiveElasticsearchClient}.

Diff for: src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java

+17
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import java.util.HashMap;
6969
import java.util.List;
7070
import java.util.Map;
71+
import java.util.Objects;
7172
import java.util.Set;
7273
import java.util.function.Function;
7374
import java.util.stream.Collectors;
@@ -530,6 +531,22 @@ public co.elastic.clients.elasticsearch.indices.GetTemplateRequest indicesGetTem
530531
.of(gtr -> gtr.name(getTemplateRequest.getTemplateName()).flatSettings(true));
531532
}
532533

534+
public co.elastic.clients.elasticsearch.sql.QueryRequest sqlQueryRequest(SqlQuery query) {
535+
Assert.notNull(query, "Query must not be null.");
536+
537+
return co.elastic.clients.elasticsearch.sql.QueryRequest.of(sqb -> {
538+
sqb.query(query.getQuery()).catalog(query.getCatalog()).columnar(query.getColumnar()).cursor(query.getCursor())
539+
.fetchSize(query.getFetchSize()).fieldMultiValueLeniency(query.getFieldMultiValueLeniency())
540+
.indexUsingFrozen(query.getIndexIncludeFrozen()).keepAlive(time(query.getKeepAlive()))
541+
.keepOnCompletion(query.getKeepOnCompletion()).pageTimeout(time(query.getPageTimeout()))
542+
.requestTimeout(time(query.getRequestTimeout()))
543+
.waitForCompletionTimeout(time(query.getWaitForCompletionTimeout())).filter(getQuery(query.getFilter(), null))
544+
.timeZone(Objects.toString(query.getTimeZone(), null)).format("json");
545+
546+
return sqb;
547+
});
548+
}
549+
533550
// endregion
534551

535552
// region documents

Diff for: src/main/java/org/springframework/data/elasticsearch/client/elc/ResponseConverter.java

+57-19
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,9 @@
1515
*/
1616
package org.springframework.data.elasticsearch.client.elc;
1717

18-
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.*;
19-
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.*;
20-
21-
import co.elastic.clients.elasticsearch._types.BulkIndexByScrollFailure;
22-
import co.elastic.clients.elasticsearch._types.ErrorCause;
23-
import co.elastic.clients.elasticsearch._types.Time;
24-
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
25-
import co.elastic.clients.elasticsearch.cluster.ComponentTemplateSummary;
26-
import co.elastic.clients.elasticsearch.cluster.GetComponentTemplateResponse;
27-
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
28-
import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse;
29-
import co.elastic.clients.elasticsearch.core.GetScriptResponse;
30-
import co.elastic.clients.elasticsearch.core.UpdateByQueryResponse;
31-
import co.elastic.clients.elasticsearch.core.mget.MultiGetError;
32-
import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem;
33-
import co.elastic.clients.elasticsearch.indices.*;
34-
import co.elastic.clients.elasticsearch.indices.get_index_template.IndexTemplateItem;
35-
import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord;
36-
import co.elastic.clients.json.JsonpMapper;
18+
import static org.springframework.data.elasticsearch.client.elc.JsonUtils.toJson;
19+
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.removePrefixFromJson;
20+
import static org.springframework.data.elasticsearch.client.elc.TypeUtils.typeMapping;
3721

3822
import java.util.ArrayList;
3923
import java.util.HashMap;
@@ -61,10 +45,41 @@
6145
import org.springframework.data.elasticsearch.core.query.StringQuery;
6246
import org.springframework.data.elasticsearch.core.reindex.ReindexResponse;
6347
import org.springframework.data.elasticsearch.core.script.Script;
48+
import org.springframework.data.elasticsearch.core.sql.SqlResponse;
6449
import org.springframework.data.elasticsearch.support.DefaultStringObjectMap;
6550
import org.springframework.lang.Nullable;
6651
import org.springframework.util.Assert;
6752

53+
import co.elastic.clients.elasticsearch._types.BulkIndexByScrollFailure;
54+
import co.elastic.clients.elasticsearch._types.ErrorCause;
55+
import co.elastic.clients.elasticsearch._types.Time;
56+
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
57+
import co.elastic.clients.elasticsearch.cluster.ComponentTemplateSummary;
58+
import co.elastic.clients.elasticsearch.cluster.GetComponentTemplateResponse;
59+
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
60+
import co.elastic.clients.elasticsearch.core.DeleteByQueryResponse;
61+
import co.elastic.clients.elasticsearch.core.GetScriptResponse;
62+
import co.elastic.clients.elasticsearch.core.UpdateByQueryResponse;
63+
import co.elastic.clients.elasticsearch.core.mget.MultiGetError;
64+
import co.elastic.clients.elasticsearch.core.mget.MultiGetResponseItem;
65+
import co.elastic.clients.elasticsearch.indices.Alias;
66+
import co.elastic.clients.elasticsearch.indices.AliasDefinition;
67+
import co.elastic.clients.elasticsearch.indices.GetAliasResponse;
68+
import co.elastic.clients.elasticsearch.indices.GetIndexResponse;
69+
import co.elastic.clients.elasticsearch.indices.GetIndexTemplateResponse;
70+
import co.elastic.clients.elasticsearch.indices.GetIndicesSettingsResponse;
71+
import co.elastic.clients.elasticsearch.indices.GetMappingResponse;
72+
import co.elastic.clients.elasticsearch.indices.GetTemplateResponse;
73+
import co.elastic.clients.elasticsearch.indices.IndexSettings;
74+
import co.elastic.clients.elasticsearch.indices.IndexState;
75+
import co.elastic.clients.elasticsearch.indices.IndexTemplateSummary;
76+
import co.elastic.clients.elasticsearch.indices.TemplateMapping;
77+
import co.elastic.clients.elasticsearch.indices.get_index_template.IndexTemplateItem;
78+
import co.elastic.clients.elasticsearch.indices.get_mapping.IndexMappingRecord;
79+
import co.elastic.clients.elasticsearch.sql.QueryResponse;
80+
import co.elastic.clients.json.JsonData;
81+
import co.elastic.clients.json.JsonpMapper;
82+
6883
/**
6984
* Class to convert Elasticsearch responses into Spring Data Elasticsearch classes.
7085
*
@@ -536,6 +551,29 @@ public Script scriptResponse(GetScriptResponse response) {
536551
}
537552
// endregion
538553

554+
// region sql
555+
public SqlResponse sqlResponse(QueryResponse response) {
556+
SqlResponse.Builder builder = SqlResponse.builder();
557+
builder.withRunning(Boolean.TRUE.equals(response.isRunning()))
558+
.withPartial(Boolean.TRUE.equals(response.isPartial())).withCursor(response.cursor());
559+
560+
final List<SqlResponse.Column> columns = response.columns().stream()
561+
.map(column -> new SqlResponse.Column(column.name(), column.type())).toList();
562+
builder.withColumns(columns);
563+
564+
for (List<JsonData> rowValues : response.rows()) {
565+
SqlResponse.Row.Builder rowBuilder = SqlResponse.Row.builder();
566+
for (int idx = 0; idx < rowValues.size(); idx++) {
567+
rowBuilder.withValue(columns.get(idx), rowValues.get(idx).toJson());
568+
}
569+
570+
builder.withRow(rowBuilder.build());
571+
}
572+
573+
return builder.build();
574+
}
575+
// end region
576+
539577
// region helper functions
540578

541579
private long timeToLong(Time time) {

Diff for: src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
2121
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
2222
import org.springframework.data.elasticsearch.core.script.ScriptOperations;
23+
import org.springframework.data.elasticsearch.core.sql.SqlOperations;
2324
import org.springframework.lang.Nullable;
2425

2526
/**
@@ -35,7 +36,7 @@
3536
* @author Dmitriy Yakovlev
3637
* @author Peter-Josef Meisch
3738
*/
38-
public interface ElasticsearchOperations extends DocumentOperations, SearchOperations, ScriptOperations {
39+
public interface ElasticsearchOperations extends DocumentOperations, SearchOperations, ScriptOperations, SqlOperations {
3940

4041
/**
4142
* get an {@link IndexOperations} that is bound to the given class

Diff for: src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
2222
import org.springframework.data.elasticsearch.core.routing.RoutingResolver;
2323
import org.springframework.data.elasticsearch.core.script.ReactiveScriptOperations;
24+
import org.springframework.data.elasticsearch.core.sql.ReactiveSqlOperations;
2425
import org.springframework.lang.Nullable;
2526

2627
/**
@@ -31,7 +32,7 @@
3132
* @since 3.2
3233
*/
3334
public interface ReactiveElasticsearchOperations
34-
extends ReactiveDocumentOperations, ReactiveSearchOperations, ReactiveScriptOperations {
35+
extends ReactiveDocumentOperations, ReactiveSearchOperations, ReactiveScriptOperations, ReactiveSqlOperations {
3536

3637
/**
3738
* Get the {@link ElasticsearchConverter} used.

0 commit comments

Comments
 (0)