Skip to content

Commit d1d7302

Browse files
ES|QL: Add support for LOOKUP JOIN on aliases (#128519)
1 parent 186972c commit d1d7302

File tree

22 files changed

+394
-73
lines changed

22 files changed

+394
-73
lines changed

docs/changelog/128519.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 128519
2+
summary: Add support for LOOKUP JOIN on aliases
3+
area: ES|QL
4+
type: enhancement
5+
issues: []

server/src/main/java/org/elasticsearch/TransportVersions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ static TransportVersion def(int id) {
276276
public static final TransportVersion ESQL_LIMIT_ROW_SIZE = def(9_085_0_00);
277277
public static final TransportVersion ESQL_REGEX_MATCH_WITH_CASE_INSENSITIVITY = def(9_086_0_00);
278278
public static final TransportVersion IDP_CUSTOM_SAML_ATTRIBUTES = def(9_087_0_00);
279+
public static final TransportVersion JOIN_ON_ALIASES = def(9_088_0_00);
279280

280281
/*
281282
* STOP! READ THIS FIRST! No, really,

x-pack/plugin/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ tasks.named("yamlRestCompatTestTransform").configure({ task ->
133133
task.replaceValueInMatch("Size", 49, "Test flamegraph from test-events")
134134
task.skipTest("esql/90_non_indexed/fetch", "Temporary until backported")
135135
task.skipTest("esql/63_enrich_int_range/Invalid age as double", "TODO: require disable allow_partial_results")
136+
task.skipTest("esql/191_lookup_join_on_datastreams/data streams not supported in LOOKUP JOIN", "Added support for aliases in JOINs")
136137
})
137138

138139
tasks.named('yamlRestCompatTest').configure {

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/lookup/QueryList.java

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.elasticsearch.index.mapper.MappedFieldType;
3535
import org.elasticsearch.index.mapper.RangeFieldMapper;
3636
import org.elasticsearch.index.query.SearchExecutionContext;
37+
import org.elasticsearch.search.internal.AliasFilter;
3738

3839
import java.io.IOException;
3940
import java.io.UncheckedIOException;
@@ -46,6 +47,7 @@
4647
*/
4748
public abstract class QueryList {
4849
protected final SearchExecutionContext searchExecutionContext;
50+
protected final AliasFilter aliasFilter;
4951
protected final MappedFieldType field;
5052
protected final Block block;
5153
@Nullable
@@ -54,10 +56,12 @@ public abstract class QueryList {
5456
protected QueryList(
5557
MappedFieldType field,
5658
SearchExecutionContext searchExecutionContext,
59+
AliasFilter aliasFilter,
5760
Block block,
5861
OnlySingleValueParams onlySingleValueParams
5962
) {
6063
this.searchExecutionContext = searchExecutionContext;
64+
this.aliasFilter = aliasFilter;
6165
this.field = field;
6266
this.block = block;
6367
this.onlySingleValueParams = onlySingleValueParams;
@@ -74,7 +78,7 @@ int getPositionCount() {
7478
* Returns a copy of this query list that only returns queries for single-valued positions.
7579
* That is, it returns `null` queries for either multivalued or null positions.
7680
* <p>
77-
* Whenever a multi-value position is encountered, whether in the input block or in the queried index, a warning is emitted.
81+
* Whenever a multi-value position is encountered, whether in the input block or in the queried index, a warning is emitted.
7882
* </p>
7983
*/
8084
public abstract QueryList onlySingleValues(Warnings warnings, String multiValueWarningMessage);
@@ -93,6 +97,17 @@ final Query getQuery(int position) {
9397

9498
Query query = doGetQuery(position, firstValueIndex, valueCount);
9599

100+
if (aliasFilter != null && aliasFilter != AliasFilter.EMPTY) {
101+
BooleanQuery.Builder builder = new BooleanQuery.Builder();
102+
builder.add(query, BooleanClause.Occur.FILTER);
103+
try {
104+
builder.add(aliasFilter.getQueryBuilder().toQuery(searchExecutionContext), BooleanClause.Occur.FILTER);
105+
query = builder.build();
106+
} catch (IOException e) {
107+
throw new UncheckedIOException("Error while building query for alias filter", e);
108+
}
109+
}
110+
96111
if (onlySingleValueParams != null) {
97112
query = wrapSingleValueQuery(query);
98113
}
@@ -138,7 +153,12 @@ private Query wrapSingleValueQuery(Query query) {
138153
* using only the {@link ElementType} of the {@link Block} to determine the
139154
* query.
140155
*/
141-
public static QueryList rawTermQueryList(MappedFieldType field, SearchExecutionContext searchExecutionContext, Block block) {
156+
public static QueryList rawTermQueryList(
157+
MappedFieldType field,
158+
SearchExecutionContext searchExecutionContext,
159+
AliasFilter aliasFilter,
160+
Block block
161+
) {
142162
IntFunction<Object> blockToJavaObject = switch (block.elementType()) {
143163
case BOOLEAN -> {
144164
BooleanBlock booleanBlock = (BooleanBlock) block;
@@ -170,17 +190,22 @@ public static QueryList rawTermQueryList(MappedFieldType field, SearchExecutionC
170190
case AGGREGATE_METRIC_DOUBLE -> throw new IllegalArgumentException("can't read values from [aggregate metric double] block");
171191
case UNKNOWN -> throw new IllegalArgumentException("can't read values from [" + block + "]");
172192
};
173-
return new TermQueryList(field, searchExecutionContext, block, null, blockToJavaObject);
193+
return new TermQueryList(field, searchExecutionContext, aliasFilter, block, null, blockToJavaObject);
174194
}
175195

176196
/**
177197
* Returns a list of term queries for the given field and the input block of
178198
* {@code ip} field values.
179199
*/
180-
public static QueryList ipTermQueryList(MappedFieldType field, SearchExecutionContext searchExecutionContext, BytesRefBlock block) {
200+
public static QueryList ipTermQueryList(
201+
MappedFieldType field,
202+
SearchExecutionContext searchExecutionContext,
203+
AliasFilter aliasFilter,
204+
BytesRefBlock block
205+
) {
181206
BytesRef scratch = new BytesRef();
182207
byte[] ipBytes = new byte[InetAddressPoint.BYTES];
183-
return new TermQueryList(field, searchExecutionContext, block, null, offset -> {
208+
return new TermQueryList(field, searchExecutionContext, aliasFilter, block, null, offset -> {
184209
final var bytes = block.getBytesRef(offset, scratch);
185210
if (ipBytes.length != bytes.length) {
186211
// Lucene only support 16-byte IP addresses, even IPv4 is encoded in 16 bytes
@@ -195,10 +220,16 @@ public static QueryList ipTermQueryList(MappedFieldType field, SearchExecutionCo
195220
* Returns a list of term queries for the given field and the input block of
196221
* {@code date} field values.
197222
*/
198-
public static QueryList dateTermQueryList(MappedFieldType field, SearchExecutionContext searchExecutionContext, LongBlock block) {
223+
public static QueryList dateTermQueryList(
224+
MappedFieldType field,
225+
SearchExecutionContext searchExecutionContext,
226+
AliasFilter aliasFilter,
227+
LongBlock block
228+
) {
199229
return new TermQueryList(
200230
field,
201231
searchExecutionContext,
232+
aliasFilter,
202233
block,
203234
null,
204235
field instanceof RangeFieldMapper.RangeFieldType rangeFieldType
@@ -210,8 +241,13 @@ public static QueryList dateTermQueryList(MappedFieldType field, SearchExecution
210241
/**
211242
* Returns a list of geo_shape queries for the given field and the input block.
212243
*/
213-
public static QueryList geoShapeQueryList(MappedFieldType field, SearchExecutionContext searchExecutionContext, Block block) {
214-
return new GeoShapeQueryList(field, searchExecutionContext, block, null);
244+
public static QueryList geoShapeQueryList(
245+
MappedFieldType field,
246+
SearchExecutionContext searchExecutionContext,
247+
AliasFilter aliasFilter,
248+
Block block
249+
) {
250+
return new GeoShapeQueryList(field, searchExecutionContext, aliasFilter, block, null);
215251
}
216252

217253
private static class TermQueryList extends QueryList {
@@ -220,11 +256,12 @@ private static class TermQueryList extends QueryList {
220256
private TermQueryList(
221257
MappedFieldType field,
222258
SearchExecutionContext searchExecutionContext,
259+
AliasFilter aliasFilter,
223260
Block block,
224261
OnlySingleValueParams onlySingleValueParams,
225262
IntFunction<Object> blockValueReader
226263
) {
227-
super(field, searchExecutionContext, block, onlySingleValueParams);
264+
super(field, searchExecutionContext, aliasFilter, block, onlySingleValueParams);
228265
this.blockValueReader = blockValueReader;
229266
}
230267

@@ -233,6 +270,7 @@ public TermQueryList onlySingleValues(Warnings warnings, String multiValueWarnin
233270
return new TermQueryList(
234271
field,
235272
searchExecutionContext,
273+
aliasFilter,
236274
block,
237275
new OnlySingleValueParams(warnings, multiValueWarningMessage),
238276
blockValueReader
@@ -264,10 +302,11 @@ private static class GeoShapeQueryList extends QueryList {
264302
private GeoShapeQueryList(
265303
MappedFieldType field,
266304
SearchExecutionContext searchExecutionContext,
305+
AliasFilter aliasFilter,
267306
Block block,
268307
OnlySingleValueParams onlySingleValueParams
269308
) {
270-
super(field, searchExecutionContext, block, onlySingleValueParams);
309+
super(field, searchExecutionContext, aliasFilter, block, onlySingleValueParams);
271310

272311
this.blockValueReader = blockToGeometry(block);
273312
this.shapeQuery = shapeQuery();
@@ -278,6 +317,7 @@ public GeoShapeQueryList onlySingleValues(Warnings warnings, String multiValueWa
278317
return new GeoShapeQueryList(
279318
field,
280319
searchExecutionContext,
320+
aliasFilter,
281321
block,
282322
new OnlySingleValueParams(warnings, multiValueWarningMessage)
283323
);

x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/lookup/EnrichQuerySourceOperatorTests.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.elasticsearch.index.mapper.MappedFieldType;
4242
import org.elasticsearch.index.query.SearchExecutionContext;
4343
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
44+
import org.elasticsearch.search.internal.AliasFilter;
4445
import org.elasticsearch.test.ESTestCase;
4546
import org.junit.After;
4647
import org.junit.Before;
@@ -83,7 +84,7 @@ public void testQueries() throws Exception {
8384
var inputTerms = makeTermsBlock(List.of(List.of("b2"), List.of("c1", "a2"), List.of("z2"), List.of(), List.of("a3"), List.of()))
8485
) {
8586
MappedFieldType uidField = new KeywordFieldMapper.KeywordFieldType("uid");
86-
QueryList queryList = QueryList.rawTermQueryList(uidField, directoryData.searchExecutionContext, inputTerms);
87+
QueryList queryList = QueryList.rawTermQueryList(uidField, directoryData.searchExecutionContext, AliasFilter.EMPTY, inputTerms);
8788
assertThat(queryList.getPositionCount(), equalTo(6));
8889
assertThat(queryList.getQuery(0), equalTo(new TermQuery(new Term("uid", new BytesRef("b2")))));
8990
assertThat(queryList.getQuery(1), equalTo(new TermInSetQuery("uid", List.of(new BytesRef("c1"), new BytesRef("a2")))));
@@ -153,7 +154,12 @@ public void testRandomMatchQueries() throws Exception {
153154
}).toList();
154155

155156
try (var directoryData = makeDirectoryWith(directoryTermsList); var inputTerms = makeTermsBlock(inputTermsList)) {
156-
var queryList = QueryList.rawTermQueryList(directoryData.field, directoryData.searchExecutionContext, inputTerms);
157+
var queryList = QueryList.rawTermQueryList(
158+
directoryData.field,
159+
directoryData.searchExecutionContext,
160+
AliasFilter.EMPTY,
161+
inputTerms
162+
);
157163
int maxPageSize = between(1, 256);
158164
EnrichQuerySourceOperator queryOperator = new EnrichQuerySourceOperator(
159165
blockFactory,
@@ -190,8 +196,12 @@ public void testQueries_OnlySingleValues() throws Exception {
190196
List.of(List.of("b2"), List.of("c1", "a2"), List.of("z2"), List.of(), List.of("a3"), List.of("a3", "a2", "z2", "xx"))
191197
)
192198
) {
193-
QueryList queryList = QueryList.rawTermQueryList(directoryData.field, directoryData.searchExecutionContext, inputTerms)
194-
.onlySingleValues(warnings(), "multi-value found");
199+
QueryList queryList = QueryList.rawTermQueryList(
200+
directoryData.field,
201+
directoryData.searchExecutionContext,
202+
AliasFilter.EMPTY,
203+
inputTerms
204+
).onlySingleValues(warnings(), "multi-value found");
195205
// pos -> terms -> docs
196206
// -----------------------------
197207
// 0 -> [b2] -> []

0 commit comments

Comments
 (0)