Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
49d15e6
ESQL: Views REST API
craigtaverner Feb 9, 2025
507bf1c
Update docs/changelog/137818.yaml
craigtaverner Nov 11, 2025
b0ae25c
Refine changelog
craigtaverner Nov 11, 2025
73da7cd
Expand get-views to cover use cases for listing views
craigtaverner Nov 12, 2025
21ca8f0
Fixed failing tests after changing behaviour of get to throw exceptio…
craigtaverner Nov 12, 2025
050d89c
Fixed failing yaml-tests after changing behaviour of get to return ol…
craigtaverner Nov 12, 2025
f529f11
Remove list_views API (covered by get_view now)
craigtaverner Nov 12, 2025
2694cef
Simply fixes based on code review
craigtaverner Nov 13, 2025
d466dcd
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Nov 13, 2025
fe44dbb
Fix transport version numbers
craigtaverner Nov 13, 2025
507c781
Update REST API to use ProjectID where possible
craigtaverner Nov 13, 2025
ef4e0bf
Fixed things that did not get backported correctly
craigtaverner Nov 13, 2025
2065a22
Fixed named writable
craigtaverner Nov 13, 2025
3cd972d
Missing project ID
craigtaverner Nov 13, 2025
adbee24
Register NamedDiff
craigtaverner Nov 14, 2025
4ddbebf
Make views tests not fail in release mode
craigtaverner Nov 14, 2025
88216dd
Disable for release build and have tests that assert this
craigtaverner Nov 14, 2025
3611c1f
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Nov 14, 2025
d55fe6e
Update transport versions
craigtaverner Nov 14, 2025
dc1f152
Switch to using FeatureFlag for enabling/disabling the API
craigtaverner Nov 17, 2025
b195a2a
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Nov 17, 2025
71bc604
Update transport versions
craigtaverner Nov 17, 2025
341713d
Merge branch 'main' into views_rest_crud
craigtaverner Nov 17, 2025
e44cce4
Do not use cluster_features for yaml tests
craigtaverner Nov 18, 2025
07e3153
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Nov 18, 2025
cbaf51f
Remove NodeFeature because the FeatureFlag conflicts with it
craigtaverner Nov 18, 2025
b2b858c
Merge branch 'main' into views_rest_crud
craigtaverner Nov 18, 2025
2ada8e2
Merge branch 'main' into views_rest_crud
craigtaverner Nov 18, 2025
64f3331
Move from unbatch to batched action processing using SequentialAcking…
craigtaverner Nov 19, 2025
ba6a6a1
Round of fixes based on code review
craigtaverner Nov 19, 2025
8ef87be
Merge branch 'main' into views_rest_crud
craigtaverner Nov 19, 2025
c1873ea
Delete docs/changelog/137818.yaml
craigtaverner Nov 19, 2025
48aeef7
Fixed faioling tests
craigtaverner Nov 19, 2025
2135cb9
Merge branch 'main' into views_rest_crud
craigtaverner Nov 19, 2025
6680668
Simplify metadata parsing
craigtaverner Nov 19, 2025
b7c7c73
Fix comment and add one more syntax validation test
craigtaverner Nov 19, 2025
2c9ded6
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Nov 19, 2025
32020b7
Update transport version
craigtaverner Nov 19, 2025
1a9113f
Simplify view service
craigtaverner Nov 19, 2025
8c1ad1a
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Nov 20, 2025
d881abe
Update transport version
craigtaverner Nov 20, 2025
724de84
Merge branch 'main' into views_rest_crud
craigtaverner Nov 20, 2025
3703936
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Nov 24, 2025
e1bb8fd
Fixes after merging main
craigtaverner Nov 24, 2025
7d61793
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Nov 25, 2025
dde0a7e
Update transport version
craigtaverner Nov 25, 2025
562e79d
Fixed getting ProjectMetadata from cluster service
craigtaverner Nov 25, 2025
d0d9fdb
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Nov 27, 2025
a77ad01
Updated transport version
craigtaverner Nov 27, 2025
fe9f92d
Updated ViewMetadata with view name inside View, and structured as list
craigtaverner Nov 28, 2025
d4ace23
Updated GET views to use new array format
craigtaverner Nov 28, 2025
b7a693b
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Dec 1, 2025
9a2844c
Update transport version
craigtaverner Dec 1, 2025
272b202
Merge branch 'main' into views_rest_crud
craigtaverner Dec 1, 2025
7484177
Merge branch 'main' into views_rest_crud
craigtaverner Dec 1, 2025
ca36b99
Merge branch 'main' into views_rest_crud
craigtaverner Dec 2, 2025
eecb071
Moved View and ViewMetadata to server
craigtaverner Dec 2, 2025
4801a89
Merge remote-tracking branch 'origin/main' into views_rest_crud
craigtaverner Dec 2, 2025
e82eb68
Update transport version
craigtaverner Dec 2, 2025
b8e6206
Merge branch 'views_rest_crud' of github.com:craigtaverner/elasticsea…
craigtaverner Dec 2, 2025
410eb58
Move ViewTests and ViewMetadataTests to server
craigtaverner Dec 2, 2025
bc7ec4c
ESQL: Views prototype
craigtaverner Feb 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"esql.delete_view": {
"documentation": {
"url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-view-delete",
"description": "Delete a non-materialized VIEW for ESQL."
},
"stability": "experimental",
"visibility": "feature_flag",
"feature_flag": "esql_views",
"headers": {
"accept": [
"application/json"
],
"content_type": [
"application/json"
]
},
"url": {
"paths": [
{
"path": "/_query/view/{name}",
"methods": [
"DELETE"
],
"parts": {
"name": {
"type": "string",
"description": "The name of the view to delete"
}
}
}
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"description": "Get a specific running ES|QL query information"
},
"stability": "experimental",
"visibility": "public",
"visibility": "feature_flag",
"feature_flag": "esql_views",
"headers": {
"accept": [
"application/json"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"esql.get_view": {
"documentation": {
"url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-view-get",
"description": "Get a non-materialized VIEW for ESQL."
},
"stability": "experimental",
"visibility": "feature_flag",
"feature_flag": "esql_views",
"headers": {
"accept": [
"application/json"
],
"content_type": [
"application/json"
]
},
"url": {
"paths": [
{
"path": "/_query/view/{name}",
"methods": [
"GET"
],
"parts": {
"name": {
"type": "list",
"description": "A comma-separated list of view names"
}
}
},
{
"path": "/_query/view",
"methods": [
"GET"
]
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"esql.put_view": {
"documentation": {
"url": "https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-view-put",
"description": "Creates a non-materialized VIEW for ESQL."
},
"stability": "experimental",
"visibility": "public",
"headers": {
"accept": [
"application/json"
],
"content_type": [
"application/json"
]
},
"url": {
"paths": [
{
"path": "/_query/view/{name}",
"methods": [
"PUT"
],
"parts": {
"name": {
"type": "string",
"description": "The name of the view to create or update"
}
}
}
]
},
"body": {
"description": "Use the `query` element to define the ES|QL query to use as a non-materialized VIEW.",
"required": true
}
}
}
108 changes: 108 additions & 0 deletions server/src/main/java/org/elasticsearch/cluster/metadata/View.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.cluster.metadata;

import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.xcontent.ConstructingObjectParser;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContentFragment;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;

import java.io.IOException;
import java.util.Objects;

/**
* Represents a single view definition, which is simply a name and a query string.
*/
public final class View implements Writeable, ToXContentFragment {
private static final ParseField NAME = new ParseField("name");
private static final ParseField QUERY = new ParseField("query");

// Parser that includes the name field (eg. serializing/deserializing the full object)
static final ConstructingObjectParser<View, Void> PARSER = new ConstructingObjectParser<>(
"view",
false,
(args, ctx) -> new View((String) args[0], (String) args[1])
);

static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), NAME);
PARSER.declareString(ConstructingObjectParser.constructorArg(), QUERY);
}

// Parser that excludes the name field (eg. when the name is provided externally, in the URL path)
public static ConstructingObjectParser<View, Void> parser(String name) {
ConstructingObjectParser<View, Void> parser = new ConstructingObjectParser<>(
"view",
false,
(args, ctx) -> new View(name, (String) args[0])
);
parser.declareString(ConstructingObjectParser.constructorArg(), QUERY);
return parser;
}

private final String name;
private final String query;

public View(String name, String query) {
this.name = name;
this.query = query;
}

public View(StreamInput in) throws IOException {
this.name = in.readString();
this.query = in.readString();
}

public static View fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(name);
out.writeString(query);
}

public String name() {
return name;
}

public String query() {
return query;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field(NAME.getPreferredName(), name);
builder.field(QUERY.getPreferredName(), query);
return builder;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
View other = (View) o;
return Objects.equals(name, other.name) && Objects.equals(query, other.query);
}

@Override
public int hashCode() {
return Objects.hash(name, query);
}

public String toString() {
return Strings.toString(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.cluster.metadata;

import org.elasticsearch.TransportVersion;
import org.elasticsearch.cluster.AbstractNamedDiffable;
import org.elasticsearch.cluster.NamedDiff;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ChunkedToXContentHelper;
import org.elasticsearch.xcontent.ObjectParser;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContentParser;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

import static org.elasticsearch.common.io.stream.StreamOutput.GENERIC_LIST_HEADER;

/**
* Encapsulates view definitions as custom metadata inside ProjectMetadata within cluster state.
*/
public final class ViewMetadata extends AbstractNamedDiffable<Metadata.ProjectCustom> implements Metadata.ProjectCustom {
public static final String TYPE = "esql_view";
public static final List<NamedWriteableRegistry.Entry> ENTRIES = List.of(
new NamedWriteableRegistry.Entry(Metadata.ProjectCustom.class, TYPE, ViewMetadata::readFromStream),
new NamedWriteableRegistry.Entry(NamedDiff.class, TYPE, in -> ViewMetadata.readDiffFrom(Metadata.ProjectCustom.class, TYPE, in))
);
private static final TransportVersion ESQL_VIEWS = TransportVersion.fromName("esql_views");

static final ParseField VIEWS = new ParseField("views");

public static final ViewMetadata EMPTY = new ViewMetadata(Collections.emptyList());

@SuppressWarnings("unchecked")
private static final ObjectParser<ViewMetadata, Void> PARSER = new ObjectParser<>("view_metadata", ViewMetadata::new);

static {
PARSER.declareObjectArrayOrNull((viewMetadata, views) -> views.forEach(viewMetadata::add), (p, c) -> View.fromXContent(p), VIEWS);
}

public static ViewMetadata fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}

private final ArrayList<View> views;

public static ViewMetadata readFromStream(StreamInput in) throws IOException {
assert in.readByte() == GENERIC_LIST_HEADER;
int count = in.readVInt();
ArrayList<View> views = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
views.add(new View(in));
}
return new ViewMetadata(views);
}

public ViewMetadata() {
this(List.of());
}

public ViewMetadata(List<View> views) {
this.views = new ArrayList<>(views);
}

public void add(View view) {
views.add(view);
}

public List<View> views() {
return views;
}

public List<String> viewNames() {
return views.stream().map(View::name).toList();
}

public View getView(String name) {
return views.stream().filter(v -> v.name().equals(name)).findAny().orElse(null);
}

@Override
public EnumSet<Metadata.XContentContext> context() {
return Metadata.ALL_CONTEXTS;
}

@Override
public TransportVersion getMinimalSupportedVersion() {
return ESQL_VIEWS;
}

@Override
public String getWriteableName() {
return TYPE;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeGenericList(views, StreamOutput::writeWriteable);
}

@Override
public Iterator<? extends ToXContent> toXContentChunked(ToXContent.Params ignored) {
return ChunkedToXContentHelper.array(VIEWS.getPreferredName(), new ViewIterator(views));
}

public static class ViewIterator implements Iterator<ToXContent> {
private final Iterator<View> internal;

public ViewIterator(List<View> views) {
this.internal = views.iterator();
}

@Override
public boolean hasNext() {
return internal.hasNext();
}

@Override
public ToXContent next() {
View view = internal.next();
return (builder, params) -> {
builder.startObject();
view.toXContent(builder, params);
builder.endObject();
return builder;
};
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ViewMetadata that = (ViewMetadata) o;
return views.equals(that.views);
}

@Override
public int hashCode() {
return Objects.hash(views);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9232000
2 changes: 1 addition & 1 deletion server/src/main/resources/transport/upper_bounds/9.3.csv
Original file line number Diff line number Diff line change
@@ -1 +1 @@
esql_use_minimum_version_for_enrich_resolution,9231000
esql_views,9232000
Loading