-
Notifications
You must be signed in to change notification settings - Fork 25k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ESQL: add =~ operator (case insensitive equality) #103656
Changes from 3 commits
87f2ec8
4c1e89c
67ac517
b268716
c54f444
e002329
1f16348
952f4c3
53c0eec
c5be3ee
5417b2f
d53727b
f1b423d
a79fd5d
c50ba9b
2dc49b0
9ec7fbf
52718cc
929b34f
5bca81c
1999e3e
aac2340
65941c9
4051636
84e58d7
c13ab85
e8023eb
453a77f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
|
||
simpleFilter#[skip:-8.12.99, reason:case insensitive operators implemented in v 8.13] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if in a follow up we can remove the skip from the name of the test that we print. And maybe put it on the next line. It's kind of a lot. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It hurts me as well, especially when I see it in the logs. But does it work if I move it to the next line? I'll check it separately There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. Separately. |
||
from employees | where first_name =~ "mary" | keep emp_no, first_name, last_name; | ||
|
||
emp_no:integer | first_name:keyword | last_name:keyword | ||
10011 | Mary | Sluis | ||
; | ||
|
||
|
||
simpleFilterUpper#[skip:-8.12.99, reason:case insensitive operators implemented in v 8.13] | ||
from employees | where first_name =~ "MARY" | keep emp_no, first_name, last_name; | ||
|
||
emp_no:integer | first_name:keyword | last_name:keyword | ||
10011 | Mary | Sluis | ||
; | ||
|
||
simpleFilterPartial#[skip:-8.12.99, reason:case insensitive operators implemented in v 8.13] | ||
from employees | where first_name =~ "mar" | keep emp_no, first_name, last_name; | ||
|
||
emp_no:integer | first_name:keyword | last_name:keyword | ||
; | ||
|
||
mixedConditionsAnd#[skip:-8.12.99, reason:case insensitive operators implemented in v 8.13] | ||
from employees | where first_name =~ "mary" AND emp_no == 10011 | keep emp_no, first_name, last_name; | ||
|
||
emp_no:integer | first_name:keyword | last_name:keyword | ||
10011 | Mary | Sluis | ||
; | ||
|
||
|
||
mixedConditionsOr#[skip:-8.12.99, reason:case insensitive operators implemented in v 8.13] | ||
from employees | where first_name =~ "mary" OR emp_no == 10001 | keep emp_no, first_name, last_name |sort emp_no; | ||
|
||
emp_no:integer | first_name:keyword | last_name:keyword | ||
10001 | Georgi | Facello | ||
10011 | Mary | Sluis | ||
; | ||
|
||
|
||
evalEquals#[skip:-8.12.99, reason:case insensitive operators implemented in v 8.13] | ||
from employees | where emp_no == 10001 | ||
| eval a = first_name =~ "georgi", b = first_name == "georgi", c = first_name =~ "GEORGI", d = first_name =~ "Geor" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. try "GeoRgI" or something similar just in cae. |
||
| keep emp_no, first_name, a, b, c, d; | ||
|
||
emp_no:integer | first_name:keyword | a:boolean | b:boolean | c:boolean | d:boolean | ||
10001 | Georgi | true | false | true | false | ||
; | ||
|
||
|
||
filterNumeric#[skip:-8.12.99, reason:case insensitive operators implemented in v 8.13] | ||
from employees | where emp_no =~ 10001 | keep emp_no, first_name, last_name; | ||
|
||
emp_no:integer | first_name:keyword | last_name:keyword | ||
10001 | Georgi | Facello | ||
; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -130,6 +130,7 @@ RP : ')'; | |
TRUE : 'true'; | ||
|
||
EQ : '=='; | ||
EQ_IGNORE_CASE : '=~'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I propose we use SEQ for "string equality" (just like in EQL) or IEQ for "Insensitive Equality". |
||
NEQ : '!='; | ||
LT : '<'; | ||
LTE : '<='; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
// or more contributor license agreements. Licensed under the Elastic License | ||
// 2.0; you may not use this file except in compliance with the Elastic License | ||
// 2.0. | ||
package org.elasticsearch.xpack.esql.evaluator.predicate.operator.comparison; | ||
|
||
import java.lang.IllegalArgumentException; | ||
import java.lang.Override; | ||
import java.lang.String; | ||
import org.elasticsearch.compute.data.Block; | ||
import org.elasticsearch.compute.data.BooleanBlock; | ||
import org.elasticsearch.compute.data.BooleanVector; | ||
import org.elasticsearch.compute.data.Page; | ||
import org.elasticsearch.compute.operator.DriverContext; | ||
import org.elasticsearch.compute.operator.EvalOperator; | ||
import org.elasticsearch.core.Releasables; | ||
import org.elasticsearch.xpack.esql.expression.function.Warnings; | ||
import org.elasticsearch.xpack.ql.tree.Source; | ||
|
||
/** | ||
* {@link EvalOperator.ExpressionEvaluator} implementation for {@link EqualsIgnoreCase}. | ||
* This class is generated. Do not edit it. | ||
*/ | ||
public final class EqualsIgnoreCaseBoolsEvaluator implements EvalOperator.ExpressionEvaluator { | ||
private final Warnings warnings; | ||
|
||
private final EvalOperator.ExpressionEvaluator lhs; | ||
|
||
private final EvalOperator.ExpressionEvaluator rhs; | ||
|
||
private final DriverContext driverContext; | ||
|
||
public EqualsIgnoreCaseBoolsEvaluator(Source source, EvalOperator.ExpressionEvaluator lhs, | ||
EvalOperator.ExpressionEvaluator rhs, DriverContext driverContext) { | ||
this.warnings = new Warnings(source); | ||
this.lhs = lhs; | ||
this.rhs = rhs; | ||
this.driverContext = driverContext; | ||
} | ||
|
||
@Override | ||
public Block eval(Page page) { | ||
try (BooleanBlock lhsBlock = (BooleanBlock) lhs.eval(page)) { | ||
try (BooleanBlock rhsBlock = (BooleanBlock) rhs.eval(page)) { | ||
BooleanVector lhsVector = lhsBlock.asVector(); | ||
if (lhsVector == null) { | ||
return eval(page.getPositionCount(), lhsBlock, rhsBlock); | ||
} | ||
BooleanVector rhsVector = rhsBlock.asVector(); | ||
if (rhsVector == null) { | ||
return eval(page.getPositionCount(), lhsBlock, rhsBlock); | ||
} | ||
return eval(page.getPositionCount(), lhsVector, rhsVector).asBlock(); | ||
} | ||
} | ||
} | ||
|
||
public BooleanBlock eval(int positionCount, BooleanBlock lhsBlock, BooleanBlock rhsBlock) { | ||
try(BooleanBlock.Builder result = driverContext.blockFactory().newBooleanBlockBuilder(positionCount)) { | ||
position: for (int p = 0; p < positionCount; p++) { | ||
if (lhsBlock.isNull(p)) { | ||
result.appendNull(); | ||
continue position; | ||
} | ||
if (lhsBlock.getValueCount(p) != 1) { | ||
if (lhsBlock.getValueCount(p) > 1) { | ||
warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value")); | ||
} | ||
result.appendNull(); | ||
continue position; | ||
} | ||
if (rhsBlock.isNull(p)) { | ||
result.appendNull(); | ||
continue position; | ||
} | ||
if (rhsBlock.getValueCount(p) != 1) { | ||
if (rhsBlock.getValueCount(p) > 1) { | ||
warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value")); | ||
} | ||
result.appendNull(); | ||
continue position; | ||
} | ||
result.appendBoolean(EqualsIgnoreCase.processBools(lhsBlock.getBoolean(lhsBlock.getFirstValueIndex(p)), rhsBlock.getBoolean(rhsBlock.getFirstValueIndex(p)))); | ||
} | ||
return result.build(); | ||
} | ||
} | ||
|
||
public BooleanVector eval(int positionCount, BooleanVector lhsVector, BooleanVector rhsVector) { | ||
try(BooleanVector.Builder result = driverContext.blockFactory().newBooleanVectorBuilder(positionCount)) { | ||
position: for (int p = 0; p < positionCount; p++) { | ||
result.appendBoolean(EqualsIgnoreCase.processBools(lhsVector.getBoolean(p), rhsVector.getBoolean(p))); | ||
} | ||
return result.build(); | ||
} | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "EqualsIgnoreCaseBoolsEvaluator[" + "lhs=" + lhs + ", rhs=" + rhs + "]"; | ||
} | ||
|
||
@Override | ||
public void close() { | ||
Releasables.closeExpectNoException(lhs, rhs); | ||
} | ||
|
||
static class Factory implements EvalOperator.ExpressionEvaluator.Factory { | ||
private final Source source; | ||
|
||
private final EvalOperator.ExpressionEvaluator.Factory lhs; | ||
|
||
private final EvalOperator.ExpressionEvaluator.Factory rhs; | ||
|
||
public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory lhs, | ||
EvalOperator.ExpressionEvaluator.Factory rhs) { | ||
this.source = source; | ||
this.lhs = lhs; | ||
this.rhs = rhs; | ||
} | ||
|
||
@Override | ||
public EqualsIgnoreCaseBoolsEvaluator get(DriverContext context) { | ||
return new EqualsIgnoreCaseBoolsEvaluator(source, lhs.get(context), rhs.get(context), context); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "EqualsIgnoreCaseBoolsEvaluator[" + "lhs=" + lhs + ", rhs=" + rhs + "]"; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
// or more contributor license agreements. Licensed under the Elastic License | ||
// 2.0; you may not use this file except in compliance with the Elastic License | ||
// 2.0. | ||
package org.elasticsearch.xpack.esql.evaluator.predicate.operator.comparison; | ||
|
||
import java.lang.IllegalArgumentException; | ||
import java.lang.Override; | ||
import java.lang.String; | ||
import org.elasticsearch.compute.data.Block; | ||
import org.elasticsearch.compute.data.BooleanBlock; | ||
import org.elasticsearch.compute.data.BooleanVector; | ||
import org.elasticsearch.compute.data.DoubleBlock; | ||
import org.elasticsearch.compute.data.DoubleVector; | ||
import org.elasticsearch.compute.data.Page; | ||
import org.elasticsearch.compute.operator.DriverContext; | ||
import org.elasticsearch.compute.operator.EvalOperator; | ||
import org.elasticsearch.core.Releasables; | ||
import org.elasticsearch.xpack.esql.expression.function.Warnings; | ||
import org.elasticsearch.xpack.ql.tree.Source; | ||
|
||
/** | ||
* {@link EvalOperator.ExpressionEvaluator} implementation for {@link EqualsIgnoreCase}. | ||
* This class is generated. Do not edit it. | ||
*/ | ||
public final class EqualsIgnoreCaseDoublesEvaluator implements EvalOperator.ExpressionEvaluator { | ||
private final Warnings warnings; | ||
|
||
private final EvalOperator.ExpressionEvaluator lhs; | ||
|
||
private final EvalOperator.ExpressionEvaluator rhs; | ||
|
||
private final DriverContext driverContext; | ||
|
||
public EqualsIgnoreCaseDoublesEvaluator(Source source, EvalOperator.ExpressionEvaluator lhs, | ||
EvalOperator.ExpressionEvaluator rhs, DriverContext driverContext) { | ||
this.warnings = new Warnings(source); | ||
this.lhs = lhs; | ||
this.rhs = rhs; | ||
this.driverContext = driverContext; | ||
} | ||
|
||
@Override | ||
public Block eval(Page page) { | ||
try (DoubleBlock lhsBlock = (DoubleBlock) lhs.eval(page)) { | ||
try (DoubleBlock rhsBlock = (DoubleBlock) rhs.eval(page)) { | ||
DoubleVector lhsVector = lhsBlock.asVector(); | ||
if (lhsVector == null) { | ||
return eval(page.getPositionCount(), lhsBlock, rhsBlock); | ||
} | ||
DoubleVector rhsVector = rhsBlock.asVector(); | ||
if (rhsVector == null) { | ||
return eval(page.getPositionCount(), lhsBlock, rhsBlock); | ||
} | ||
return eval(page.getPositionCount(), lhsVector, rhsVector).asBlock(); | ||
} | ||
} | ||
} | ||
|
||
public BooleanBlock eval(int positionCount, DoubleBlock lhsBlock, DoubleBlock rhsBlock) { | ||
try(BooleanBlock.Builder result = driverContext.blockFactory().newBooleanBlockBuilder(positionCount)) { | ||
position: for (int p = 0; p < positionCount; p++) { | ||
if (lhsBlock.isNull(p)) { | ||
result.appendNull(); | ||
continue position; | ||
} | ||
if (lhsBlock.getValueCount(p) != 1) { | ||
if (lhsBlock.getValueCount(p) > 1) { | ||
warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value")); | ||
} | ||
result.appendNull(); | ||
continue position; | ||
} | ||
if (rhsBlock.isNull(p)) { | ||
result.appendNull(); | ||
continue position; | ||
} | ||
if (rhsBlock.getValueCount(p) != 1) { | ||
if (rhsBlock.getValueCount(p) > 1) { | ||
warnings.registerException(new IllegalArgumentException("single-value function encountered multi-value")); | ||
} | ||
result.appendNull(); | ||
continue position; | ||
} | ||
result.appendBoolean(EqualsIgnoreCase.processDoubles(lhsBlock.getDouble(lhsBlock.getFirstValueIndex(p)), rhsBlock.getDouble(rhsBlock.getFirstValueIndex(p)))); | ||
} | ||
return result.build(); | ||
} | ||
} | ||
|
||
public BooleanVector eval(int positionCount, DoubleVector lhsVector, DoubleVector rhsVector) { | ||
try(BooleanVector.Builder result = driverContext.blockFactory().newBooleanVectorBuilder(positionCount)) { | ||
position: for (int p = 0; p < positionCount; p++) { | ||
result.appendBoolean(EqualsIgnoreCase.processDoubles(lhsVector.getDouble(p), rhsVector.getDouble(p))); | ||
} | ||
return result.build(); | ||
} | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "EqualsIgnoreCaseDoublesEvaluator[" + "lhs=" + lhs + ", rhs=" + rhs + "]"; | ||
} | ||
|
||
@Override | ||
public void close() { | ||
Releasables.closeExpectNoException(lhs, rhs); | ||
} | ||
|
||
static class Factory implements EvalOperator.ExpressionEvaluator.Factory { | ||
private final Source source; | ||
|
||
private final EvalOperator.ExpressionEvaluator.Factory lhs; | ||
|
||
private final EvalOperator.ExpressionEvaluator.Factory rhs; | ||
|
||
public Factory(Source source, EvalOperator.ExpressionEvaluator.Factory lhs, | ||
EvalOperator.ExpressionEvaluator.Factory rhs) { | ||
this.source = source; | ||
this.lhs = lhs; | ||
this.rhs = rhs; | ||
} | ||
|
||
@Override | ||
public EqualsIgnoreCaseDoublesEvaluator get(DriverContext context) { | ||
return new EqualsIgnoreCaseDoublesEvaluator(source, lhs.get(context), rhs.get(context), context); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "EqualsIgnoreCaseDoublesEvaluator[" + "lhs=" + lhs + ", rhs=" + rhs + "]"; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear from the tests if the right hand side allows only literals (the original requirement), folded expressions or generic expressions (which you added).
I think it's the first variant but I don't see any tests validating this.
So please add more test to either validate that literals/folded expressions are required (and fields are not allowed) or vice-versa - queries that have fields on both sides and more over expressions:
where concat(field, "constant") =~ concat(field, concat("con", "stant))
etc...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @costin
Yes, the implementation supports any kind of expression, both on the left and on the right.
I added a few more tests for this.