Skip to content

Commit 1faa227

Browse files
ESQL: Views prototype
This is based on the origional "ESQL: View" prototype by Nik but with the following changes: * Removal of support for FROM VIEW command (ie. all parser/grammar changes reverted) * Removal of UnresolvedView and View resolution from that * Port to latest origin/main as of mid-September The goal was to keep the following parts: * REST API for view CRUD * The initial design concept of ViewService and ViewMetadata Co-authored-by: Craig Taverner <[email protected]>
1 parent 478f44d commit 1faa227

File tree

20 files changed

+1007
-7
lines changed

20 files changed

+1007
-7
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"esql.delete_view":{
3+
"documentation":{
4+
"url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-view-delete",
5+
"description":"Creates a VIEW for ESQL."
6+
},
7+
"stability": "experimental",
8+
"visibility": "public",
9+
"headers": {
10+
"accept": [ "application/json"],
11+
"content_type": ["application/json"]
12+
},
13+
"url":{
14+
"paths":[
15+
{
16+
"path":"/_query/view/{name}",
17+
"methods":[
18+
"DELETE"
19+
],
20+
"parts": {
21+
"name": {
22+
"type": "string",
23+
"description": "The name of the view"
24+
}
25+
}
26+
}
27+
]
28+
}
29+
}
30+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"esql.put_view":{
3+
"documentation":{
4+
"url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-view-put",
5+
"description":"Creates a VIEW for ESQL."
6+
},
7+
"stability": "experimental",
8+
"visibility": "public",
9+
"headers": {
10+
"accept": [ "application/json"],
11+
"content_type": ["application/json"]
12+
},
13+
"url":{
14+
"paths":[
15+
{
16+
"path":"/_query/view/{name}",
17+
"methods":[
18+
"POST"
19+
],
20+
"parts": {
21+
"name": {
22+
"type": "string",
23+
"description": "The name of the view"
24+
}
25+
}
26+
}
27+
]
28+
},
29+
"body":{
30+
"description":"Use the `query` element to start a query.",
31+
"required":true
32+
}
33+
}
34+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,11 @@ public enum Cap {
10811081
*/
10821082
FORK_UNION_TYPES,
10831083

1084+
/**
1085+
* Views.
1086+
*/
1087+
VIEW_V1(Build.current().isSnapshot()),
1088+
10841089
/**
10851090
* Support for the {@code leading_zeros} named parameter.
10861091
*/

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.elasticsearch.xpack.esql.telemetry.PlanTelemetry;
3131
import org.elasticsearch.xpack.esql.telemetry.PlanTelemetryManager;
3232
import org.elasticsearch.xpack.esql.telemetry.QueryMetric;
33+
import org.elasticsearch.xpack.esql.view.ViewService;
3334

3435
import java.util.List;
3536
import java.util.function.BiConsumer;
@@ -49,14 +50,15 @@ public class PlanExecutor {
4950

5051
public PlanExecutor(
5152
IndexResolver indexResolver,
53+
EsqlFunctionRegistry functionRegistry,
5254
MeterRegistry meterRegistry,
5355
XPackLicenseState licenseState,
5456
EsqlQueryLog queryLog,
5557
List<BiConsumer<LogicalPlan, Failures>> extraCheckers
5658
) {
5759
this.indexResolver = indexResolver;
5860
this.preAnalyzer = new PreAnalyzer();
59-
this.functionRegistry = new EsqlFunctionRegistry();
61+
this.functionRegistry = functionRegistry;
6062
this.mapper = new Mapper();
6163
this.metrics = new Metrics(functionRegistry);
6264
this.verifier = new Verifier(metrics, licenseState, extraCheckers);
@@ -69,6 +71,7 @@ public void esql(
6971
String sessionId,
7072
AnalyzerSettings analyzerSettings,
7173
EnrichPolicyResolver enrichPolicyResolver,
74+
ViewService viewService,
7275
EsqlExecutionInfo executionInfo,
7376
IndicesExpressionGrouper indicesExpressionGrouper,
7477
EsqlSession.PlanRunner planRunner,
@@ -81,6 +84,7 @@ public void esql(
8184
analyzerSettings,
8285
indexResolver,
8386
enrichPolicyResolver,
87+
viewService,
8488
preAnalyzer,
8589
functionRegistry,
8690
mapper,
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
package org.elasticsearch.xpack.esql.plan.logical;
8+
9+
import org.elasticsearch.common.io.stream.StreamOutput;
10+
import org.elasticsearch.xpack.esql.capabilities.TelemetryAware;
11+
import org.elasticsearch.xpack.esql.core.capabilities.Unresolvable;
12+
import org.elasticsearch.xpack.esql.core.expression.Attribute;
13+
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
14+
import org.elasticsearch.xpack.esql.core.tree.Source;
15+
import org.elasticsearch.xpack.esql.telemetry.PlanTelemetry;
16+
17+
import java.util.Collections;
18+
import java.util.List;
19+
import java.util.Objects;
20+
21+
public class UnresolvedView extends LeafPlan implements Unresolvable, TelemetryAware {
22+
private final String name;
23+
24+
public UnresolvedView(Source source, String name) {
25+
super(source);
26+
this.name = name;
27+
}
28+
29+
@Override
30+
public void writeTo(StreamOutput out) {
31+
throw new UnsupportedOperationException("not serialized");
32+
}
33+
34+
@Override
35+
public String getWriteableName() {
36+
throw new UnsupportedOperationException("not serialized");
37+
}
38+
39+
@Override
40+
protected NodeInfo<UnresolvedView> info() {
41+
return NodeInfo.create(this, UnresolvedView::new, name);
42+
}
43+
44+
@Override
45+
public boolean resolved() {
46+
return false;
47+
}
48+
49+
public String name() {
50+
return name;
51+
}
52+
53+
/**
54+
*
55+
* This is used by {@link PlanTelemetry} to collect query statistics
56+
* It can return
57+
* <ul>
58+
* <li>"FROM" if this a <code>|FROM idx</code> command</li>
59+
* <li>"FROM TS" if it is the result of a <code>| METRICS idx some_aggs() BY fields</code> command</li>
60+
* <li>"METRICS" if it is the result of a <code>| METRICS idx</code> (no aggs, no groupings)</li>
61+
* </ul>
62+
*/
63+
@Override
64+
public String telemetryLabel() {
65+
return "FROM VIEW";
66+
}
67+
68+
@Override
69+
public boolean expressionsResolved() {
70+
return false;
71+
}
72+
73+
@Override
74+
public List<Attribute> output() {
75+
return Collections.emptyList();
76+
}
77+
78+
@Override
79+
public String unresolvedMessage() {
80+
return "Unknown view [" + name + "]";
81+
}
82+
83+
@Override
84+
public int hashCode() {
85+
return Objects.hash(source(), name);
86+
}
87+
88+
@Override
89+
public boolean equals(Object obj) {
90+
if (this == obj) {
91+
return true;
92+
}
93+
94+
if (obj == null || getClass() != obj.getClass()) {
95+
return false;
96+
}
97+
98+
UnresolvedView other = (UnresolvedView) obj;
99+
return name.equals(other.name);
100+
}
101+
102+
@Override
103+
public String toString() {
104+
return UNRESOLVED_PREFIX + name;
105+
}
106+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.elasticsearch.xpack.esql.enrich.LookupFromIndexOperator;
7575
import org.elasticsearch.xpack.esql.execution.PlanExecutor;
7676
import org.elasticsearch.xpack.esql.expression.ExpressionWritables;
77+
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
7778
import org.elasticsearch.xpack.esql.io.stream.ExpressionQueryBuilder;
7879
import org.elasticsearch.xpack.esql.io.stream.PlanStreamWrapperQueryBuilder;
7980
import org.elasticsearch.xpack.esql.plan.PlanWritables;
@@ -82,6 +83,13 @@
8283
import org.elasticsearch.xpack.esql.querydsl.query.SingleValueQuery;
8384
import org.elasticsearch.xpack.esql.querylog.EsqlQueryLog;
8485
import org.elasticsearch.xpack.esql.session.IndexResolver;
86+
import org.elasticsearch.xpack.esql.view.DeleteViewAction;
87+
import org.elasticsearch.xpack.esql.view.PutViewAction;
88+
import org.elasticsearch.xpack.esql.view.RestDeleteViewAction;
89+
import org.elasticsearch.xpack.esql.view.RestPutViewAction;
90+
import org.elasticsearch.xpack.esql.view.TransportDeleteViewAction;
91+
import org.elasticsearch.xpack.esql.view.TransportPutViewAction;
92+
import org.elasticsearch.xpack.esql.view.ViewService;
8593

8694
import java.lang.invoke.MethodHandles;
8795
import java.util.ArrayList;
@@ -182,6 +190,8 @@ public Collection<?> createComponents(PluginServices services) {
182190
);
183191
BigArrays bigArrays = services.indicesService().getBigArrays().withCircuitBreaking();
184192
var blockFactoryProvider = blockFactoryProvider(circuitBreaker, bigArrays, maxPrimitiveArrayBlockSize);
193+
EsqlFunctionRegistry functionRegistry = new EsqlFunctionRegistry();
194+
ViewService viewService = new ViewService(services.clusterService(), functionRegistry);
185195
setupSharedSecrets();
186196
List<BiConsumer<LogicalPlan, Failures>> extraCheckers = extraCheckerProviders.stream()
187197
.flatMap(p -> p.checkers(services.projectResolver(), services.clusterService()).stream())
@@ -190,6 +200,7 @@ public Collection<?> createComponents(PluginServices services) {
190200
return List.of(
191201
new PlanExecutor(
192202
new IndexResolver(services.client()),
203+
functionRegistry,
193204
services.telemetryProvider().getMeterRegistry(),
194205
getLicenseState(),
195206
new EsqlQueryLog(services.clusterService().getClusterSettings(), services.slowLogFieldProvider()),
@@ -201,7 +212,9 @@ public Collection<?> createComponents(PluginServices services) {
201212
ThreadPool.Names.SEARCH,
202213
blockFactoryProvider.blockFactory()
203214
),
204-
blockFactoryProvider
215+
blockFactoryProvider,
216+
functionRegistry,
217+
viewService
205218
);
206219
}
207220

@@ -264,7 +277,9 @@ public List<ActionHandler> getActions() {
264277
new ActionHandler(EsqlSearchShardsAction.TYPE, EsqlSearchShardsAction.class),
265278
new ActionHandler(EsqlAsyncStopAction.INSTANCE, TransportEsqlAsyncStopAction.class),
266279
new ActionHandler(EsqlListQueriesAction.INSTANCE, TransportEsqlListQueriesAction.class),
267-
new ActionHandler(EsqlGetQueryAction.INSTANCE, TransportEsqlGetQueryAction.class)
280+
new ActionHandler(EsqlGetQueryAction.INSTANCE, TransportEsqlGetQueryAction.class),
281+
new ActionHandler(PutViewAction.INSTANCE, TransportPutViewAction.class),
282+
new ActionHandler(DeleteViewAction.INSTANCE, TransportDeleteViewAction.class)
268283
);
269284
}
270285

@@ -286,7 +301,9 @@ public List<RestHandler> getRestHandlers(
286301
new RestEsqlGetAsyncResultAction(),
287302
new RestEsqlStopAsyncAction(),
288303
new RestEsqlDeleteAsyncResultAction(),
289-
new RestEsqlListQueriesAction()
304+
new RestEsqlListQueriesAction(),
305+
new RestPutViewAction(),
306+
new RestDeleteViewAction()
290307
);
291308
}
292309

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.elasticsearch.xpack.esql.planner.PlannerSettings;
5656
import org.elasticsearch.xpack.esql.session.EsqlSession.PlanRunner;
5757
import org.elasticsearch.xpack.esql.session.Result;
58+
import org.elasticsearch.xpack.esql.view.ViewService;
5859

5960
import java.io.IOException;
6061
import java.util.ArrayList;
@@ -77,6 +78,7 @@ public class TransportEsqlQueryAction extends HandledTransportAction<EsqlQueryRe
7778
private final ClusterService clusterService;
7879
private final Executor requestExecutor;
7980
private final EnrichPolicyResolver enrichPolicyResolver;
81+
private final ViewService viewService;
8082
private final EnrichLookupService enrichLookupService;
8183
private final LookupFromIndexService lookupFromIndexService;
8284
private final AsyncTaskManagementService<EsqlQueryRequest, EsqlQueryResponse, EsqlQueryTask> asyncTaskManagementService;
@@ -98,6 +100,7 @@ public TransportEsqlQueryAction(
98100
SearchService searchService,
99101
ExchangeService exchangeService,
100102
ClusterService clusterService,
103+
ViewService viewService,
101104
ProjectResolver projectResolver,
102105
ThreadPool threadPool,
103106
BigArrays bigArrays,
@@ -112,6 +115,7 @@ public TransportEsqlQueryAction(
112115
this.threadPool = threadPool;
113116
this.planExecutor = planExecutor;
114117
this.clusterService = clusterService;
118+
this.viewService = viewService;
115119
this.requestExecutor = threadPool.executor(ThreadPool.Names.SEARCH);
116120
exchangeService.registerTransportHandler(transportService);
117121
this.exchangeService = exchangeService;
@@ -262,6 +266,7 @@ private void innerExecute(Task task, EsqlQueryRequest request, ActionListener<Es
262266
timeseriesResultTruncationDefaultSize
263267
),
264268
enrichPolicyResolver,
269+
viewService,
265270
executionInfo,
266271
remoteClusterService,
267272
planRunner,

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import org.elasticsearch.xpack.esql.planner.premapper.PreMapper;
8484
import org.elasticsearch.xpack.esql.plugin.TransportActionServices;
8585
import org.elasticsearch.xpack.esql.telemetry.PlanTelemetry;
86+
import org.elasticsearch.xpack.esql.view.ViewService;
8687

8788
import java.util.ArrayList;
8889
import java.util.Collection;
@@ -120,6 +121,7 @@ public interface PlanRunner {
120121
private final AnalyzerSettings clusterSettings;
121122
private final IndexResolver indexResolver;
122123
private final EnrichPolicyResolver enrichPolicyResolver;
124+
private final ViewService viewService;
123125

124126
private final PreAnalyzer preAnalyzer;
125127
private final Verifier verifier;
@@ -144,6 +146,7 @@ public EsqlSession(
144146
AnalyzerSettings clusterSettings,
145147
IndexResolver indexResolver,
146148
EnrichPolicyResolver enrichPolicyResolver,
149+
ViewService viewService,
147150
PreAnalyzer preAnalyzer,
148151
EsqlFunctionRegistry functionRegistry,
149152
Mapper mapper,
@@ -156,6 +159,7 @@ public EsqlSession(
156159
this.clusterSettings = clusterSettings;
157160
this.indexResolver = indexResolver;
158161
this.enrichPolicyResolver = enrichPolicyResolver;
162+
this.viewService = viewService;
159163
this.preAnalyzer = preAnalyzer;
160164
this.verifier = verifier;
161165
this.functionRegistry = functionRegistry;
@@ -181,7 +185,6 @@ public void execute(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, P
181185
assert ThreadPool.assertCurrentThreadPool(ThreadPool.Names.SEARCH);
182186
assert executionInfo != null : "Null EsqlExecutionInfo";
183187
LOGGER.debug("ESQL query:\n{}", request.query());
184-
EsqlStatement statement = parse(request.query(), request.params());
185188
Configuration configuration = new Configuration(
186189
request.timeZone() == null
187190
? statement.setting(QuerySettings.TIME_ZONE)
@@ -203,7 +206,7 @@ public void execute(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, P
203206
);
204207
FoldContext foldContext = configuration.newFoldContext();
205208

206-
LogicalPlan plan = statement.plan();
209+
LogicalPlan plan = viewService.replaceViews(parse(request.query(), request.params()).plan(), planTelemetry, configuration);
207210
if (plan instanceof Explain explain) {
208211
explainMode = true;
209212
plan = explain.query();

0 commit comments

Comments
 (0)