Skip to content

Commit fac9764

Browse files
committed
Add NOT support
1 parent 9c684d3 commit fac9764

File tree

3 files changed

+186
-7
lines changed

3 files changed

+186
-7
lines changed

x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/plugin/MatchFunctionIT.java

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,21 +319,69 @@ public void testDisjunctionScoringMultipleNonPushableFunctions() {
319319
}
320320
}
321321

322-
public void testScoresAreSimilarToQstr() {
323-
var queryMatch = """
322+
public void testScoresAreSimilarToPushable() {
323+
var nonPushableQuery = """
324324
FROM test METADATA _score
325-
| WHERE match(content, "fox") OR (length(content) < 25 AND id > 3)
325+
| WHERE match(content, "fox") OR (length(content) < 40 AND id > 3)
326326
| KEEP id, _score
327327
| SORT _score DESC
328328
""";
329329

330-
var queryQstr = """
330+
var pushableQuery = """
331331
FROM test METADATA _score
332-
| WHERE qstr("content:fox OR (length < 25 AND id > 3)")
332+
| WHERE match(content, "fox") OR (length < 40 AND id > 3)
333333
| KEEP id, _score
334334
| SORT _score DESC
335335
""";
336336

337+
compareQueryResultsAndScores(nonPushableQuery, pushableQuery);
338+
}
339+
340+
public void testDisjunctionScoringWithNot() {
341+
var query = """
342+
FROM test METADATA _score
343+
| WHERE NOT(match(content, "dog")) OR length(content) > 50
344+
| KEEP id, _score
345+
| SORT _score DESC, id ASC
346+
""";
347+
348+
try (var resp = run(query)) {
349+
assertColumnNames(resp.columns(), List.of("id", "_score"));
350+
assertColumnTypes(resp.columns(), List.of("integer", "double"));
351+
List<List<Object>> values = getValuesList(resp);
352+
assertThat(values.size(), equalTo(3));
353+
354+
assertThat(values.get(0).get(0), equalTo(4));
355+
assertThat(values.get(1).get(0), equalTo(1));
356+
assertThat(values.get(2).get(0), equalTo(5));
357+
358+
// Matches NOT and non pushable query
359+
assertThat((Double) values.get(0).get(1), equalTo(1.0));
360+
// Matches just NOT
361+
assertThat((Double) values.get(1).get(1), equalTo(0.0));
362+
assertThat((Double) values.get(2).get(1), equalTo(0.0));
363+
}
364+
}
365+
366+
public void testScoresAreSimilarToPushableUsingNot() {
367+
var nonPushableQuery = """
368+
FROM test METADATA _score
369+
| WHERE NOT(match(content, "fox")) OR (length(content) < 25 AND id > 3)
370+
| KEEP id, _score
371+
| SORT _score DESC, id ASC
372+
""";
373+
374+
var pushableQuery = """
375+
FROM test METADATA _score
376+
| WHERE NOT(match(content, "fox")) OR (length < 25 AND id > 3)
377+
| KEEP id, _score
378+
| SORT _score DESC, id ASC
379+
""";
380+
381+
compareQueryResultsAndScores(nonPushableQuery, pushableQuery);
382+
}
383+
384+
private void compareQueryResultsAndScores(String queryMatch, String queryQstr) {
337385
try (var respMatch = run(queryMatch); var respQstr = run(queryQstr)) {
338386
assertEquals(respMatch.columns(), respQstr.columns());
339387
var matchValues = getValuesList(respMatch);
@@ -343,7 +391,7 @@ public void testScoresAreSimilarToQstr() {
343391
// Compare ids
344392
assertEquals(matchValues.get(i).get(0), qstrValues.get(i).get(0));
345393
// Compare scores
346-
assertThat((Double) matchValues.get(i).get(1), closeTo((Double) qstrValues.get(i).get(1), 0.01));
394+
assertThat((Double) matchValues.get(i).get(1), closeTo((Double) qstrValues.get(i).get(1), 0.0001));
347395
}
348396
}
349397
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/evaluator/EvalMapper.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.elasticsearch.xpack.esql.core.expression.Literal;
2424
import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper;
2525
import org.elasticsearch.xpack.esql.evaluator.mapper.ExpressionMapper;
26+
import org.elasticsearch.xpack.esql.evaluator.predicate.operator.logical.NotScoringEvaluator;
2627
import org.elasticsearch.xpack.esql.expression.predicate.logical.BinaryLogic;
2728
import org.elasticsearch.xpack.esql.expression.predicate.logical.Not;
2829
import org.elasticsearch.xpack.esql.expression.predicate.nulls.IsNotNull;
@@ -114,7 +115,10 @@ public ExpressionEvaluator.Factory map(
114115
List<ShardContext> shardContexts,
115116
boolean usesScoring
116117
) {
117-
var expEval = toEvaluator(foldCtx, not.field(), layout);
118+
var expEval = toEvaluator(foldCtx, not.field(), layout, shardContexts, usesScoring);
119+
if (usesScoring) {
120+
return new NotScoringEvaluator.Factory(not.source(), expEval);
121+
}
118122
return dvrCtx -> new org.elasticsearch.xpack.esql.evaluator.predicate.operator.logical.NotEvaluator(
119123
not.source(),
120124
expEval.get(dvrCtx),
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
package org.elasticsearch.xpack.esql.evaluator.predicate.operator.logical;
8+
9+
import org.elasticsearch.compute.data.Block;
10+
import org.elasticsearch.compute.data.DoubleBlock;
11+
import org.elasticsearch.compute.data.DoubleVector;
12+
import org.elasticsearch.compute.data.Page;
13+
import org.elasticsearch.compute.operator.DriverContext;
14+
import org.elasticsearch.compute.operator.EvalOperator;
15+
import org.elasticsearch.compute.operator.Warnings;
16+
import org.elasticsearch.core.Releasables;
17+
import org.elasticsearch.xpack.esql.core.tree.Source;
18+
import org.elasticsearch.xpack.esql.evaluator.mapper.BooleanToScoringExpressionEvaluator;
19+
20+
import static org.elasticsearch.compute.lucene.LuceneQueryExpressionEvaluator.SCORE_FOR_FALSE;
21+
22+
/**
23+
* {@link EvalOperator.ExpressionEvaluator} implementation for {@link Not}.
24+
* This class is generated. Do not edit it.
25+
*/
26+
public final class NotScoringEvaluator implements EvalOperator.ExpressionEvaluator {
27+
private final Source source;
28+
29+
private final EvalOperator.ExpressionEvaluator v;
30+
31+
private final DriverContext driverContext;
32+
33+
private Warnings warnings;
34+
35+
public NotScoringEvaluator(Source source, EvalOperator.ExpressionEvaluator v, DriverContext driverContext) {
36+
this.source = source;
37+
this.v = v;
38+
this.driverContext = driverContext;
39+
}
40+
41+
@Override
42+
public Block eval(Page page) {
43+
try (DoubleBlock vBlock = (DoubleBlock) v.eval(page)) {
44+
DoubleVector vVector = vBlock.asVector();
45+
if (vVector == null) {
46+
return eval(page.getPositionCount(), vBlock);
47+
}
48+
return eval(page.getPositionCount(), vVector).asBlock();
49+
}
50+
}
51+
52+
public DoubleBlock eval(int positionCount, DoubleBlock vBlock) {
53+
try (DoubleBlock.Builder result = driverContext.blockFactory().newDoubleBlockBuilder(positionCount)) {
54+
position: for (int p = 0; p < positionCount; p++) {
55+
if (vBlock.isNull(p)) {
56+
result.appendNull();
57+
continue position;
58+
}
59+
if (vBlock.getValueCount(p) != 1) {
60+
if (vBlock.getValueCount(p) > 1) {
61+
warnings().registerException(new IllegalArgumentException("single-value function encountered multi-value"));
62+
}
63+
result.appendNull();
64+
continue position;
65+
}
66+
result.appendDouble(evaluate(vBlock.getDouble(vBlock.getFirstValueIndex(p))));
67+
}
68+
return result.build();
69+
}
70+
}
71+
72+
private static Double evaluate(Double v) {
73+
return v == SCORE_FOR_FALSE ? 0.0 : SCORE_FOR_FALSE;
74+
}
75+
76+
public DoubleVector eval(int positionCount, DoubleVector vVector) {
77+
try (DoubleVector.FixedBuilder result = driverContext.blockFactory().newDoubleVectorFixedBuilder(positionCount)) {
78+
position: for (int p = 0; p < positionCount; p++) {
79+
result.appendDouble(evaluate(vVector.getDouble(p)));
80+
}
81+
return result.build();
82+
}
83+
}
84+
85+
@Override
86+
public String toString() {
87+
return "NotEvaluator[" + "v=" + v + "]";
88+
}
89+
90+
@Override
91+
public void close() {
92+
Releasables.closeExpectNoException(v);
93+
}
94+
95+
private Warnings warnings() {
96+
if (warnings == null) {
97+
this.warnings = Warnings.createWarnings(
98+
driverContext.warningsMode(),
99+
source.source().getLineNumber(),
100+
source.source().getColumnNumber(),
101+
source.text()
102+
);
103+
}
104+
return warnings;
105+
}
106+
107+
public static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
108+
private final Source source;
109+
110+
private final EvalOperator.ExpressionEvaluator.Factory v;
111+
112+
public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory v) {
113+
this.source = source;
114+
this.v = v;
115+
}
116+
117+
@Override
118+
public NotScoringEvaluator get(DriverContext context) {
119+
return new NotScoringEvaluator(source, new BooleanToScoringExpressionEvaluator(v.get(context), context), context);
120+
}
121+
122+
@Override
123+
public String toString() {
124+
return "NotEvaluator[" + "v=" + v + "]";
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)