Skip to content

Commit dc91ca4

Browse files
luigidellaquilamridula-s109
authored andcommitted
ES|QL: project_routing validation and Kibana docs (elastic#134778)
1 parent ee0547d commit dc91ca4

File tree

5 files changed

+226
-0
lines changed

5 files changed

+226
-0
lines changed

docs/reference/query-languages/esql/kibana/definition/settings/project_routing.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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.esql.plan;
9+
10+
import org.elasticsearch.transport.RemoteClusterService;
11+
import org.elasticsearch.xpack.esql.core.type.DataType;
12+
import org.elasticsearch.xpack.esql.parser.ParsingException;
13+
14+
import java.util.function.Predicate;
15+
16+
public enum QuerySettings {
17+
// TODO check cluster state and see if project routing is allowed
18+
// see https://github.com/elastic/elasticsearch/pull/134446
19+
// PROJECT_ROUTING(..., state -> state.getRemoteClusterNames().crossProjectEnabled());
20+
PROJECT_ROUTING(
21+
"project_routing",
22+
DataType.KEYWORD,
23+
true,
24+
false,
25+
true,
26+
"A project routing expression, "
27+
+ "used to define which projects to route the query to. "
28+
+ "Only supported if Cross-Project Search is enabled."
29+
),;
30+
31+
private String settingName;
32+
private DataType type;
33+
private final boolean serverlessOnly;
34+
private final boolean snapshotOnly;
35+
private final boolean preview;
36+
private final String description;
37+
private final Predicate<RemoteClusterService> validator;
38+
39+
QuerySettings(
40+
String name,
41+
DataType type,
42+
boolean serverlessOnly,
43+
boolean preview,
44+
boolean snapshotOnly,
45+
String description,
46+
Predicate<RemoteClusterService> validator
47+
) {
48+
this.settingName = name;
49+
this.type = type;
50+
this.serverlessOnly = serverlessOnly;
51+
this.preview = preview;
52+
this.snapshotOnly = snapshotOnly;
53+
this.description = description;
54+
this.validator = validator;
55+
}
56+
57+
QuerySettings(String name, DataType type, boolean serverlessOnly, boolean preview, boolean snapshotOnly, String description) {
58+
this(name, type, serverlessOnly, preview, snapshotOnly, description, state -> true);
59+
}
60+
61+
public String settingName() {
62+
return settingName;
63+
}
64+
65+
public DataType type() {
66+
return type;
67+
}
68+
69+
public boolean serverlessOnly() {
70+
return serverlessOnly;
71+
}
72+
73+
public boolean snapshotOnly() {
74+
return snapshotOnly;
75+
}
76+
77+
public boolean preview() {
78+
return preview;
79+
}
80+
81+
public String description() {
82+
return description;
83+
}
84+
85+
public Predicate<RemoteClusterService> validator() {
86+
return validator;
87+
}
88+
89+
public static void validate(EsqlStatement statement, RemoteClusterService clusterService) {
90+
for (QuerySetting setting : statement.settings()) {
91+
boolean found = false;
92+
for (QuerySettings qs : values()) {
93+
if (qs.settingName().equals(setting.name())) {
94+
found = true;
95+
if (setting.value().dataType() != qs.type()) {
96+
throw new ParsingException(setting.source(), "Setting [" + setting.name() + "] must be of type " + qs.type());
97+
}
98+
if (qs.validator().test(clusterService) == false) {
99+
throw new ParsingException(setting.source(), "Setting [" + setting.name() + "] is not allowed");
100+
}
101+
break;
102+
}
103+
}
104+
if (found == false) {
105+
throw new ParsingException(setting.source(), "Unknown setting [" + setting.name() + "]");
106+
}
107+
}
108+
}
109+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.elasticsearch.xpack.esql.plan.EsqlStatement;
6262
import org.elasticsearch.xpack.esql.plan.IndexPattern;
6363
import org.elasticsearch.xpack.esql.plan.QuerySetting;
64+
import org.elasticsearch.xpack.esql.plan.QuerySettings;
6465
import org.elasticsearch.xpack.esql.plan.logical.Explain;
6566
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
6667
import org.elasticsearch.xpack.esql.plan.logical.join.InlineJoin;
@@ -331,6 +332,7 @@ private EsqlStatement parse(String query, QueryParams params) {
331332
LOGGER.debug("Parsed logical plan:\n{}", parsed.plan());
332333
LOGGER.debug("Parsed settings:\n[{}]", parsed.settings().stream().map(QuerySetting::toString).collect(joining("; ")));
333334
}
335+
QuerySettings.validate(parsed, remoteClusterService);
334336
return parsed;
335337
}
336338

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThan;
4343
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThanOrEqual;
4444
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.NotEquals;
45+
import org.elasticsearch.xpack.esql.plan.QuerySettings;
4546
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
4647
import org.elasticsearch.xpack.esql.session.Configuration;
4748

@@ -1078,6 +1079,45 @@ void renderTypes(String name, List<EsqlFunctionRegistry.ArgSignature> args) thro
10781079
}
10791080
}
10801081

1082+
public static class SettingsDocsSupport extends DocsV3Support {
1083+
1084+
private final QuerySettings setting;
1085+
1086+
public SettingsDocsSupport(QuerySettings setting, Class<?> testClass, Callbacks callbacks) {
1087+
super("settings", setting.settingName(), testClass, Set::of, callbacks);
1088+
this.setting = setting;
1089+
}
1090+
1091+
@Override
1092+
public void renderSignature() throws IOException {
1093+
// Unimplemented until we make setting docs dynamically generated
1094+
}
1095+
1096+
@Override
1097+
public void renderDocs() throws Exception {
1098+
// TODO docs for settings
1099+
renderKibanaCommandDefinition();
1100+
}
1101+
1102+
void renderKibanaCommandDefinition() throws Exception {
1103+
try (XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint().lfAtEnd().startObject()) {
1104+
builder.field(
1105+
"comment",
1106+
"This is generated by ESQL’s DocsV3Support. Do not edit it. See ../README.md for how to regenerate it."
1107+
);
1108+
builder.field("name", name);
1109+
builder.field("type", setting.type().typeName());
1110+
builder.field("serverlessOnly", setting.serverlessOnly());
1111+
builder.field("preview", setting.preview());
1112+
builder.field("snapshot_only", setting.snapshotOnly());
1113+
builder.field("description", setting.description());
1114+
String rendered = Strings.toString(builder.endObject());
1115+
logger.info("Writing kibana setting definition for [{}]:\n{}", name, rendered);
1116+
writeToTempKibanaDir("definition", "json", rendered);
1117+
}
1118+
}
1119+
}
1120+
10811121
protected String buildFunctionSignatureSvg() throws IOException {
10821122
return (definition != null) ? RailRoadDiagram.functionSignature(definition) : null;
10831123
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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.esql.plan;
9+
10+
import org.elasticsearch.common.lucene.BytesRefs;
11+
import org.elasticsearch.test.ESTestCase;
12+
import org.elasticsearch.xpack.esql.core.expression.Alias;
13+
import org.elasticsearch.xpack.esql.core.expression.Literal;
14+
import org.elasticsearch.xpack.esql.core.tree.Source;
15+
import org.elasticsearch.xpack.esql.core.type.DataType;
16+
import org.elasticsearch.xpack.esql.expression.function.DocsV3Support;
17+
import org.elasticsearch.xpack.esql.parser.ParsingException;
18+
import org.junit.AfterClass;
19+
20+
import java.util.List;
21+
22+
import static org.hamcrest.Matchers.containsString;
23+
24+
public class QuerySettingsTests extends ESTestCase {
25+
26+
public void test() {
27+
28+
QuerySetting project_routing = new QuerySetting(
29+
Source.EMPTY,
30+
new Alias(Source.EMPTY, "project_routing", new Literal(Source.EMPTY, BytesRefs.toBytesRef("my-project"), DataType.KEYWORD))
31+
);
32+
QuerySettings.validate(new EsqlStatement(null, List.of(project_routing)), null);
33+
34+
QuerySetting wrong_type = new QuerySetting(
35+
Source.EMPTY,
36+
new Alias(Source.EMPTY, "project_routing", new Literal(Source.EMPTY, 12, DataType.INTEGER))
37+
);
38+
assertThat(
39+
expectThrows(ParsingException.class, () -> QuerySettings.validate(new EsqlStatement(null, List.of(wrong_type)), null))
40+
.getMessage(),
41+
containsString("Setting [project_routing] must be of type KEYWORD")
42+
);
43+
44+
QuerySetting non_existing = new QuerySetting(
45+
Source.EMPTY,
46+
new Alias(Source.EMPTY, "non_existing", new Literal(Source.EMPTY, BytesRefs.toBytesRef("12"), DataType.KEYWORD))
47+
);
48+
assertThat(
49+
expectThrows(ParsingException.class, () -> QuerySettings.validate(new EsqlStatement(null, List.of(non_existing)), null))
50+
.getMessage(),
51+
containsString("Unknown setting [non_existing]")
52+
);
53+
}
54+
55+
@AfterClass
56+
public static void generateDocs() throws Exception {
57+
for (QuerySettings value : QuerySettings.values()) {
58+
DocsV3Support.SettingsDocsSupport settingsDocsSupport = new DocsV3Support.SettingsDocsSupport(
59+
value,
60+
QuerySettingsTests.class,
61+
DocsV3Support.callbacksFromSystemProperty()
62+
);
63+
settingsDocsSupport.renderDocs();
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)