Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fa962c4
Update grammar to rely on indexPattern instead of identifier in join …
idegtiarenko Jan 20, 2025
6049006
Update docs/changelog/120494.yaml
idegtiarenko Jan 21, 2025
610b3bc
initial test
idegtiarenko Jan 21, 2025
8675905
add join type
idegtiarenko Jan 21, 2025
2770dbb
upd
idegtiarenko Jan 21, 2025
58b2f27
upd
idegtiarenko Jan 21, 2025
bf9c556
cleanup
idegtiarenko Jan 21, 2025
a7a91b9
fix quotes usage
idegtiarenko Jan 21, 2025
318fd1d
inc lookup version
idegtiarenko Jan 21, 2025
60444a8
add pattern test cases
idegtiarenko Jan 21, 2025
62d53f9
Merge branch 'main' into es-10559
idegtiarenko Jan 21, 2025
a5f713f
Merge branch 'main' into es-10559
idegtiarenko Jan 22, 2025
2b642b9
prohibit * in join patterns
idegtiarenko Jan 22, 2025
52d98e6
Merge branch 'main' into es-10559
idegtiarenko Jan 22, 2025
7b38bec
random identifier generator
idegtiarenko Jan 22, 2025
2cc2643
use random identifier generator
idegtiarenko Jan 23, 2025
9529874
Merge branch 'main' into es-10559
idegtiarenko Jan 23, 2025
e7d794d
fix merge
idegtiarenko Jan 23, 2025
62ba2fe
add date math generation
idegtiarenko Jan 23, 2025
f562bc1
rename
idegtiarenko Jan 23, 2025
9e7f125
upd
idegtiarenko Jan 23, 2025
a9ecc24
Merge branch 'main' into es-10559
idegtiarenko Jan 23, 2025
4574fd1
fix new tests
idegtiarenko Jan 23, 2025
856a6ec
add IT with quotes
idegtiarenko Jan 23, 2025
0a39bb2
Merge branch 'main' into es-10559
idegtiarenko Jan 24, 2025
c1b399d
add missing capability
idegtiarenko Jan 24, 2025
1116eb7
Merge branch 'main' into es-10559
idegtiarenko Jan 24, 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
5 changes: 5 additions & 0 deletions docs/changelog/120494.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 120494
summary: Update grammar to rely on `indexPattern` instead of identifier in join target
area: ES|QL
type: enhancement
issues: []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add randomization to have the index specified with and without double quotes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to keep this test simple. All possible index name variations are covered in dedicated parser test.

Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ public void testLookupJoinIndexAllowed() throws Exception {

Response resp = runESQLCommand(
"metadata1_read2",
"ROW x = 40.0 | EVAL value = x | LOOKUP JOIN `lookup-user2` ON value | KEEP x, org"
"ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org"
);
assertOK(resp);
Map<String, Object> respMap = entityAsMap(resp);
Expand All @@ -564,7 +564,7 @@ public void testLookupJoinIndexAllowed() throws Exception {
assertThat(respMap.get("values"), equalTo(List.of(List.of(40.0, "sales"))));

// Alias, should find the index and the row
resp = runESQLCommand("alias_user1", "ROW x = 31.0 | EVAL value = x | LOOKUP JOIN `lookup-first-alias` ON value | KEEP x, org");
resp = runESQLCommand("alias_user1", "ROW x = 31.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org");
assertOK(resp);
respMap = entityAsMap(resp);
assertThat(
Expand All @@ -574,7 +574,7 @@ public void testLookupJoinIndexAllowed() throws Exception {
assertThat(respMap.get("values"), equalTo(List.of(List.of(31.0, "sales"))));

// Alias, for a row that's filtered out
resp = runESQLCommand("alias_user1", "ROW x = 123.0 | EVAL value = x | LOOKUP JOIN `lookup-first-alias` ON value | KEEP x, org");
resp = runESQLCommand("alias_user1", "ROW x = 123.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org");
assertOK(resp);
respMap = entityAsMap(resp);
assertThat(
Expand All @@ -592,21 +592,21 @@ public void testLookupJoinIndexForbidden() throws Exception {

var resp = expectThrows(
ResponseException.class,
() -> runESQLCommand("metadata1_read2", "FROM lookup-user2 | EVAL value = 10.0 | LOOKUP JOIN `lookup-user1` ON value | KEEP x")
() -> runESQLCommand("metadata1_read2", "FROM lookup-user2 | EVAL value = 10.0 | LOOKUP JOIN lookup-user1 ON value | KEEP x")
);
assertThat(resp.getMessage(), containsString("Unknown index [lookup-user1]"));
assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST));

resp = expectThrows(
ResponseException.class,
() -> runESQLCommand("metadata1_read2", "ROW x = 10.0 | EVAL value = x | LOOKUP JOIN `lookup-user1` ON value | KEEP x")
() -> runESQLCommand("metadata1_read2", "ROW x = 10.0 | EVAL value = x | LOOKUP JOIN lookup-user1 ON value | KEEP x")
);
assertThat(resp.getMessage(), containsString("Unknown index [lookup-user1]"));
assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST));

resp = expectThrows(
ResponseException.class,
() -> runESQLCommand("alias_user1", "ROW x = 10.0 | EVAL value = x | LOOKUP JOIN `lookup-user1` ON value | KEEP x")
() -> runESQLCommand("alias_user1", "ROW x = 10.0 | EVAL value = x | LOOKUP JOIN lookup-user1 ON value | KEEP x")
);
assertThat(resp.getMessage(), containsString("Unknown index [lookup-user1]"));
assertThat(resp.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST));
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugin/esql/src/main/antlr/EsqlBaseLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,10 @@ JOIN_AS : AS -> type(AS);
JOIN_ON : ON -> type(ON), popMode, pushMode(EXPRESSION_MODE);
USING : 'USING' -> popMode, pushMode(EXPRESSION_MODE);

JOIN_UNQUOTED_SOURCE: UNQUOTED_SOURCE -> type(UNQUOTED_SOURCE);
JOIN_QUOTED_SOURCE : QUOTED_STRING -> type(QUOTED_STRING);
JOIN_COLON : COLON -> type(COLON);

JOIN_UNQUOTED_IDENTIFER: UNQUOTED_IDENTIFIER -> type(UNQUOTED_IDENTIFIER);
JOIN_QUOTED_IDENTIFIER : QUOTED_IDENTIFIER -> type(QUOTED_IDENTIFIER);

Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugin/esql/src/main/antlr/EsqlBaseParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ joinCommand
;

joinTarget
: index=identifier (AS alias=identifier)?
: index=indexPattern (AS alias=identifier)?
;

joinCondition
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ public PlanFactory visitJoinCommand(EsqlBaseParser.JoinCommandContext ctx) {
var target = ctx.joinTarget();
UnresolvedRelation right = new UnresolvedRelation(
source(target),
new TableIdentifier(source(target.index), null, visitIdentifier(target.index)),
new TableIdentifier(source(target.index), null, visitIndexPattern(List.of(target.index))),
false,
emptyList(),
IndexMode.LOOKUP,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
import org.elasticsearch.xpack.esql.plan.logical.Rename;
import org.elasticsearch.xpack.esql.plan.logical.Row;
import org.elasticsearch.xpack.esql.plan.logical.UnresolvedRelation;
import org.elasticsearch.xpack.esql.plan.logical.join.JoinTypes;
import org.elasticsearch.xpack.esql.plan.logical.join.LookupJoin;

import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -2939,4 +2941,69 @@ public void testNamedFunctionArgumentWithUnsupportedNamedParameterTypes() {
);
}
}

public void testValidJoinPattern() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the index pattern makes sense for parser (vs grammar) validation however I would have expected for patterns to be rejected from the start instead of waiting to resolve them first.
That is LOOKUP JOIN foo works during parsing (but might fail at runtime if it's an alias pointing to multiple indices) however LOOKUP JOIN foo* fails at parsing since it's pattern (regardless of whether it gets resolved to 1 or more indices).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 2b642b9

var basePattern = randomIndexPatterns();
var joinPattern = randomIndexPattern();
var onField = randomIdentifier();
var type = randomFrom("", "LOOKUP ");

Comment on lines +2954 to +2955
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The empty string looks wrong - FROM idx | JOIN other_idx ON field is not supported, but by the test below, we parse this as a lookup join o.O

Consider removing the plain join, but it's also fine as-is due to this only testing the parsing itself.

var plan = statement("FROM " + basePattern + " | " + type + " JOIN " + joinPattern + " ON " + onField);

var join = as(plan, LookupJoin.class);
assertThat(as(join.left(), UnresolvedRelation.class).table().index(), equalTo(unquote(basePattern)));
assertThat(as(join.right(), UnresolvedRelation.class).table().index(), equalTo(unquote(joinPattern)));

var joinType = as(join.config().type(), JoinTypes.UsingJoinType.class);
assertThat(joinType.columns(), hasSize(1));
assertThat(as(joinType.columns().getFirst(), UnresolvedAttribute.class).name(), equalTo(onField));
assertThat(joinType.coreJoin().joinName(), equalTo("LEFT OUTER"));
}

private static String randomIndexPatterns() {
return maybeQuote(String.join(",", randomList(1, 5, StatementParserTests::randomIndexPattern)));
}

private static String randomIndexPattern() {
String pattern = maybeQuote(randomIndexIdentifier());
if (randomBoolean()) {
var cluster = randomIdentifier();
pattern = maybeQuote(cluster + ":" + pattern);
}
return pattern;
}

/**
* This generates random valid index, alias or pattern
*/
private static String randomIndexIdentifier() {
// https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html#indices-create-api-path-params
var validFirstCharacters = "abcdefghijklmnopqrstuvwxyz0123456789!'$^&";
var validCharacters = validFirstCharacters + "+-_.";

var index = new StringBuilder();
if (randomInt(9) == 0) {// hidden index
index.append('.');
}
index.append(randomCharacterFrom(validFirstCharacters));
for (int i = 0; i < randomIntBetween(1, 100); i++) {
index.append(randomCharacterFrom(validCharacters));
}
if (randomBoolean()) {// pattern
index.append('*');
}
return index.toString();
}

private static char randomCharacterFrom(String str) {
return str.charAt(randomInt(str.length() - 1));
}

private static String maybeQuote(String term) {
return randomBoolean() && term.contains("\"") == false ? "\"" + term + "\"" : term;
}

private static String unquote(String term) {
return term.replace("\"", "");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ basic:
- do:
esql.query:
body:
query: 'FROM test | SORT key | LOOKUP JOIN `test-lookup-1` ON key | LIMIT 3'
query: 'FROM test | SORT key | LOOKUP JOIN test-lookup-1 ON key | LIMIT 3'

- match: {columns.0.name: "key"}
- match: {columns.0.type: "long"}
Expand All @@ -96,7 +96,7 @@ non-lookup index:
- do:
esql.query:
body:
query: 'FROM test-lookup-1 | SORT key | LOOKUP JOIN `test` ON key | LIMIT 3'
query: 'FROM test-lookup-1 | SORT key | LOOKUP JOIN test ON key | LIMIT 3'
catch: "bad_request"

- match: { error.type: "verification_exception" }
Expand All @@ -107,7 +107,7 @@ alias:
- do:
esql.query:
body:
query: 'FROM test | SORT key | LOOKUP JOIN `test-lookup-alias` ON key | LIMIT 3'
query: 'FROM test | SORT key | LOOKUP JOIN test-lookup-alias ON key | LIMIT 3'

- match: {columns.0.name: "key"}
- match: {columns.0.type: "long"}
Expand All @@ -121,7 +121,7 @@ alias-repeated-alias:
- do:
esql.query:
body:
query: 'FROM test-lookup-alias | SORT key | LOOKUP JOIN `test-lookup-alias` ON key | LIMIT 3'
query: 'FROM test-lookup-alias | SORT key | LOOKUP JOIN test-lookup-alias ON key | LIMIT 3'

- match: {columns.0.name: "key"}
- match: {columns.0.type: "long"}
Expand All @@ -135,7 +135,7 @@ alias-repeated-index:
- do:
esql.query:
body:
query: 'FROM test-lookup-1 | SORT key | LOOKUP JOIN `test-lookup-alias` ON key | LIMIT 3'
query: 'FROM test-lookup-1 | SORT key | LOOKUP JOIN test-lookup-alias ON key | LIMIT 3'

- match: {columns.0.name: "key"}
- match: {columns.0.type: "long"}
Expand All @@ -149,7 +149,7 @@ alias-pattern-multiple:
- do:
esql.query:
body:
query: 'FROM test-lookup-1 | LOOKUP JOIN `test-lookup-alias-pattern-multiple` ON key'
query: 'FROM test-lookup-1 | LOOKUP JOIN test-lookup-alias-pattern-multiple ON key'
catch: "bad_request"

- match: { error.type: "verification_exception" }
Expand All @@ -160,7 +160,7 @@ alias-pattern-single:
- do:
esql.query:
body:
query: 'FROM test | SORT key | LOOKUP JOIN `test-lookup-alias-pattern-single` ON key | LIMIT 3'
query: 'FROM test | SORT key | LOOKUP JOIN test-lookup-alias-pattern-single ON key | LIMIT 3'

- match: {columns.0.name: "key"}
- match: {columns.0.type: "long"}
Expand Down