Skip to content

Commit 3e4a786

Browse files
ESQL: Fix attribute only in full text function not found (#137395)
A verification exception is thrown if there is LOOKUP JOIN with a FULL TEXT FUNCTION on the LOOKUP INDEX. The field from the LOOKUP INDEX is only used in the full text function. There is KEEP that selects some fields, but not the field in question. The fix involves making sure that all the fields from the JoinOnCondition of the LOOKUP JOIN are collected during resolveFieldNames() to be used for field caps. Resolves #137396
1 parent 2e8cbc9 commit 3e4a786

File tree

6 files changed

+81
-0
lines changed

6 files changed

+81
-0
lines changed

docs/changelog/137395.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 137395
2+
summary: Fix attribute only in full text function not found
3+
area: ES|QL
4+
type: bug
5+
issues:
6+
- 137396

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,3 +1253,22 @@ FROM airports
12531253
airport_code:keyword | name:text | city:keyword | country:keyword | location:geo_point | city_boundary:geo_shape
12541254
LTN | London Luton | Luton | United Kingdom | POINT(-0.376227267397439 51.8802952570969) | POLYGON((-0.5059 51.9006, -0.4225 51.8545, -0.3499 51.8787, -0.3856 51.9157, -0.4191 51.9123, -0.4263 51.9267, -0.4857 51.9227, -0.4823 51.9078, -0.5059 51.9006))
12551255
;
1256+
1257+
lookupJoinExpressionOnRightFieldNotAnywhereElse
1258+
required_capability: join_lookup_v12
1259+
required_capability: lookup_join_with_full_text_function_bugfix
1260+
1261+
FROM multi_column_joinable
1262+
| RENAME id_int AS id_left
1263+
| LOOKUP JOIN multi_column_joinable_lookup ON id_int == id_left AND MATCH(other1, "beta")
1264+
| WHERE other2 IS NOT NULL
1265+
| KEEP id_left, name_str, extra1, other2
1266+
| SORT id_left, name_str, extra1, other2
1267+
;
1268+
1269+
warning:Line 3:3: evaluation of [LOOKUP JOIN multi_column_joinable_lookup ON id_int == id_left AND MATCH(other1, \"beta\")] failed, treating result as null. Only first 20 failures recorded.
1270+
warning:Line 3:3: java.lang.IllegalArgumentException: LOOKUP JOIN encountered multi-value
1271+
1272+
id_left:integer | name_str:keyword | extra1:keyword | other2:integer
1273+
1 | Alice | foo | 2000
1274+
;

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,6 +1423,10 @@ public enum Cap {
14231423
* to be applied to the lookup index used
14241424
*/
14251425
LOOKUP_JOIN_WITH_FULL_TEXT_FUNCTION,
1426+
/**
1427+
* Bugfix for lookup join with Full Text Function
1428+
*/
1429+
LOOKUP_JOIN_WITH_FULL_TEXT_FUNCTION_BUGFIX,
14261430
/**
14271431
* FORK with remote indices
14281432
*/

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/FieldNameUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ public static PreAnalysisResult resolveFieldNames(LogicalPlan parsed, boolean ha
163163
referencesBuilder.get().addAll(enrichRefs);
164164
} else if (p instanceof LookupJoin join) {
165165
joinRefs.addAll(join.config().leftFields());
166+
if (join.config().joinOnConditions() != null) {
167+
joinRefs.addAll(join.config().joinOnConditions().references());
168+
}
166169
if (keepRefs.isEmpty()) {
167170
// No KEEP commands after the JOIN, so we need to mark this index for "*" field resolution
168171
wildcardJoinIndices.add(((UnresolvedRelation) join.right()).indexPattern().indexPattern());

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTestUtils.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.esql.analysis;
99

10+
import org.elasticsearch.TransportVersion;
1011
import org.elasticsearch.index.IndexMode;
1112
import org.elasticsearch.inference.TaskType;
1213
import org.elasticsearch.test.ESTestCase;
@@ -156,6 +157,17 @@ public static LogicalPlan analyze(String query, Analyzer analyzer) {
156157
return analyzed;
157158
}
158159

160+
public static LogicalPlan analyze(String query, TransportVersion transportVersion) {
161+
Analyzer baseAnalyzer = expandedDefaultAnalyzer();
162+
if (baseAnalyzer.context() instanceof MutableAnalyzerContext mutableContext) {
163+
try (var restore = mutableContext.setTemporaryTransportVersionOnOrAfter(transportVersion)) {
164+
return analyze(query, baseAnalyzer);
165+
}
166+
} else {
167+
throw new UnsupportedOperationException("Analyzer Context is not mutable");
168+
}
169+
}
170+
159171
private static final Pattern indexFromPattern = Pattern.compile("(?i)FROM\\s+([\\w-]+)");
160172

161173
private static String indexFromQuery(String query) {

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5561,6 +5561,43 @@ public void testSubqueryWithFullTextFunctionInMainQuery() {
55615561
assertEquals("sample_data", subqueryIndex.indexPattern());
55625562
}
55635563

5564+
public void testLookupJoinOnFieldNotAnywhereElse() {
5565+
assumeTrue(
5566+
"requires LOOKUP JOIN ON boolean expression capability",
5567+
EsqlCapabilities.Cap.LOOKUP_JOIN_WITH_FULL_TEXT_FUNCTION_BUGFIX.isEnabled()
5568+
);
5569+
5570+
String query = "FROM test | LOOKUP JOIN languages_lookup "
5571+
+ "ON languages == language_code AND MATCH(language_name, \"English\")"
5572+
+ "| KEEP languages";
5573+
5574+
LogicalPlan analyzedPlan = analyze(query, Analyzer.ESQL_LOOKUP_JOIN_FULL_TEXT_FUNCTION);
5575+
5576+
Limit limit = as(analyzedPlan, Limit.class);
5577+
assertThat(limit.limit(), instanceOf(Literal.class));
5578+
assertEquals(1000, as(limit.limit(), Literal.class).value());
5579+
5580+
EsqlProject project = as(limit.child(), EsqlProject.class);
5581+
assertEquals(1, project.projections().size());
5582+
5583+
LookupJoin lookupJoin = as(project.child(), LookupJoin.class);
5584+
5585+
// Verify join condition contains MATCH function
5586+
assertThat(lookupJoin.config().joinOnConditions(), notNullValue());
5587+
Expression joinCondition = lookupJoin.config().joinOnConditions();
5588+
// Check that the join condition contains a MATCH function (it should be an AND expression)
5589+
assertThat(joinCondition.toString(), containsString("MATCH"));
5590+
5591+
// Verify left relation is test index
5592+
EsRelation leftRelation = as(lookupJoin.left(), EsRelation.class);
5593+
assertEquals("test", leftRelation.indexPattern());
5594+
5595+
// Verify right relation is languages_lookup with LOOKUP mode
5596+
EsRelation rightRelation = as(lookupJoin.right(), EsRelation.class);
5597+
assertEquals("languages_lookup", rightRelation.indexPattern());
5598+
assertEquals(IndexMode.LOOKUP, rightRelation.indexMode());
5599+
}
5600+
55645601
private void verifyNameAndTypeAndMultiTypeEsField(
55655602
String actualName,
55665603
DataType actualType,

0 commit comments

Comments
 (0)