Skip to content

Commit ed34ca4

Browse files
Serialization changes for JoinConfig, comments
1 parent 7354a2d commit ed34ca4

File tree

4 files changed

+73
-26
lines changed

4 files changed

+73
-26
lines changed

x-pack/plugin/esql/qa/testFixtures/src/main/resources/lookup-join-expression.csv-spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ emp_no:integer | language_code_left:integer | language_name:keyword
710710

711711
lookupJoinExpressionWithPushableFilterOnRight
712712
required_capability: join_lookup_v12
713-
required_capability: lookup_join_on_multiple_fields
713+
required_capability: lookup_join_on_boolean_expression
714714

715715
FROM multi_column_joinable
716716
| RENAME id_int AS id_left, is_active_bool AS is_active_left

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/BinaryComparisonQueryList.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@
2828
import java.util.List;
2929
import java.util.function.IntFunction;
3030

31+
/**
32+
* A {@link QueryList} that generates a query for a binary comparison.
33+
* This class is used in the context of an expression based lookup join,
34+
* where we need to generate a query for each row of the left dataset.
35+
* The query is then used to fetch the matching rows from the right dataset.
36+
* The query is a binary comparison between a field from the right dataset and a value from the left dataset.
37+
* The field is on the left side of the comparison and the value is on the right side.
38+
* The value is extracted from a block at a given position.
39+
* The comparison is then translated to a Lucene query.
40+
* If the comparison cannot be translated, an exception is thrown.
41+
* This class is used in conjunction with {@link ExpressionQueryList} to generate the final query for the lookup join.
42+
*/
3143
public class BinaryComparisonQueryList extends QueryList {
3244
private final EsqlBinaryComparison binaryComparison;
3345
private final IntFunction<Object> blockValueReader;

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/enrich/ExpressionQueryList.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
* Each query in the resulting query will be a conjunction of all queries from the input lists at the same position.
4545
* In addition, we support an optional pre-join filter that will be applied to all queries if it is pushable.
4646
* If the pre-join filter cannot be pushed down to Lucene, it will be ignored.
47+
* This class is used in the context of a lookup join, where we need to generate a query for each row of the left dataset.
48+
* The query is then used to fetch the matching rows from the right dataset.
49+
* The class supports two types of joins:
50+
* 1. Field-based join: The join conditions are based on the equality of fields from the left and right datasets.
51+
* 2. Expression-based join: The join conditions are based on a complex expression that can involve multiple fields and operators.
4752
*/
4853
public class ExpressionQueryList implements LookupEnrichQueryGenerator {
4954
private final List<QueryList> queryLists;
@@ -64,6 +69,20 @@ private ExpressionQueryList(
6469
buildPreJoinFilter(rightPreJoinPlan, clusterService);
6570
}
6671

72+
/**
73+
* Creates a new {@link ExpressionQueryList} for a field-based join.
74+
* A field-based join is a join where the join conditions are based on the equality of fields from the left and right datasets.
75+
* For example | LOOKUP JOIN on field1, field2, field3
76+
* The query lists are generated from the join conditions.
77+
* The pre-join filter is an optional filter that is applied to the right dataset before the join.
78+
* @param queryLists The list of query lists that will be combined.
79+
* @param context The search execution context.
80+
* @param rightPreJoinPlan The physical plan for the right side of the join.
81+
* @param clusterService The cluster service.
82+
* @param aliasFilter The alias filter.
83+
* @return A new {@link ExpressionQueryList} for a field-based join.
84+
* @throws IllegalArgumentException if the number of query lists is less than 2 and there is no pre-join filter.
85+
*/
6786
public static ExpressionQueryList fieldBasedJoin(
6887
List<QueryList> queryLists,
6988
SearchExecutionContext context,
@@ -77,6 +96,22 @@ public static ExpressionQueryList fieldBasedJoin(
7796
return new ExpressionQueryList(queryLists, context, rightPreJoinPlan, clusterService, aliasFilter);
7897
}
7998

99+
/**
100+
* Creates a new {@link ExpressionQueryList} for an expression-based join.
101+
* An expression-based join is a join where the join conditions are based on a complex expression
102+
* that can involve multiple fields and operators.
103+
* Example | LOOKUP JOIN on left_field > right_field AND left_field2 == right_field2
104+
* The query lists are generated from the join conditions.
105+
* The pre-join filter is an optional filter that is applied to the right dataset before the join.
106+
* @param context The search execution context.
107+
* @param rightPreJoinPlan The physical plan for the right side of the join.
108+
* @param clusterService The cluster service.
109+
* @param request The transport request.
110+
* @param aliasFilter The alias filter.
111+
* @param warnings The warnings.
112+
* @return A new {@link ExpressionQueryList} for an expression-based join.
113+
* @throws IllegalStateException if the join conditions are null.
114+
*/
80115
public static ExpressionQueryList expressionBasedJoin(
81116
SearchExecutionContext context,
82117
PhysicalPlan rightPreJoinPlan,
@@ -207,6 +242,13 @@ private void buildPreJoinFilter(PhysicalPlan rightPreJoinPlan, ClusterService cl
207242
}
208243
}
209244

245+
/**
246+
* Returns the query at the given position.
247+
* The query is a conjunction of all queries from the input lists at the same position.
248+
* If a pre-join filter exists, it is also added to the query.
249+
* @param position The position of the query to return.
250+
* @return The query at the given position, or null if any of the match fields are null.
251+
*/
210252
@Override
211253
public Query getQuery(int position) {
212254
BooleanQuery.Builder builder = new BooleanQuery.Builder();
@@ -226,6 +268,12 @@ public Query getQuery(int position) {
226268
return builder.build();
227269
}
228270

271+
/**
272+
* Returns the number of positions in the query list.
273+
* The number of positions is the same for all query lists.
274+
* @return The number of positions in the query list.
275+
* @throws IllegalArgumentException if the query lists have different position counts.
276+
*/
229277
@Override
230278
public int getPositionCount() {
231279
int positionCount = queryLists.get(0).getPositionCount();

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/join/JoinConfig.java

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,31 +30,17 @@ public record JoinConfig(JoinType type, List<Attribute> leftFields, List<Attribu
3030
implements
3131
Writeable {
3232

33-
/**
34-
* Legacy constructor that included the match fields, which were always the left fields.
35-
* They are kept here for serialization compatibility, but are not used anymore.
36-
*/
37-
// TODO: Remove
38-
@Deprecated(forRemoval = true)
39-
private JoinConfig(
40-
JoinType type,
41-
List<Attribute> matchFields,
42-
List<Attribute> leftFields,
43-
List<Attribute> rightFields,
44-
Expression joinOnConditions
45-
) {
46-
this(type, leftFields, rightFields, joinOnConditions);
33+
public JoinConfig(StreamInput in) throws IOException {
34+
this(JoinTypes.readFrom(in), readLeftFields(in), in.readNamedWriteableCollectionAsList(Attribute.class), readJoinConditions(in));
4735
}
4836

49-
public JoinConfig(StreamInput in) throws IOException {
50-
this(
51-
JoinTypes.readFrom(in),
52-
// TODO we read the match fields for legacy reasons, they are not used anymore.
53-
in.readNamedWriteableCollectionAsList(Attribute.class),
54-
in.readNamedWriteableCollectionAsList(Attribute.class),
55-
in.readNamedWriteableCollectionAsList(Attribute.class),
56-
readJoinConditions(in)
57-
);
37+
private static List<Attribute> readLeftFields(StreamInput in) throws IOException {
38+
if (in.getTransportVersion().onOrAfter(TransportVersions.ESQL_LOOKUP_JOIN_ON_EXPRESSION) == false) {
39+
// For BWC, the left fields were written twice (once as match fields)
40+
// We read the first set and ignore them.
41+
in.readNamedWriteableCollectionAsList(Attribute.class);
42+
}
43+
return in.readNamedWriteableCollectionAsList(Attribute.class);
5844
}
5945

6046
private static Expression readJoinConditions(StreamInput in) throws IOException {
@@ -67,8 +53,9 @@ private static Expression readJoinConditions(StreamInput in) throws IOException
6753
@Override
6854
public void writeTo(StreamOutput out) throws IOException {
6955
type.writeTo(out);
70-
// TODO we write the match fields for legacy reasons, they used to always be the left fields.
71-
out.writeNamedWriteableCollection(leftFields);
56+
if (out.getTransportVersion().onOrAfter(TransportVersions.ESQL_LOOKUP_JOIN_ON_EXPRESSION) == false) {
57+
out.writeNamedWriteableCollection(leftFields);
58+
}
7259
out.writeNamedWriteableCollection(leftFields);
7360
out.writeNamedWriteableCollection(rightFields);
7461
if (out.getTransportVersion().onOrAfter(TransportVersions.ESQL_LOOKUP_JOIN_ON_EXPRESSION)) {

0 commit comments

Comments
 (0)