Skip to content

Commit e7ef641

Browse files
authored
Merge branch 'main' into lucene_snapshot_10_1
2 parents 769bdcd + 8c3d9e1 commit e7ef641

File tree

45 files changed

+822
-321
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+822
-321
lines changed

docs/README.asciidoc

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,15 @@ used for its modifiers:
157157
* `// TESTRESPONSE[skip:reason]`: Skip the assertions specified by this
158158
response.
159159
* `// TESTSETUP`: Marks this snippet as the "setup" for all other snippets in
160-
this file. This is a somewhat natural way of structuring documentation. You
161-
say "this is the data we use to explain this feature" then you add the
162-
snippet that you mark `// TESTSETUP` and then every snippet will turn into
163-
a test that runs the setup snippet first. See the "painless" docs for a file
164-
that puts this to good use. This is fairly similar to `// TEST[setup:name]`
165-
but rather than the setup defined in `docs/build.gradle` the setup is defined
166-
right in the documentation file. In general, we should prefer `// TESTSETUP`
167-
over `// TEST[setup:name]` because it makes it more clear what steps have to
168-
be taken before the examples will work. Tip: `// TESTSETUP` can only be used
169-
on the first snippet of a document.
160+
this file. In order to enhance clarity and simplify understanding for readers,
161+
a straightforward approach involves marking the first snippet in the documentation file with the
162+
`// TESTSETUP` marker. By doing so, it clearly indicates that this particular snippet serves as the setup
163+
or preparation step for all subsequent snippets in the file.
164+
This helps in explaining the necessary steps that need to be executed before running the examples.
165+
Unlike the alternative convention `// TEST[setup:name]`, which relies on a setup defined in a separate file,
166+
this convention brings the setup directly into the documentation file, making it more self-contained and reducing ambiguity.
167+
By adopting this convention, users can easily identify and follow the correct sequence
168+
of steps to ensure that the examples provided in the documentation work as intended.
170169
* `// TEARDOWN`: Ends and cleans up a test series started with `// TESTSETUP` or
171170
`// TEST[setup:name]`. You can use `// TEARDOWN` to set up multiple tests in
172171
the same file.

docs/changelog/120291.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 120291
2+
summary: ESQL - Allow full text functions disjunctions for non-full text functions
3+
area: ES|QL
4+
type: feature
5+
issues: []

docs/reference/esql/esql-limitations.asciidoc

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ it is necessary to use the search function, like <<esql-match>>, in a <<esql-whe
112112
directly after the <<esql-from>> source command, or close enough to it.
113113
Otherwise, the query will fail with a validation error.
114114
Another limitation is that any <<esql-where>> command containing a full-text search function
115-
cannot also use disjunctions (`OR`) unless all functions used in the OR clauses are full-text functions themselves.
115+
cannot use disjunctions (`OR`), unless:
116+
117+
* All functions used in the OR clauses are full-text functions themselves, or scoring is not used
116118

117119
For example, this query is valid:
118120

@@ -131,19 +133,27 @@ FROM books
131133
| WHERE MATCH(author, "Faulkner")
132134
----
133135

134-
And this query will fail due to the disjunction:
136+
And this query that uses a disjunction will succeed:
135137

136138
[source,esql]
137139
----
138140
FROM books
141+
| WHERE MATCH(author, "Faulkner") OR QSTR("author: Hemingway")
142+
----
143+
144+
However using scoring will fail because it uses a non full text function as part of the disjunction:
145+
146+
[source,esql]
147+
----
148+
FROM books METADATA _score
139149
| WHERE MATCH(author, "Faulkner") OR author LIKE "Hemingway"
140150
----
141151

142-
However this query will succeed because it uses full text functions on both `OR` clauses:
152+
Scoring will work in the following query, as it uses full text functions on both `OR` clauses:
143153

144154
[source,esql]
145155
----
146-
FROM books
156+
FROM books METADATA _score
147157
| WHERE MATCH(author, "Faulkner") OR QSTR("author: Hemingway")
148158
----
149159

muted-tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,12 @@ tests:
289289
- class: org.elasticsearch.xpack.remotecluster.RemoteClusterSecurityEsqlIT
290290
method: testCrossClusterAsyncQueryStop
291291
issue: https://github.com/elastic/elasticsearch/issues/121021
292+
- class: org.elasticsearch.xpack.security.profile.ProfileIntegTests
293+
method: testSuggestProfilesWithName
294+
issue: https://github.com/elastic/elasticsearch/issues/121022
295+
- class: org.elasticsearch.xpack.inference.action.filter.ShardBulkInferenceActionFilterIT
296+
method: testBulkOperations {p0=true}
297+
issue: https://github.com/elastic/elasticsearch/issues/120969
292298

293299
# Examples:
294300
#

server/src/main/java/org/elasticsearch/index/IndexSettings.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,8 @@ public Iterator<Setting<?>> settings() {
718718
"index.mapping.source.mode",
719719
value -> {},
720720
Setting.Property.Final,
721-
Setting.Property.IndexScope
721+
Setting.Property.IndexScope,
722+
Setting.Property.ServerlessPublic
722723
);
723724

724725
public static final Setting<Boolean> RECOVERY_USE_SYNTHETIC_SOURCE_SETTING = Setting.boolSetting(

x-pack/plugin/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ tasks.named("yamlRestCompatTestTransform").configure({ task ->
101101
task.skipTest("esql/190_lookup_join/alias-repeated-index", "LOOKUP JOIN does not support index aliases for now")
102102
task.skipTest("esql/190_lookup_join/alias-pattern-multiple", "LOOKUP JOIN does not support index aliases for now")
103103
task.skipTest("esql/190_lookup_join/alias-pattern-single", "LOOKUP JOIN does not support index aliases for now")
104-
104+
task.skipTest("esql/180_match_operator/match with disjunctions", "Disjunctions in full text functions work now")
105105
})
106106

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

x-pack/plugin/esql/compute/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@
3535
exports org.elasticsearch.compute.operator.mvdedupe;
3636
exports org.elasticsearch.compute.aggregation.table;
3737
exports org.elasticsearch.compute.data.sort;
38+
exports org.elasticsearch.compute.querydsl.query;
3839
}

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/LuceneQueryExpressionEvaluator.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.compute.data.DocVector;
2626
import org.elasticsearch.compute.data.IntVector;
2727
import org.elasticsearch.compute.data.Page;
28+
import org.elasticsearch.compute.operator.DriverContext;
2829
import org.elasticsearch.compute.operator.EvalOperator;
2930
import org.elasticsearch.core.Releasable;
3031
import org.elasticsearch.core.Releasables;
@@ -44,19 +45,20 @@ public record ShardConfig(Query query, IndexSearcher searcher) {}
4445

4546
private final BlockFactory blockFactory;
4647
private final ShardConfig[] shards;
47-
private final int docChannel;
4848

4949
private ShardState[] perShardState = EMPTY_SHARD_STATES;
5050

51-
public LuceneQueryExpressionEvaluator(BlockFactory blockFactory, ShardConfig[] shards, int docChannel) {
51+
public LuceneQueryExpressionEvaluator(BlockFactory blockFactory, ShardConfig[] shards) {
5252
this.blockFactory = blockFactory;
5353
this.shards = shards;
54-
this.docChannel = docChannel;
5554
}
5655

5756
@Override
5857
public Block eval(Page page) {
59-
DocVector docs = page.<DocBlock>getBlock(docChannel).asVector();
58+
// Lucene based operators retrieve DocVectors as first block
59+
Block block = page.getBlock(0);
60+
assert block instanceof DocBlock : "LuceneQueryExpressionEvaluator expects DocBlock as input";
61+
DocVector docs = (DocVector) block.asVector();
6062
try {
6163
if (docs.singleSegmentNonDecreasing()) {
6264
return evalSingleSegmentNonDecreasing(docs).asBlock();
@@ -341,4 +343,17 @@ public void close() {
341343
Releasables.closeExpectNoException(builder);
342344
}
343345
}
346+
347+
public static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
348+
private final ShardConfig[] shardConfigs;
349+
350+
public Factory(ShardConfig[] shardConfigs) {
351+
this.shardConfigs = shardConfigs;
352+
}
353+
354+
@Override
355+
public EvalOperator.ExpressionEvaluator get(DriverContext context) {
356+
return new LuceneQueryExpressionEvaluator(context.blockFactory(), shardConfigs);
357+
}
358+
}
344359
}

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

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
import org.apache.lucene.document.InetAddressPoint;
1111
import org.apache.lucene.geo.GeoEncodingUtils;
12+
import org.apache.lucene.search.BooleanClause;
13+
import org.apache.lucene.search.BooleanQuery;
14+
import org.apache.lucene.search.MatchAllDocsQuery;
1215
import org.apache.lucene.search.Query;
1316
import org.apache.lucene.util.BytesRef;
1417
import org.elasticsearch.common.geo.ShapeRelation;
@@ -20,6 +23,8 @@
2023
import org.elasticsearch.compute.data.FloatBlock;
2124
import org.elasticsearch.compute.data.IntBlock;
2225
import org.elasticsearch.compute.data.LongBlock;
26+
import org.elasticsearch.compute.operator.Warnings;
27+
import org.elasticsearch.compute.querydsl.query.SingleValueMatchQuery;
2328
import org.elasticsearch.core.Nullable;
2429
import org.elasticsearch.geometry.Geometry;
2530
import org.elasticsearch.geometry.Point;
@@ -30,6 +35,7 @@
3035
import org.elasticsearch.index.mapper.RangeFieldMapper;
3136
import org.elasticsearch.index.query.SearchExecutionContext;
3237

38+
import java.io.IOException;
3339
import java.util.ArrayList;
3440
import java.util.List;
3541
import java.util.function.IntFunction;
@@ -38,10 +44,14 @@
3844
* Generates a list of Lucene queries based on the input block.
3945
*/
4046
public abstract class QueryList {
47+
protected final SearchExecutionContext searchExecutionContext;
48+
protected final MappedFieldType field;
4149
protected final Block block;
4250
protected final boolean onlySingleValues;
4351

44-
protected QueryList(Block block, boolean onlySingleValues) {
52+
protected QueryList(MappedFieldType field, SearchExecutionContext searchExecutionContext, Block block, boolean onlySingleValues) {
53+
this.searchExecutionContext = searchExecutionContext;
54+
this.field = field;
4555
this.block = block;
4656
this.onlySingleValues = onlySingleValues;
4757
}
@@ -59,11 +69,52 @@ int getPositionCount() {
5969
*/
6070
public abstract QueryList onlySingleValues();
6171

72+
final Query getQuery(int position) {
73+
final int valueCount = block.getValueCount(position);
74+
if (onlySingleValues && valueCount != 1) {
75+
return null;
76+
}
77+
final int firstValueIndex = block.getFirstValueIndex(position);
78+
79+
Query query = doGetQuery(position, firstValueIndex, valueCount);
80+
81+
if (onlySingleValues) {
82+
query = wrapSingleValueQuery(query);
83+
}
84+
85+
return query;
86+
}
87+
6288
/**
6389
* Returns the query at the given position.
6490
*/
6591
@Nullable
66-
abstract Query getQuery(int position);
92+
abstract Query doGetQuery(int position, int firstValueIndex, int valueCount);
93+
94+
private Query wrapSingleValueQuery(Query query) {
95+
SingleValueMatchQuery singleValueQuery = new SingleValueMatchQuery(
96+
searchExecutionContext.getForField(field, MappedFieldType.FielddataOperation.SEARCH),
97+
// Not emitting warnings for multivalued fields not matching
98+
Warnings.NOOP_WARNINGS
99+
);
100+
101+
Query rewrite = singleValueQuery;
102+
try {
103+
rewrite = singleValueQuery.rewrite(searchExecutionContext.searcher());
104+
if (rewrite instanceof MatchAllDocsQuery) {
105+
// nothing to filter
106+
return query;
107+
}
108+
} catch (IOException e) {
109+
// ignore
110+
// TODO: Should we do something with the exception?
111+
}
112+
113+
BooleanQuery.Builder builder = new BooleanQuery.Builder();
114+
builder.add(query, BooleanClause.Occur.FILTER);
115+
builder.add(rewrite, BooleanClause.Occur.FILTER);
116+
return builder.build();
117+
}
67118

68119
/**
69120
* Returns a list of term queries for the given field and the input block
@@ -146,8 +197,6 @@ public static QueryList geoShapeQueryList(MappedFieldType field, SearchExecution
146197
}
147198

148199
private static class TermQueryList extends QueryList {
149-
private final MappedFieldType field;
150-
private final SearchExecutionContext searchExecutionContext;
151200
private final IntFunction<Object> blockValueReader;
152201

153202
private TermQueryList(
@@ -157,9 +206,7 @@ private TermQueryList(
157206
boolean onlySingleValues,
158207
IntFunction<Object> blockValueReader
159208
) {
160-
super(block, onlySingleValues);
161-
this.field = field;
162-
this.searchExecutionContext = searchExecutionContext;
209+
super(field, searchExecutionContext, block, onlySingleValues);
163210
this.blockValueReader = blockValueReader;
164211
}
165212

@@ -169,19 +216,14 @@ public TermQueryList onlySingleValues() {
169216
}
170217

171218
@Override
172-
Query getQuery(int position) {
173-
final int count = block.getValueCount(position);
174-
if (onlySingleValues && count != 1) {
175-
return null;
176-
}
177-
final int first = block.getFirstValueIndex(position);
178-
return switch (count) {
219+
Query doGetQuery(int position, int firstValueIndex, int valueCount) {
220+
return switch (valueCount) {
179221
case 0 -> null;
180-
case 1 -> field.termQuery(blockValueReader.apply(first), searchExecutionContext);
222+
case 1 -> field.termQuery(blockValueReader.apply(firstValueIndex), searchExecutionContext);
181223
default -> {
182-
final List<Object> terms = new ArrayList<>(count);
183-
for (int i = 0; i < count; i++) {
184-
final Object value = blockValueReader.apply(first + i);
224+
final List<Object> terms = new ArrayList<>(valueCount);
225+
for (int i = 0; i < valueCount; i++) {
226+
final Object value = blockValueReader.apply(firstValueIndex + i);
185227
terms.add(value);
186228
}
187229
yield field.termsQuery(terms, searchExecutionContext);
@@ -192,8 +234,6 @@ Query getQuery(int position) {
192234

193235
private static class GeoShapeQueryList extends QueryList {
194236
private final BytesRef scratch = new BytesRef();
195-
private final MappedFieldType field;
196-
private final SearchExecutionContext searchExecutionContext;
197237
private final IntFunction<Geometry> blockValueReader;
198238
private final IntFunction<Query> shapeQuery;
199239

@@ -203,10 +243,8 @@ private GeoShapeQueryList(
203243
Block block,
204244
boolean onlySingleValues
205245
) {
206-
super(block, onlySingleValues);
246+
super(field, searchExecutionContext, block, onlySingleValues);
207247

208-
this.field = field;
209-
this.searchExecutionContext = searchExecutionContext;
210248
this.blockValueReader = blockToGeometry(block);
211249
this.shapeQuery = shapeQuery();
212250
}
@@ -217,15 +255,10 @@ public GeoShapeQueryList onlySingleValues() {
217255
}
218256

219257
@Override
220-
Query getQuery(int position) {
221-
final int count = block.getValueCount(position);
222-
if (onlySingleValues && count != 1) {
223-
return null;
224-
}
225-
final int first = block.getFirstValueIndex(position);
226-
return switch (count) {
258+
Query doGetQuery(int position, int firstValueIndex, int valueCount) {
259+
return switch (valueCount) {
227260
case 0 -> null;
228-
case 1 -> shapeQuery.apply(first);
261+
case 1 -> shapeQuery.apply(firstValueIndex);
229262
// TODO: support multiple values
230263
default -> throw new IllegalArgumentException("can't read multiple Geometry values from a single position");
231264
};
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
package org.elasticsearch.xpack.esql.querydsl.query;
8+
package org.elasticsearch.compute.querydsl.query;
99

1010
import org.apache.lucene.index.DocValues;
1111
import org.apache.lucene.index.LeafReaderContext;
@@ -39,7 +39,7 @@
3939
/**
4040
* Finds all fields with a single-value. If a field has a multi-value, it emits a {@link Warnings}.
4141
*/
42-
final class SingleValueMatchQuery extends Query {
42+
public final class SingleValueMatchQuery extends Query {
4343

4444
/**
4545
* Choose a big enough value so this approximation never drives the iteration.
@@ -52,7 +52,7 @@ final class SingleValueMatchQuery extends Query {
5252
private final IndexFieldData<?> fieldData;
5353
private final Warnings warnings;
5454

55-
SingleValueMatchQuery(IndexFieldData<?> fieldData, Warnings warnings) {
55+
public SingleValueMatchQuery(IndexFieldData<?> fieldData, Warnings warnings) {
5656
this.fieldData = fieldData;
5757
this.warnings = warnings;
5858
}

0 commit comments

Comments
 (0)