Skip to content

Commit dc8d921

Browse files
committed
Refactor to access views within FROM command
* Also add csv-spec tests that actually pass! * Expand REST API to cover get and list to make it easier to check cluster status before loading views * Get CsvTests working on views * Initial work on view service configuration * Max nested view depth * Max number of view definitions * Max individual view query length * Support TransportVersions for ViewMetadata serialization
1 parent 1faa227 commit dc8d921

File tree

74 files changed

+3619
-850
lines changed

Some content is hidden

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

74 files changed

+3619
-850
lines changed

benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.elasticsearch.license.XPackLicenseState;
1717
import org.elasticsearch.xpack.esql.analysis.*;
1818
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
19+
import org.elasticsearch.xpack.esql.core.tree.Source;
1920
import org.elasticsearch.xpack.esql.core.type.EsField;
2021
import org.elasticsearch.xpack.esql.core.util.DateUtils;
2122
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
@@ -26,6 +27,7 @@
2627
import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer;
2728
import org.elasticsearch.xpack.esql.parser.EsqlParser;
2829
import org.elasticsearch.xpack.esql.parser.QueryParams;
30+
import org.elasticsearch.xpack.esql.plan.IndexPattern;
2931
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
3032
import org.elasticsearch.xpack.esql.plugin.QueryPragmas;
3133
import org.elasticsearch.xpack.esql.session.Configuration;
@@ -108,7 +110,7 @@ public void setup() {
108110
new AnalyzerContext(
109111
config,
110112
functionRegistry,
111-
IndexResolution.valid(esIndex),
113+
Map.of(new IndexPattern(Source.EMPTY, esIndex.name()), IndexResolution.valid(esIndex)),
112114
Map.of(),
113115
new EnrichResolution(),
114116
InferenceResolution.EMPTY,

docs/changelog/134995.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 134995
2+
summary: ES|QL Views prototype
3+
area: ES|QL
4+
type: feature
5+
issues: []
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"esql.get_view":{
3+
"documentation":{
4+
"url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-view-get",
5+
"description":"Get a VIEW for ESQL."
6+
},
7+
"stability": "experimental",
8+
"visibility": "public",
9+
"headers": {
10+
"accept": [
11+
"application/json"
12+
],
13+
"content_type": [
14+
"application/json"
15+
]
16+
},
17+
"url":{
18+
"paths":[
19+
{
20+
"path":"/_query/view/{name}",
21+
"methods":[
22+
"GET"
23+
],
24+
"parts": {
25+
"name": {
26+
"type": "string",
27+
"description": "The name of the view"
28+
}
29+
}
30+
}
31+
]
32+
}
33+
}
34+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"esql.list_views": {
3+
"documentation": {
4+
"url": null,
5+
"description": "List all defined ES|QL views"
6+
},
7+
"stability": "experimental",
8+
"visibility": "public",
9+
"headers": {
10+
"accept": [
11+
"application/json"
12+
],
13+
"content_type": [
14+
"application/json"
15+
]
16+
},
17+
"url": {
18+
"paths": [
19+
{
20+
"path": "/_query/views",
21+
"methods": [
22+
"GET"
23+
]
24+
}
25+
]
26+
}
27+
}
28+
}

rest-api-spec/src/main/resources/rest-api-spec/api/esql.put_view.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
{
1616
"path":"/_query/view/{name}",
1717
"methods":[
18-
"POST"
18+
"PUT"
1919
],
2020
"parts": {
2121
"name": {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
9198000
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
dimension_values,9197000
1+
esql_views,9198000

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/Attribute.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ public abstract class Attribute extends NamedExpression {
3535
/**
3636
* Changing this will break bwc with 8.15, see {@link FieldAttribute#fieldName()}.
3737
*/
38-
protected static final String SYNTHETIC_ATTRIBUTE_NAME_PREFIX = "$$";
38+
public static final String SYNTHETIC_ATTRIBUTE_NAME_PREFIX = "$$";
39+
40+
public static final String SYNTHETIC_ATTRIBUTE_NAME_SEPARATOR = "$";
3941

4042
private static final TransportVersion ESQL_QUALIFIERS_IN_ATTRIBUTES = TransportVersion.fromName("esql_qualifiers_in_attributes");
4143

@@ -77,7 +79,7 @@ public Attribute(
7779
}
7880

7981
public static String rawTemporaryName(String... parts) {
80-
var name = String.join("$", parts);
82+
var name = String.join(SYNTHETIC_ATTRIBUTE_NAME_SEPARATOR, parts);
8183
return name.isEmpty() || name.startsWith(SYNTHETIC_ATTRIBUTE_NAME_PREFIX) ? name : SYNTHETIC_ATTRIBUTE_NAME_PREFIX + name;
8284
}
8385

x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.JOIN_PLANNING_V1;
5757
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.METADATA_FIELDS_REMOTE_TEST;
5858
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.UNMAPPED_FIELDS;
59+
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.VIEWS_V1;
5960
import static org.elasticsearch.xpack.esql.qa.rest.RestEsqlTestCase.hasCapabilities;
6061
import static org.mockito.ArgumentMatchers.any;
6162
import static org.mockito.Mockito.doAnswer;
@@ -157,6 +158,8 @@ protected void shouldSkipTest(String testName) throws IOException {
157158
}
158159
// Unmapped fields require a correct capability response from every cluster, which isn't currently implemented.
159160
assumeFalse("UNMAPPED FIELDS not yet supported in CCS", testCase.requiredCapabilities.contains(UNMAPPED_FIELDS.capabilityName()));
161+
// Views require a correct capability response from every cluster, which isn't currently implemented.
162+
assumeFalse("VIEWS not yet supported in CCS", testCase.requiredCapabilities.contains(VIEWS_V1.capabilityName()));
160163
// Tests that use capabilities not supported in CCS
161164
assumeFalse(
162165
"This syntax is not supported with remote LOOKUP JOIN",

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

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.util.Locale;
5252
import java.util.Map;
5353
import java.util.TreeMap;
54+
import java.util.concurrent.Callable;
5455
import java.util.stream.Collectors;
5556
import java.util.stream.IntStream;
5657
import java.util.stream.LongStream;
@@ -70,7 +71,9 @@
7071
import static org.elasticsearch.xpack.esql.CsvTestUtils.loadCsvSpecValues;
7172
import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.createInferenceEndpoints;
7273
import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.deleteInferenceEndpoints;
74+
import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.deleteViews;
7375
import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.loadDataSetIntoEs;
76+
import static org.elasticsearch.xpack.esql.CsvTestsDataLoader.loadViewsIntoEs;
7477
import static org.elasticsearch.xpack.esql.EsqlTestUtils.classpathResources;
7578
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.COMPLETION;
7679
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.KNN_FUNCTION_V5;
@@ -97,6 +100,7 @@ public abstract class EsqlSpecTestCase extends ESRestTestCase {
97100
protected final String instructions;
98101
protected final Mode mode;
99102
protected static Boolean supportsTook;
103+
protected static Boolean supportsViews;
100104

101105
@ParametersFactory(argumentFormatting = "csv-spec:%2$s.%3$s")
102106
public static List<Object[]> readScriptSpec() throws Exception {
@@ -122,23 +126,67 @@ protected EsqlSpecTestCase(
122126
this.mode = randomFrom(Mode.values());
123127
}
124128

125-
private static boolean dataLoaded = false;
129+
private static class Protected {
130+
private volatile boolean completed = false;
131+
private volatile boolean started = false;
132+
private volatile Throwable failure = null;
133+
134+
private void protectedBlock(Callable<Void> callable) {
135+
if (completed) {
136+
return;
137+
}
138+
// In case tests get run in parallel, we ensure only one setup is run, and other tests wait for this
139+
synchronized (this) {
140+
if (completed) {
141+
return;
142+
}
143+
if (started) {
144+
// Should only happen if a previous test setup failed, possibly with partial setup, let's fail fast the current test
145+
if (failure != null) {
146+
fail(failure, "Previous test setup failed: " + failure.getMessage());
147+
}
148+
fail("Previous test setup failed with unknown error");
149+
}
150+
started = true;
151+
try {
152+
callable.call();
153+
completed = true;
154+
} catch (Throwable t) {
155+
failure = t;
156+
fail(failure, "Current test setup failed: " + failure.getMessage());
157+
}
158+
}
159+
}
160+
161+
private synchronized void reset() {
162+
completed = false;
163+
started = false;
164+
failure = null;
165+
}
166+
}
167+
168+
private static final Protected INGEST = new Protected();
169+
private static final Protected VIEWS = new Protected();
126170
protected static boolean testClustersOk = true;
127171

128172
@Before
129-
public void setup() throws IOException {
173+
public void setup() {
130174
assumeTrue("test clusters were broken", testClustersOk);
131-
boolean supportsLookup = supportsIndexModeLookup();
132-
boolean supportsSourceMapping = supportsSourceFieldMapping();
133-
boolean supportsInferenceTestService = supportsInferenceTestService();
134-
if (dataLoaded == false) {
135-
if (supportsInferenceTestService) {
175+
INGEST.protectedBlock(() -> {
176+
// Inference endpoints must be created before ingesting any datasets that rely on them (mapping of inference_id)
177+
if (supportsInferenceTestService()) {
136178
createInferenceEndpoints(adminClient());
137179
}
138-
139-
loadDataSetIntoEs(client(), supportsLookup, supportsSourceMapping, supportsInferenceTestService);
140-
dataLoaded = true;
141-
}
180+
loadDataSetIntoEs(client(), supportsIndexModeLookup(), supportsSourceFieldMapping(), supportsInferenceTestService());
181+
return null;
182+
});
183+
// Views can be created before or after ingest, since index resolution is currently only done on the combined query
184+
VIEWS.protectedBlock(() -> {
185+
if (supportsViews()) {
186+
loadViewsIntoEs(adminClient());
187+
}
188+
return null;
189+
});
142190
}
143191

144192
@AfterClass
@@ -147,15 +195,16 @@ public static void wipeTestData() throws IOException {
147195
return;
148196
}
149197
try {
150-
dataLoaded = false;
151198
adminClient().performRequest(new Request("DELETE", "/*"));
152199
} catch (ResponseException e) {
153200
// 404 here just means we had no indexes
154201
if (e.getResponse().getStatusLine().getStatusCode() != 404) {
155202
throw e;
156203
}
157204
}
158-
205+
INGEST.reset();
206+
deleteViews(adminClient());
207+
VIEWS.reset();
159208
deleteInferenceEndpoints(adminClient());
160209
}
161210

@@ -481,6 +530,13 @@ protected boolean supportsTook() throws IOException {
481530
return supportsTook;
482531
}
483532

533+
protected boolean supportsViews() {
534+
if (supportsViews == null) {
535+
supportsViews = hasCapabilities(adminClient(), List.of("views_v1"));
536+
}
537+
return supportsViews;
538+
}
539+
484540
private String tookKey(long took) {
485541
if (took < 10) {
486542
return "lt_10ms";

0 commit comments

Comments
 (0)