Skip to content

Commit 5c70d51

Browse files
Address review comments and don't break indices into its constituents
1. Wrapped `splitIndexName()` in try catch and ensured it throws parse exception. 2. Relaxed the validation to allow excluding garbage. 3. Validation no longer breaks patterns on pipe char as it's an invalid char.
1 parent 7da29d8 commit 5c70d51

File tree

2 files changed

+60
-97
lines changed

2 files changed

+60
-97
lines changed

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

Lines changed: 54 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
import org.elasticsearch.core.Tuple;
1616
import org.elasticsearch.indices.InvalidIndexNameException;
1717
import org.elasticsearch.transport.RemoteClusterService;
18+
import org.elasticsearch.xpack.esql.core.util.Holder;
1819
import org.elasticsearch.xpack.esql.parser.EsqlBaseParser.IdentifierContext;
1920
import org.elasticsearch.xpack.esql.parser.EsqlBaseParser.IndexStringContext;
2021

2122
import java.util.ArrayList;
22-
import java.util.Arrays;
2323
import java.util.List;
2424

2525
import static org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.SelectorResolver.SELECTOR_SEPARATOR;
@@ -88,12 +88,14 @@ public String visitSelectorString(EsqlBaseParser.SelectorStringContext ctx) {
8888

8989
public String visitIndexPattern(List<EsqlBaseParser.IndexPatternContext> ctx) {
9090
List<String> patterns = new ArrayList<>(ctx.size());
91+
Holder<Boolean> hasSeenStar = new Holder<>(false);
9192
ctx.forEach(c -> {
9293
String indexPattern = visitIndexString(c.indexString());
9394
String clusterString = visitClusterString(c.clusterString());
9495
String selectorString = visitSelectorString(c.selectorString());
9596

96-
validateClusterAndIndexPatterns(indexPattern, c, clusterString, selectorString);
97+
hasSeenStar.set(hasSeenStar.get() || indexPattern.contains(WILDCARD));
98+
validateClusterAndIndexPatterns(indexPattern, c, clusterString, selectorString, hasSeenStar.get());
9799
patterns.add(reassembleIndexName(clusterString, indexPattern, selectorString));
98100
});
99101
return Strings.collectionToDelimitedString(patterns, ",");
@@ -132,15 +134,15 @@ private static void validateClusterAndIndexPatterns(
132134
String indexPattern,
133135
EsqlBaseParser.IndexPatternContext ctx,
134136
String clusterString,
135-
String selectorString
137+
String selectorString,
138+
boolean hasSeenStar
136139
) {
137140
// multiple index names can be in the same double quote, e.g. indexPattern = "idx1, *, -idx2"
138141
String[] patterns = indexPattern.split(",");
139142
boolean isFirstPattern = true;
140143

141144
for (String pattern : patterns) {
142145
pattern = pattern.strip();
143-
String[] indices = new String[] { pattern };
144146

145147
/*
146148
* Just because there was no clusterString before this index pattern does not mean that the indices
@@ -162,30 +164,21 @@ private static void validateClusterAndIndexPatterns(
162164
}
163165

164166
// {cluster_alias, indexName}
165-
String[] clusterAliasAndIndex = splitIndexName(pattern);
166-
clusterString = clusterAliasAndIndex[0];
167-
indices[0] = clusterAliasAndIndex[1];
167+
String[] clusterAliasAndIndex;
168+
try {
169+
clusterAliasAndIndex = splitIndexName(pattern);
170+
} catch (IllegalArgumentException e) {
171+
throw new ParsingException(e, source(ctx), e.getMessage());
172+
}
168173

169-
/*
170-
* What if the pattern is index1|index2? We cannot split at the pipe char blindly as that'd mess with
171-
* logstash-like examples. We only split this way if an index is associated with a remote cluster.
172-
*/
173-
indices = Arrays.stream(indices)
174-
.map(IdentifierBuilder::breakPatternIntoIndices)
175-
.flatMap(Arrays::stream)
176-
.toArray(String[]::new);
174+
clusterString = clusterAliasAndIndex[0];
175+
pattern = clusterAliasAndIndex[1];
177176
} else if (clusterString != null) {
178177
// This is not a remote index pattern and the cluster string preceding this quoted pattern
179178
// cannot be associated with it.
180179
if (isFirstPattern == false) {
181180
clusterString = null;
182181
}
183-
184-
// Cluster alias was prefixed to the pattern and did not occur within the pattern.
185-
indices = Arrays.stream(indices)
186-
.map(IdentifierBuilder::breakPatternIntoIndices)
187-
.flatMap(Arrays::stream)
188-
.toArray(String[]::new);
189182
}
190183

191184
if (clusterString != null) {
@@ -195,7 +188,7 @@ private static void validateClusterAndIndexPatterns(
195188
validateClusterString(clusterString, ctx);
196189
}
197190

198-
validateIndicesForCluster(clusterString, indices, ctx);
191+
validateIndexForCluster(clusterString, pattern, ctx, hasSeenStar);
199192
if (selectorString != null) {
200193
try {
201194
// Ensures that the selector provided is one of the valid kinds
@@ -209,85 +202,53 @@ private static void validateClusterAndIndexPatterns(
209202
}
210203
}
211204

212-
private static void validateIndicesForCluster(String clusterString, String[] indices, EsqlBaseParser.IndexPatternContext ctx) {
213-
for (String index : indices) {
214-
// Strip spaces off first because validation checks are not written to handle them
215-
index = index.strip();
216-
217-
try {
218-
Tuple<String, String> splitPattern = IndexNameExpressionResolver.splitSelectorExpression(index);
219-
if (splitPattern.v2() != null && clusterString != null) {
220-
throwOnMixingSelectorWithCluster(reassembleIndexName(clusterString, splitPattern.v1(), splitPattern.v2()), ctx);
221-
}
205+
private static void validateIndexForCluster(
206+
String clusterString,
207+
String index,
208+
EsqlBaseParser.IndexPatternContext ctx,
209+
boolean hasSeenStar
210+
) {
211+
// Strip spaces off first because validation checks are not written to handle them
212+
index = index.strip();
222213

223-
index = splitPattern.v1();
224-
} catch (InvalidIndexNameException e) {
225-
// throws exception if the selector expression is invalid. Selector resolution does not complain about exclusions
226-
throw new ParsingException(e, source(ctx), e.getMessage());
227-
}
228-
var hasSeenStar = index.contains(WILDCARD);
229-
index = index.replace(WILDCARD, "").strip();
230-
if (index.isBlank()) {
231-
continue;
232-
}
233-
var hasExclusion = index.startsWith(EXCLUSION);
234-
index = removeExclusion(index);
235-
String tempName;
236-
try {
237-
// remove the exclusion outside of <>, from index names with DateMath expression,
238-
// e.g. -<-logstash-{now/d}> becomes <-logstash-{now/d}> before calling resolveDateMathExpression
239-
tempName = IndexNameExpressionResolver.resolveDateMathExpression(index);
240-
} catch (ElasticsearchParseException e) {
241-
// throws exception if the DateMath expression is invalid, resolveDateMathExpression does not complain about exclusions
242-
throw new ParsingException(e, source(ctx), e.getMessage());
243-
}
244-
hasExclusion = tempName.startsWith(EXCLUSION) || hasExclusion;
245-
index = tempName.equals(index) ? index : removeExclusion(tempName);
246-
try {
247-
MetadataCreateIndexService.validateIndexOrAliasName(index, InvalidIndexNameException::new);
248-
} catch (InvalidIndexNameException e) {
249-
// ignore invalid index name if it has exclusions and there is an index with wildcard before it
250-
if (hasSeenStar && hasExclusion) {
251-
continue;
252-
}
253-
throw new ParsingException(e, source(ctx), e.getMessage());
214+
try {
215+
Tuple<String, String> splitPattern = IndexNameExpressionResolver.splitSelectorExpression(index);
216+
if (splitPattern.v2() != null && clusterString != null) {
217+
throwOnMixingSelectorWithCluster(reassembleIndexName(clusterString, splitPattern.v1(), splitPattern.v2()), ctx);
254218
}
255219

220+
index = splitPattern.v1();
221+
} catch (InvalidIndexNameException e) {
222+
// throws exception if the selector expression is invalid. Selector resolution does not complain about exclusions
223+
throw new ParsingException(e, source(ctx), e.getMessage());
256224
}
257-
}
258-
259-
private static String[] breakPatternIntoIndices(String pattern) {
260-
if (pattern.codePoints().anyMatch(ch -> ch == ',')) {
261-
throw new IllegalArgumentException("Found grouped index patterns, expecting a single pattern");
225+
hasSeenStar = hasSeenStar || index.contains(WILDCARD);
226+
index = index.replace(WILDCARD, "").strip();
227+
if (index.isBlank()) {
228+
return;
262229
}
263-
264-
// Fast path: if there's no pipe char, no point in attempting to break down the string.
265-
if (pattern.contains("|") == false) {
266-
return new String[] { pattern };
230+
var hasExclusion = index.startsWith(EXCLUSION);
231+
index = removeExclusion(index);
232+
String tempName;
233+
try {
234+
// remove the exclusion outside of <>, from index names with DateMath expression,
235+
// e.g. -<-logstash-{now/d}> becomes <-logstash-{now/d}> before calling resolveDateMathExpression
236+
tempName = IndexNameExpressionResolver.resolveDateMathExpression(index);
237+
} catch (ElasticsearchParseException e) {
238+
// throws exception if the DateMath expression is invalid, resolveDateMathExpression does not complain about exclusions
239+
throw new ParsingException(e, source(ctx), e.getMessage());
267240
}
268-
269-
var indices = new ArrayList<String>();
270-
var sb = new StringBuilder();
271-
var inDateMathExpr = false;
272-
for (int i = 0; i < pattern.length(); i++) {
273-
char c = pattern.charAt(i);
274-
sb.append(c);
275-
if (c == '<') {
276-
inDateMathExpr = true;
277-
} else if (c == '>') {
278-
inDateMathExpr = false;
279-
} else if (c == '|' && inDateMathExpr == false) {
280-
sb.deleteCharAt(sb.length() - 1);
281-
indices.add(sb.toString());
282-
sb.setLength(0);
241+
hasExclusion = tempName.startsWith(EXCLUSION) || hasExclusion;
242+
index = tempName.equals(index) ? index : removeExclusion(tempName);
243+
try {
244+
MetadataCreateIndexService.validateIndexOrAliasName(index, InvalidIndexNameException::new);
245+
} catch (InvalidIndexNameException e) {
246+
// ignore invalid index name if it has exclusions and there is an index with wildcard before it
247+
if (hasSeenStar && hasExclusion) {
248+
return;
283249
}
250+
throw new ParsingException(e, source(ctx), e.getMessage());
284251
}
285-
286-
if (sb.isEmpty() == false) {
287-
indices.add(sb.toString());
288-
}
289-
290-
return indices.toArray(new String[0]);
291252
}
292253

293254
private static String removeExclusion(String indexPattern) {

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -484,14 +484,14 @@ public void testStringAsIndexPattern() {
484484
assertStringAsIndexPattern("`backtick`,``multiple`back``ticks```", command + " `backtick`, ``multiple`back``ticks```");
485485
assertStringAsIndexPattern("test,metadata,metaata,.metadata", command + " test,\"metadata\", metaata, .metadata");
486486
assertStringAsIndexPattern(".dot", command + " .dot");
487-
assertStringAsIndexPattern("cluster:index|pattern", command + " cluster:\"index|pattern\"");
488-
assertStringAsIndexPattern("*:index|pattern", command + " \"*:index|pattern\"");
489487
clusterAndIndexAsIndexPattern(command, "cluster:index");
490488
clusterAndIndexAsIndexPattern(command, "cluster:.index");
491489
clusterAndIndexAsIndexPattern(command, "cluster*:index*");
492490
clusterAndIndexAsIndexPattern(command, "cluster*:*");
493491
clusterAndIndexAsIndexPattern(command, "*:index*");
494492
clusterAndIndexAsIndexPattern(command, "*:*");
493+
expectError("FROM \"cluster:index|pattern\"", "Invalid index name [index|pattern], must not contain the following characters");
494+
expectError("FROM *:\"index|pattern\"", "Invalid index name [index|pattern], must not contain the following characters");
495495
if (EsqlCapabilities.Cap.INDEX_COMPONENT_SELECTORS.isEnabled()) {
496496
assertStringAsIndexPattern("foo::data", command + " foo::data");
497497
assertStringAsIndexPattern("foo::failures", command + " foo::failures");
@@ -692,6 +692,10 @@ public void testInvalidCharacterInIndexPattern() {
692692
}
693693
lineNumber = command.contains("FROM") ? "line 1:9: " : "line 1:12: ";
694694
String indexStarLineNumber = command.contains("FROM") ? "line 1:14: " : "line 1:17: ";
695+
clustersAndIndices(command, "*", "-index#pattern");
696+
clustersAndIndices(command, "index*", "-index#pattern");
697+
clustersAndIndices(command, "*", "-<--logstash-{now/M{yyyy.MM}}>");
698+
clustersAndIndices(command, "index*", "-<--logstash#-{now/M{yyyy.MM}}>");
695699
expectInvalidIndexNameErrorWithLineNumber(command, "*, index#pattern", lineNumber, "index#pattern", "must not contain '#'");
696700
expectInvalidIndexNameErrorWithLineNumber(
697701
command,
@@ -782,8 +786,6 @@ public void testInvalidQuotingAsFromIndexPattern() {
782786

783787
expectError("FROM \"\"\"foo\"\"\"bar\"\"\"", ": mismatched input 'bar' expecting {<EOF>, '|', ',', 'metadata'}");
784788
expectError("FROM \"\"\"foo\"\"\"\"\"\"bar\"\"\"", ": mismatched input '\"bar\"' expecting {<EOF>, '|', ',', 'metadata'}");
785-
expectError("FROM remote:\"foo:bar\"", "Index pattern [foo:bar] contains a cluster alias despite specifying one [remote]");
786-
expectError("FROM \"remote:foo:bar:baz\"", "Invalid index name [foo:bar:baz], must not contain ':'");
787789
}
788790

789791
public void testInvalidQuotingAsLookupIndexPattern() {

0 commit comments

Comments
 (0)