Skip to content

Commit 3058610

Browse files
Adhere to the new grammar
1 parent 7ad6ac3 commit 3058610

File tree

2 files changed

+195
-81
lines changed

2 files changed

+195
-81
lines changed

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

Lines changed: 113 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
import static org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.SelectorResolver.SELECTOR_SEPARATOR;
2626
import static org.elasticsearch.transport.RemoteClusterAware.REMOTE_CLUSTER_INDEX_SEPARATOR;
27-
import static org.elasticsearch.transport.RemoteClusterAware.isRemoteIndexName;
2827
import static org.elasticsearch.transport.RemoteClusterAware.splitIndexName;
2928
import static org.elasticsearch.xpack.esql.core.util.StringUtils.EXCLUSION;
3029
import static org.elasticsearch.xpack.esql.core.util.StringUtils.WILDCARD;
@@ -91,7 +90,7 @@ public String visitIndexPattern(List<EsqlBaseParser.IndexPatternContext> ctx) {
9190
String selectorString = visitSelectorString(c.selectorString());
9291

9392
hasSeenStar.set(hasSeenStar.get() || indexPattern.contains(WILDCARD));
94-
validateClusterAndIndexPatterns(indexPattern, c, clusterString, selectorString, hasSeenStar.get());
93+
validate(clusterString, indexPattern, selectorString, c, hasSeenStar.get());
9594
patterns.add(reassembleIndexName(clusterString, indexPattern, selectorString));
9695
});
9796
return Strings.collectionToDelimitedString(patterns, ",");
@@ -126,98 +125,141 @@ protected static void validateClusterString(String clusterString, EsqlBaseParser
126125
}
127126
}
128127

129-
private static void validateClusterAndIndexPatterns(
130-
String indexPattern,
131-
EsqlBaseParser.IndexPatternContext ctx,
128+
/**
129+
* Takes the parsed constituent strings and validates them.
130+
* @param clusterString Name of the remote cluster. Can be null.
131+
* @param indexPattern Name of the index.
132+
* @param selectorString Selector string, i.e. "::data" or "::failures". Can be null.
133+
* @param ctx Index Pattern Context for generating error messages with offsets.
134+
* @param hasSeenStar If we've seen an asterisk so far.
135+
*/
136+
private static void validate(
132137
String clusterString,
138+
String indexPattern,
133139
String selectorString,
140+
EsqlBaseParser.IndexPatternContext ctx,
134141
boolean hasSeenStar
135142
) {
136-
// multiple index names can be in the same double quote, e.g. indexPattern = "idx1, *, -idx2"
137-
String[] patterns = indexPattern.split(",");
138-
boolean isFirstPattern = true;
143+
/*
144+
* At this point, only 3 formats are possible:
145+
* "index_pattern(s)",
146+
* remote:index_pattern, and,
147+
* index_pattern::selector_string.
148+
*
149+
* The grammar prohibits remote:"index_pattern(s)" or "index_pattern(s)"::selector_string as they're
150+
* partially quoted. So if either of cluster string or selector string are present, there's no need
151+
* to split the pattern by comma since comma requires partial quoting.
152+
*/
139153

140-
for (String pattern : patterns) {
141-
pattern = pattern.strip();
154+
String[] patterns;
155+
if (clusterString == null && selectorString == null) {
156+
// Pattern could be quoted or is singular like "index_name".
157+
patterns = indexPattern.split(",");
158+
} else {
159+
// Either of cluster string or selector string is present. Pattern is unquoted.
160+
patterns = new String[] { indexPattern };
161+
}
142162

163+
if (patterns.length == 1) {
164+
validateSingleIndexPattern(clusterString, patterns[0], selectorString, ctx, hasSeenStar);
165+
} else {
143166
/*
144-
* Just because there was no clusterString before this index pattern does not mean that the indices
145-
* are local indices. Patterns can be clubbed with remote names within quotes such as:
146-
* "remote_one:remote_index,local_index". In this case, clusterString will be null.
167+
* Presence of multiple patterns requires a comma and comma requires quoting. If quoting is present,
168+
* cluster string and selector string cannot be present; they need to be attached within the quoting.
169+
* So we attempt to extract them later.
147170
*/
148-
if (isRemoteIndexName(pattern)) {
149-
/*
150-
* Handle scenarios like remote_one:"index1,remote_two:index2". The clusterString here is
151-
* remote_one and is associated with index1 and not index2.
152-
*/
153-
if (clusterString != null && isFirstPattern) {
154-
throw new ParsingException(
155-
source(ctx),
156-
"Index pattern [{}] contains a cluster alias despite specifying one [{}]",
157-
pattern,
158-
clusterString
159-
);
160-
}
161-
162-
// {cluster_alias, indexName}
163-
String[] clusterAliasAndIndex;
164-
try {
165-
clusterAliasAndIndex = splitIndexName(pattern);
166-
} catch (IllegalArgumentException e) {
167-
throw new ParsingException(e, source(ctx), e.getMessage());
168-
}
169-
170-
clusterString = clusterAliasAndIndex[0];
171-
pattern = clusterAliasAndIndex[1];
172-
} else if (clusterString != null) {
173-
// This is not a remote index pattern and the cluster string preceding this quoted pattern
174-
// cannot be associated with it.
175-
if (isFirstPattern == false) {
176-
clusterString = null;
177-
}
178-
}
179-
180-
if (clusterString != null) {
181-
if (selectorString != null) {
182-
throwOnMixingSelectorWithCluster(reassembleIndexName(clusterString, indexPattern, selectorString), ctx);
183-
}
184-
validateClusterString(clusterString, ctx);
185-
}
186-
187-
validateIndexForCluster(clusterString, pattern, ctx, hasSeenStar);
188-
if (selectorString != null) {
189-
try {
190-
// Ensures that the selector provided is one of the valid kinds
191-
IndexNameExpressionResolver.SelectorResolver.validateIndexSelectorString(indexPattern, selectorString);
192-
} catch (InvalidIndexNameException e) {
193-
throw new ParsingException(e, source(ctx), e.getMessage());
194-
}
171+
for (String pattern : patterns) {
172+
validateSingleIndexPattern(null, pattern, null, ctx, hasSeenStar);
195173
}
196-
197-
isFirstPattern = false;
198174
}
199175
}
200176

201-
private static void validateIndexForCluster(
177+
/**
178+
* Validates the constituent strings. Will extract the cluster string and/or selector string from the index
179+
* name if clubbed together.
180+
*
181+
* @param clusterString Name of the remote cluster. Can be null.
182+
* @param indexName Name of the index.
183+
* @param selectorString Selector string, i.e. "::data" or "::failures". Can be null.
184+
* @param ctx Index Pattern Context for generating error messages with offsets.
185+
* @param hasSeenStar If we've seen an asterisk so far.
186+
*/
187+
private static void validateSingleIndexPattern(
202188
String clusterString,
203-
String index,
189+
String indexName,
190+
String selectorString,
204191
EsqlBaseParser.IndexPatternContext ctx,
205192
boolean hasSeenStar
206193
) {
207-
// Strip spaces off first because validation checks are not written to handle them
208-
index = index.strip();
194+
indexName = indexName.strip();
195+
196+
/*
197+
* Precedence:
198+
* 1. Cannot mix cluster and selector strings.
199+
* 2. Cluster string must be valid.
200+
* 3. Index name must be valid.
201+
* 4. Selector string must be valid.
202+
*
203+
* Since cluster string and/or selector string can be clubbed with the index name, we must try to
204+
* manually extract them before we attempt to do #2, #3, and #4.
205+
*/
206+
207+
// It is possible to specify a pattern like "remote_cluster:index_name". Try to extract such details from the index string.
208+
if (clusterString == null && selectorString == null) {
209+
try {
210+
var split = splitIndexName(indexName);
211+
clusterString = split[0];
212+
indexName = split[1];
213+
} catch (IllegalArgumentException e) {
214+
throw new ParsingException(e, source(ctx), e.getMessage());
215+
}
216+
}
209217

218+
// At the moment, selector strings for remote indices is not allowed.
219+
if (clusterString != null && selectorString != null) {
220+
throwOnMixingSelectorWithCluster(reassembleIndexName(clusterString, indexName, selectorString), ctx);
221+
}
222+
223+
// Validation in the right precedence.
224+
if (clusterString != null) {
225+
clusterString = clusterString.strip();
226+
validateClusterString(clusterString, ctx);
227+
}
228+
229+
/*
230+
* It is possible for selector string to be attached to the index: "index_name::selector_string".
231+
* Try to extract the selector string.
232+
*/
210233
try {
211-
Tuple<String, String> splitPattern = IndexNameExpressionResolver.splitSelectorExpression(index);
212-
if (splitPattern.v2() != null && clusterString != null) {
213-
throwOnMixingSelectorWithCluster(reassembleIndexName(clusterString, splitPattern.v1(), splitPattern.v2()), ctx);
234+
Tuple<String, String> splitPattern = IndexNameExpressionResolver.splitSelectorExpression(indexName);
235+
if (splitPattern.v2() != null) {
236+
// Cluster string too was clubbed with the index name like selector string.
237+
if (clusterString != null) {
238+
throwOnMixingSelectorWithCluster(reassembleIndexName(clusterString, splitPattern.v1(), splitPattern.v2()), ctx);
239+
} else {
240+
// We've seen a selectorString. Use it.
241+
selectorString = splitPattern.v2();
242+
}
214243
}
215244

216-
index = splitPattern.v1();
245+
indexName = splitPattern.v1();
217246
} catch (InvalidIndexNameException e) {
218-
// throws exception if the selector expression is invalid. Selector resolution does not complain about exclusions
219247
throw new ParsingException(e, source(ctx), e.getMessage());
220248
}
249+
250+
resolveAndValidateIndex(indexName, ctx, hasSeenStar);
251+
if (selectorString != null) {
252+
selectorString = selectorString.strip();
253+
try {
254+
// Ensures that the selector provided is one of the valid kinds.
255+
IndexNameExpressionResolver.SelectorResolver.validateIndexSelectorString(indexName, selectorString);
256+
} catch (InvalidIndexNameException e) {
257+
throw new ParsingException(e, source(ctx), e.getMessage());
258+
}
259+
}
260+
}
261+
262+
private static void resolveAndValidateIndex(String index, EsqlBaseParser.IndexPatternContext ctx, boolean hasSeenStar) {
221263
hasSeenStar = hasSeenStar || index.contains(WILDCARD);
222264
index = index.replace(WILDCARD, "").strip();
223265
if (index.isBlank()) {

0 commit comments

Comments
 (0)