EQL: implement case sensitivity for indexOf and endsWith string functions (#57707) (#57908)

* EQL: implement case sensitivity for indexOf and endsWith string functions
This commit is contained in:
Aleksandr Maus 2020-06-10 08:55:49 -04:00 committed by GitHub
parent 85f5c4192b
commit ec60335496
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 287 additions and 131 deletions

View File

@ -6,14 +6,16 @@
package org.elasticsearch.xpack.eql.expression.function.scalar.string;
import org.elasticsearch.xpack.eql.session.EqlConfiguration;
import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.expression.Expressions;
import org.elasticsearch.xpack.ql.expression.Expressions.ParamOrdinal;
import org.elasticsearch.xpack.ql.expression.FieldAttribute;
import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.ql.expression.function.scalar.string.CaseSensitiveScalarFunction;
import org.elasticsearch.xpack.ql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.ql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.ql.expression.gen.script.Scripts;
import org.elasticsearch.xpack.ql.session.Configuration;
import org.elasticsearch.xpack.ql.tree.NodeInfo;
import org.elasticsearch.xpack.ql.tree.Source;
import org.elasticsearch.xpack.ql.type.DataType;
@ -32,17 +34,22 @@ import static org.elasticsearch.xpack.ql.expression.gen.script.ParamsBuilder.par
* Function that checks if first parameter ends with the second parameter. Both parameters should be strings
* and the function returns a boolean value. The function is case insensitive.
*/
public class EndsWith extends ScalarFunction {
public class EndsWith extends CaseSensitiveScalarFunction {
private final Expression source;
private final Expression pattern;
public EndsWith(Source source, Expression src, Expression pattern) {
super(source, Arrays.asList(src, pattern));
public EndsWith(Source source, Expression src, Expression pattern, Configuration configuration) {
super(source, Arrays.asList(src, pattern), configuration);
this.source = src;
this.pattern = pattern;
}
@Override
public boolean isCaseSensitive() {
return ((EqlConfiguration) configuration()).isCaseSensitive();
}
@Override
protected TypeResolution resolveType() {
if (!childrenResolved()) {
@ -59,7 +66,7 @@ public class EndsWith extends ScalarFunction {
@Override
protected Pipe makePipe() {
return new EndsWithFunctionPipe(source(), this, Expressions.pipe(source), Expressions.pipe(pattern));
return new EndsWithFunctionPipe(source(), this, Expressions.pipe(source), Expressions.pipe(pattern), isCaseSensitive());
}
@Override
@ -69,12 +76,12 @@ public class EndsWith extends ScalarFunction {
@Override
public Object fold() {
return doProcess(source.fold(), pattern.fold());
return doProcess(source.fold(), pattern.fold(), isCaseSensitive());
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, EndsWith::new, source, pattern);
return NodeInfo.create(this, EndsWith::new, source, pattern, configuration());
}
@Override
@ -86,14 +93,16 @@ public class EndsWith extends ScalarFunction {
}
protected ScriptTemplate asScriptFrom(ScriptTemplate sourceScript, ScriptTemplate patternScript) {
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{eql}.%s(%s,%s)"),
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{eql}.%s(%s,%s,%s)"),
"endsWith",
sourceScript.template(),
patternScript.template()),
patternScript.template(),
"{}"),
paramsBuilder()
.script(sourceScript.params())
.script(patternScript.params())
.build(), dataType());
.script(sourceScript.params())
.script(patternScript.params())
.variable(isCaseSensitive())
.build(), dataType());
}
@Override
@ -114,7 +123,7 @@ public class EndsWith extends ScalarFunction {
throw new IllegalArgumentException("expected [2] children but received [" + newChildren.size() + "]");
}
return new EndsWith(source(), newChildren.get(0), newChildren.get(1));
return new EndsWith(source(), newChildren.get(0), newChildren.get(1), configuration());
}
}
}

View File

@ -19,11 +19,13 @@ public class EndsWithFunctionPipe extends Pipe {
private final Pipe source;
private final Pipe pattern;
private final boolean isCaseSensitive;
public EndsWithFunctionPipe(Source source, Expression expression, Pipe src, Pipe pattern) {
public EndsWithFunctionPipe(Source source, Expression expression, Pipe src, Pipe pattern, boolean isCaseSensitive) {
super(source, expression, Arrays.asList(src, pattern));
this.source = src;
this.pattern = pattern;
this.isCaseSensitive = isCaseSensitive;
}
@Override
@ -55,7 +57,7 @@ public class EndsWithFunctionPipe extends Pipe {
}
protected Pipe replaceChildren(Pipe newSource, Pipe newPattern) {
return new EndsWithFunctionPipe(source(), expression(), newSource, newPattern);
return new EndsWithFunctionPipe(source(), expression(), newSource, newPattern, isCaseSensitive);
}
@Override
@ -66,12 +68,12 @@ public class EndsWithFunctionPipe extends Pipe {
@Override
protected NodeInfo<EndsWithFunctionPipe> info() {
return NodeInfo.create(this, EndsWithFunctionPipe::new, expression(), source, pattern);
return NodeInfo.create(this, EndsWithFunctionPipe::new, expression(), source, pattern, isCaseSensitive);
}
@Override
public EndsWithFunctionProcessor asProcessor() {
return new EndsWithFunctionProcessor(source.asProcessor(), pattern.asProcessor());
return new EndsWithFunctionProcessor(source.asProcessor(), pattern.asProcessor(), isCaseSensitive);
}
public Pipe src() {
@ -84,7 +86,7 @@ public class EndsWithFunctionPipe extends Pipe {
@Override
public int hashCode() {
return Objects.hash(source, pattern);
return Objects.hash(source, pattern, isCaseSensitive);
}
@Override
@ -99,6 +101,7 @@ public class EndsWithFunctionPipe extends Pipe {
EndsWithFunctionPipe other = (EndsWithFunctionPipe) obj;
return Objects.equals(source, other.source)
&& Objects.equals(pattern, other.pattern);
&& Objects.equals(pattern, other.pattern)
&& Objects.equals(isCaseSensitive, other.isCaseSensitive);
}
}
}

View File

@ -20,29 +20,33 @@ public class EndsWithFunctionProcessor implements Processor {
private final Processor source;
private final Processor pattern;
private final boolean isCaseSensitive;
public EndsWithFunctionProcessor(Processor source, Processor pattern) {
public EndsWithFunctionProcessor(Processor source, Processor pattern, boolean isCaseSensitive) {
this.source = source;
this.pattern = pattern;
this.isCaseSensitive = isCaseSensitive;
}
public EndsWithFunctionProcessor(StreamInput in) throws IOException {
source = in.readNamedWriteable(Processor.class);
pattern = in.readNamedWriteable(Processor.class);
isCaseSensitive = in.readBoolean();
}
@Override
public final void writeTo(StreamOutput out) throws IOException {
out.writeNamedWriteable(source);
out.writeNamedWriteable(pattern);
out.writeBoolean(isCaseSensitive);
}
@Override
public Object process(Object input) {
return doProcess(source.process(input), pattern.process(input));
return doProcess(source.process(input), pattern.process(input), isCaseSensitive());
}
public static Object doProcess(Object source, Object pattern) {
public static Object doProcess(Object source, Object pattern, boolean isCaseSensitive) {
if (source == null) {
return null;
}
@ -56,7 +60,11 @@ public class EndsWithFunctionProcessor implements Processor {
throw new EqlIllegalArgumentException("A string/char is required; received [{}]", pattern);
}
return source.toString().toLowerCase(Locale.ROOT).endsWith(pattern.toString().toLowerCase(Locale.ROOT));
if (isCaseSensitive) {
return source.toString().endsWith(pattern.toString());
} else {
return source.toString().toLowerCase(Locale.ROOT).endsWith(pattern.toString().toLowerCase(Locale.ROOT));
}
}
protected Processor source() {
@ -66,7 +74,11 @@ public class EndsWithFunctionProcessor implements Processor {
protected Processor pattern() {
return pattern;
}
protected boolean isCaseSensitive() {
return isCaseSensitive;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
@ -79,12 +91,13 @@ public class EndsWithFunctionProcessor implements Processor {
EndsWithFunctionProcessor other = (EndsWithFunctionProcessor) obj;
return Objects.equals(source(), other.source())
&& Objects.equals(pattern(), other.pattern());
&& Objects.equals(pattern(), other.pattern())
&& Objects.equals(isCaseSensitive(), other.isCaseSensitive());
}
@Override
public int hashCode() {
return Objects.hash(source(), pattern());
return Objects.hash(source(), pattern(), isCaseSensitive());
}
@ -92,4 +105,4 @@ public class EndsWithFunctionProcessor implements Processor {
public String getWriteableName() {
return NAME;
}
}
}

View File

@ -6,16 +6,18 @@
package org.elasticsearch.xpack.eql.expression.function.scalar.string;
import org.elasticsearch.xpack.eql.session.EqlConfiguration;
import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.expression.Expressions;
import org.elasticsearch.xpack.ql.expression.Expressions.ParamOrdinal;
import org.elasticsearch.xpack.ql.expression.FieldAttribute;
import org.elasticsearch.xpack.ql.expression.Literal;
import org.elasticsearch.xpack.ql.expression.function.OptionalArgument;
import org.elasticsearch.xpack.ql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.ql.expression.function.scalar.string.CaseSensitiveScalarFunction;
import org.elasticsearch.xpack.ql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.ql.expression.gen.script.ScriptTemplate;
import org.elasticsearch.xpack.ql.expression.gen.script.Scripts;
import org.elasticsearch.xpack.ql.session.Configuration;
import org.elasticsearch.xpack.ql.tree.NodeInfo;
import org.elasticsearch.xpack.ql.tree.Source;
import org.elasticsearch.xpack.ql.type.DataType;
@ -35,17 +37,22 @@ import static org.elasticsearch.xpack.ql.expression.gen.script.ParamsBuilder.par
* Find the first position (zero-indexed) of a string where a substring is found.
* If the optional parameter start is provided, then this will find the first occurrence at or after the start position.
*/
public class IndexOf extends ScalarFunction implements OptionalArgument {
public class IndexOf extends CaseSensitiveScalarFunction implements OptionalArgument {
private final Expression source, substring, start;
public IndexOf(Source source, Expression src, Expression substring, Expression start) {
super(source, Arrays.asList(src, substring, start != null ? start : new Literal(source, null, DataTypes.NULL)));
public IndexOf(Source source, Expression src, Expression substring, Expression start, Configuration configuration) {
super(source, Arrays.asList(src, substring, start != null ? start : new Literal(source, null, DataTypes.NULL)), configuration);
this.source = src;
this.substring = substring;
this.start = arguments().get(2);
}
@Override
public boolean isCaseSensitive() {
return ((EqlConfiguration) configuration()).isCaseSensitive();
}
@Override
protected TypeResolution resolveType() {
if (!childrenResolved()) {
@ -61,13 +68,14 @@ public class IndexOf extends ScalarFunction implements OptionalArgument {
if (resolution.unresolved()) {
return resolution;
}
return isInteger(start, sourceText(), ParamOrdinal.THIRD);
}
@Override
protected Pipe makePipe() {
return new IndexOfFunctionPipe(source(), this, Expressions.pipe(source), Expressions.pipe(substring), Expressions.pipe(start));
return new IndexOfFunctionPipe(source(), this, Expressions.pipe(source), Expressions.pipe(substring),
Expressions.pipe(start), isCaseSensitive());
}
@Override
@ -77,12 +85,12 @@ public class IndexOf extends ScalarFunction implements OptionalArgument {
@Override
public Object fold() {
return doProcess(source.fold(), substring.fold(), start.fold());
return doProcess(source.fold(), substring.fold(), start.fold(), isCaseSensitive());
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, IndexOf::new, source, substring, start);
return NodeInfo.create(this, IndexOf::new, source, substring, start, configuration());
}
@Override
@ -93,18 +101,20 @@ public class IndexOf extends ScalarFunction implements OptionalArgument {
return asScriptFrom(sourceScript, substringScript, startScript);
}
protected ScriptTemplate asScriptFrom(ScriptTemplate sourceScript, ScriptTemplate substringScript, ScriptTemplate startScript) {
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{eql}.%s(%s,%s,%s)"),
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{eql}.%s(%s,%s,%s,%s)"),
"indexOf",
sourceScript.template(),
substringScript.template(),
startScript.template()),
startScript.template(),
"{}"),
paramsBuilder()
.script(sourceScript.params())
.script(substringScript.params())
.script(startScript.params())
.build(), dataType());
.script(sourceScript.params())
.script(substringScript.params())
.script(startScript.params())
.variable(isCaseSensitive())
.build(), dataType());
}
@Override
@ -125,7 +135,7 @@ public class IndexOf extends ScalarFunction implements OptionalArgument {
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
}
return new IndexOf(source(), newChildren.get(0), newChildren.get(1), newChildren.get(2));
return new IndexOf(source(), newChildren.get(0), newChildren.get(1), newChildren.get(2), configuration());
}
}
}

View File

@ -18,12 +18,14 @@ import java.util.Objects;
public class IndexOfFunctionPipe extends Pipe {
private final Pipe source, substring, start;
private final boolean isCaseSensitive;
public IndexOfFunctionPipe(Source source, Expression expression, Pipe src, Pipe substring, Pipe start) {
public IndexOfFunctionPipe(Source source, Expression expression, Pipe src, Pipe substring, Pipe start, boolean isCaseSensitive) {
super(source, expression, Arrays.asList(src, substring, start));
this.source = src;
this.substring = substring;
this.start = start;
this.isCaseSensitive = isCaseSensitive;
}
@Override
@ -56,7 +58,7 @@ public class IndexOfFunctionPipe extends Pipe {
}
protected Pipe replaceChildren(Pipe newSource, Pipe newSubstring, Pipe newStart) {
return new IndexOfFunctionPipe(source(), expression(), newSource, newSubstring, newStart);
return new IndexOfFunctionPipe(source(), expression(), newSource, newSubstring, newStart, isCaseSensitive);
}
@Override
@ -68,12 +70,12 @@ public class IndexOfFunctionPipe extends Pipe {
@Override
protected NodeInfo<IndexOfFunctionPipe> info() {
return NodeInfo.create(this, IndexOfFunctionPipe::new, expression(), source, substring, start);
return NodeInfo.create(this, IndexOfFunctionPipe::new, expression(), source, substring, start, isCaseSensitive);
}
@Override
public IndexOfFunctionProcessor asProcessor() {
return new IndexOfFunctionProcessor(source.asProcessor(), substring.asProcessor(), start.asProcessor());
return new IndexOfFunctionProcessor(source.asProcessor(), substring.asProcessor(), start.asProcessor(), isCaseSensitive);
}
public Pipe src() {
@ -90,7 +92,7 @@ public class IndexOfFunctionPipe extends Pipe {
@Override
public int hashCode() {
return Objects.hash(source, substring, start);
return Objects.hash(source, substring, start, isCaseSensitive);
}
@Override
@ -106,6 +108,7 @@ public class IndexOfFunctionPipe extends Pipe {
IndexOfFunctionPipe other = (IndexOfFunctionPipe) obj;
return Objects.equals(source, other.source)
&& Objects.equals(substring, other.substring)
&& Objects.equals(start, other.start);
&& Objects.equals(start, other.start)
&& Objects.equals(isCaseSensitive, other.isCaseSensitive);
}
}
}

View File

@ -21,17 +21,20 @@ public class IndexOfFunctionProcessor implements Processor {
private final Processor source;
private final Processor substring;
private final Processor start;
private final boolean isCaseSensitive;
public IndexOfFunctionProcessor(Processor source, Processor substring, Processor start) {
public IndexOfFunctionProcessor(Processor source, Processor substring, Processor start, boolean isCaseSensitive) {
this.source = source;
this.substring = substring;
this.start = start;
this.isCaseSensitive = isCaseSensitive;
}
public IndexOfFunctionProcessor(StreamInput in) throws IOException {
source = in.readNamedWriteable(Processor.class);
substring = in.readNamedWriteable(Processor.class);
start = in.readNamedWriteable(Processor.class);
isCaseSensitive = in.readBoolean();
}
@Override
@ -39,14 +42,15 @@ public class IndexOfFunctionProcessor implements Processor {
out.writeNamedWriteable(source);
out.writeNamedWriteable(substring);
out.writeNamedWriteable(start);
out.writeBoolean(isCaseSensitive);
}
@Override
public Object process(Object input) {
return doProcess(source.process(input), substring.process(input), start.process(input));
return doProcess(source.process(input), substring.process(input), start.process(input), isCaseSensitive());
}
public static Object doProcess(Object source, Object substring, Object start) {
public static Object doProcess(Object source, Object substring, Object start, boolean isCaseSensitive) {
if (source == null) {
return null;
}
@ -64,7 +68,14 @@ public class IndexOfFunctionProcessor implements Processor {
throw new EqlIllegalArgumentException("A number is required; received [{}]", start);
}
int startIndex = start == null ? 0 : ((Number) start).intValue();
int result = source.toString().toLowerCase(Locale.ROOT).indexOf(substring.toString().toLowerCase(Locale.ROOT), startIndex);
int result;
if (isCaseSensitive) {
result = source.toString().indexOf(substring.toString(), startIndex);
} else {
result = source.toString().toLowerCase(Locale.ROOT).indexOf(substring.toString().toLowerCase(Locale.ROOT), startIndex);
}
return result < 0 ? null : result;
}
@ -80,7 +91,11 @@ public class IndexOfFunctionProcessor implements Processor {
protected Processor start() {
return start;
}
protected boolean isCaseSensitive() {
return isCaseSensitive;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
@ -94,12 +109,13 @@ public class IndexOfFunctionProcessor implements Processor {
IndexOfFunctionProcessor other = (IndexOfFunctionProcessor) obj;
return Objects.equals(source(), other.source())
&& Objects.equals(substring(), other.substring())
&& Objects.equals(start(), other.start());
&& Objects.equals(start(), other.start())
&& Objects.equals(isCaseSensitive(), other.isCaseSensitive());
}
@Override
public int hashCode() {
return Objects.hash(source(), substring(), start());
return Objects.hash(source(), substring(), start(), isCaseSensitive());
}
@ -107,4 +123,4 @@ public class IndexOfFunctionProcessor implements Processor {
public String getWriteableName() {
return NAME;
}
}
}

View File

@ -41,12 +41,12 @@ public class InternalEqlScriptUtils extends InternalQlScriptUtils {
return (String) ConcatFunctionProcessor.doProcess(values);
}
public static Boolean endsWith(String s, String pattern) {
return (Boolean) EndsWithFunctionProcessor.doProcess(s, pattern);
public static Boolean endsWith(String s, String pattern, Boolean isCaseSensitive) {
return (Boolean) EndsWithFunctionProcessor.doProcess(s, pattern, isCaseSensitive);
}
public static Integer indexOf(String s, String substring, Number start) {
return (Integer) IndexOfFunctionProcessor.doProcess(s, substring, start);
public static Integer indexOf(String s, String substring, Number start, Boolean isCaseSensitive) {
return (Integer) IndexOfFunctionProcessor.doProcess(s, substring, start, isCaseSensitive);
}
public static Integer length(String s) {

View File

@ -68,8 +68,8 @@ class org.elasticsearch.xpack.eql.expression.function.scalar.whitelist.InternalE
String between(String, String, String, Boolean, Boolean)
Boolean cidrMatch(String, java.util.List)
String concat(java.util.List)
Boolean endsWith(String, String)
Integer indexOf(String, String, Number)
Boolean endsWith(String, String, Boolean)
Integer indexOf(String, String, Number, Boolean)
Integer length(String)
Number number(String, Number)
String string(Object)

View File

@ -10,7 +10,9 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.ql.QlIllegalArgumentException;
import org.elasticsearch.xpack.ql.expression.Literal;
import org.elasticsearch.xpack.ql.expression.LiteralTests;
import org.elasticsearch.xpack.ql.session.Configuration;
import static org.elasticsearch.xpack.eql.EqlTestUtils.randomConfigurationWithCaseSensitive;
import static org.elasticsearch.xpack.ql.expression.function.scalar.FunctionTestUtils.l;
import static org.elasticsearch.xpack.ql.tree.Source.EMPTY;
import static org.elasticsearch.xpack.ql.type.DataTypes.KEYWORD;
@ -18,37 +20,60 @@ import static org.hamcrest.Matchers.startsWith;
public class EndsWithProcessorTests extends ESTestCase {
public void testStartsWithFunctionWithValidInput() {
assertEquals(true, new EndsWith(EMPTY, l("foobarbar"), l("r")).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foobar"), l("foo")).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foo"), l("foobar")).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foobar"), l("")).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foo"), l("foo")).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foo"), l("oO")).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foo"), l("FOo")).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l('f'), l('f')).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l(""), l("bar")).makePipe().asProcessor().process(null));
assertEquals(null, new EndsWith(EMPTY, l(null), l("bar")).makePipe().asProcessor().process(null));
assertEquals(null, new EndsWith(EMPTY, l("foo"), l(null)).makePipe().asProcessor().process(null));
assertEquals(null, new EndsWith(EMPTY, l(null), l(null)).makePipe().asProcessor().process(null));
final Configuration caseInsensitive = randomConfigurationWithCaseSensitive(false);
public void testEndsWithFunctionWithValidInputCaseSensitive() {
final Configuration caseSensitive = randomConfigurationWithCaseSensitive(true);
assertEquals(true, new EndsWith(EMPTY, l("foobarbar"), l("r"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foobarbar"), l("R"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foobarbar"), l("bar"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foobarBar"), l("bar"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foobar"), l("foo"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foo"), l("foobar"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foobar"), l(""), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foo"), l("foo"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foo"), l("oO"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foo"), l("FOo"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l('f'), l('f'), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l(""), l("bar"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new EndsWith(EMPTY, l(null), l("bar"), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new EndsWith(EMPTY, l("foo"), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new EndsWith(EMPTY, l(null), l(null), caseSensitive).makePipe().asProcessor().process(null));
}
public void testStartsWithFunctionInputsValidation() {
public void testEndsWithFunctionWithValidInputCaseInsensitive() {
final Configuration config = randomConfigurationWithCaseSensitive(false);
assertEquals(true, new EndsWith(EMPTY, l("foobarbar"), l("r"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foobarbar"), l("R"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foobar"), l("foo"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l("foo"), l("foobar"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foobar"), l(""), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foo"), l("foo"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foo"), l("oO"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l("foo"), l("FOo"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(true, new EndsWith(EMPTY, l('f'), l('f'), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(false, new EndsWith(EMPTY, l(""), l("bar"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new EndsWith(EMPTY, l(null), l("bar"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new EndsWith(EMPTY, l("foo"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new EndsWith(EMPTY, l(null), l(null), caseInsensitive).makePipe().asProcessor().process(null));
}
public void testEndsWithFunctionInputsValidation() {
QlIllegalArgumentException siae = expectThrows(QlIllegalArgumentException.class,
() -> new EndsWith(EMPTY, l(5), l("foo")).makePipe().asProcessor().process(null));
() -> new EndsWith(EMPTY, l(5), l("foo"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(QlIllegalArgumentException.class,
() -> new EndsWith(EMPTY, l("bar"), l(false)).makePipe().asProcessor().process(null));
() -> new EndsWith(EMPTY, l("bar"), l(false), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals("A string/char is required; received [false]", siae.getMessage());
}
public void testStartsWithFunctionWithRandomInvalidDataType() {
public void testEndsWithFunctionWithRandomInvalidDataType() {
Literal literal = randomValueOtherThanMany(v -> v.dataType() == KEYWORD, () -> LiteralTests.randomLiteral());
QlIllegalArgumentException siae = expectThrows(QlIllegalArgumentException.class,
() -> new EndsWith(EMPTY, literal, l("foo")).makePipe().asProcessor().process(null));
() -> new EndsWith(EMPTY, literal, l("foo"), caseInsensitive).makePipe().asProcessor().process(null));
assertThat(siae.getMessage(), startsWith("A string/char is required; received"));
siae = expectThrows(QlIllegalArgumentException.class,
() -> new EndsWith(EMPTY, l("foo"), literal).makePipe().asProcessor().process(null));
() -> new EndsWith(EMPTY, l("foo"), literal, caseInsensitive).makePipe().asProcessor().process(null));
assertThat(siae.getMessage(), startsWith("A string/char is required; received"));
}
}
}

View File

@ -10,7 +10,9 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.ql.QlIllegalArgumentException;
import org.elasticsearch.xpack.ql.expression.Literal;
import org.elasticsearch.xpack.ql.expression.LiteralTests;
import org.elasticsearch.xpack.ql.session.Configuration;
import static org.elasticsearch.xpack.eql.EqlTestUtils.randomConfigurationWithCaseSensitive;
import static org.elasticsearch.xpack.ql.expression.function.scalar.FunctionTestUtils.l;
import static org.elasticsearch.xpack.ql.tree.Source.EMPTY;
import static org.elasticsearch.xpack.ql.type.DataTypes.KEYWORD;
@ -18,51 +20,77 @@ import static org.hamcrest.Matchers.startsWith;
public class IndexOfProcessorTests extends ESTestCase {
public void testStartsWithFunctionWithValidInput() {
assertEquals(5, new IndexOf(EMPTY, l("foobarbar"), l("r"), l(null)).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("foobar"), l("foo"), l(null)).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foo"), l("foobar"), l(null)).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("foo"), l("foo"), l(null)).makePipe().asProcessor().process(null));
assertEquals(1, new IndexOf(EMPTY, l("foo"), l("oO"), l(null)).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("foo"), l("FOo"), l(null)).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l('f'), l('f'), l(null)).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(""), l("bar"), l(1)).makePipe().asProcessor().process(null));
assertEquals(5, new IndexOf(EMPTY, l("foobarbar"), l("R"), l(5)).makePipe().asProcessor().process(null));
assertEquals(2, new IndexOf(EMPTY, l("foobar"), l("O"), l(2)).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foobar"), l("O"), l(3)).makePipe().asProcessor().process(null));
assertEquals(6, new IndexOf(EMPTY, l("foobarbaz"), l("ba"), l(4)).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(null), l("bar"), l(2)).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(null), l("bar"), l(2)).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foo"), l(null), l(3)).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(null), l(null), l(4)).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("bar"), l("bar"), l(null)).makePipe().asProcessor().process(null));
final Configuration caseInsensitive = randomConfigurationWithCaseSensitive(false);
public void testIndexOfFunctionWithValidInputInsensitive() {
assertEquals(5, new IndexOf(EMPTY, l("foobarbar"), l("r"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(5, new IndexOf(EMPTY, l("foobaRbar"), l("r"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("foobar"), l("Foo"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foo"), l("foobar"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("foo"), l("foo"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(1, new IndexOf(EMPTY, l("foo"), l("oO"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("foo"), l("FOo"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l('f'), l('f'), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(""), l("bar"), l(1), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(5, new IndexOf(EMPTY, l("foobarbar"), l("R"), l(5), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(2, new IndexOf(EMPTY, l("foobar"), l("O"), l(2), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foobar"), l("O"), l(3), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(6, new IndexOf(EMPTY, l("foobarbaz"), l("ba"), l(4), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(null), l("bar"), l(2), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(null), l("bar"), l(2), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foo"), l(null), l(3), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(null), l(null), l(4), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("bar"), l("bar"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
}
public void testStartsWithFunctionInputsValidation() {
public void testIndexOfFunctionWithValidInputSensitive() {
final Configuration caseSensitive = randomConfigurationWithCaseSensitive(true);
assertEquals(5, new IndexOf(EMPTY, l("foobarbar"), l("r"), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(8, new IndexOf(EMPTY, l("foobaRbar"), l("r"), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(4, new IndexOf(EMPTY, l("foobARbar"), l("AR"), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("foobar"), l("foo"), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foo"), l("foobar"), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("foo"), l("foo"), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foo"), l("oO"), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foo"), l("FOo"), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l('f'), l('f'), l(null), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(""), l("bar"), l(1), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foobarbar"), l("R"), l(5), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foobar"), l("O"), l(2), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foobar"), l("O"), l(3), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(6, new IndexOf(EMPTY, l("foobarbaz"), l("ba"), l(4), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(null), l("bar"), l(2), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(null), l("bar"), l(2), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l("foo"), l(null), l(3), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(null, new IndexOf(EMPTY, l(null), l(null), l(4), caseSensitive).makePipe().asProcessor().process(null));
assertEquals(0, new IndexOf(EMPTY, l("bar"), l("bar"), l(null), caseSensitive).makePipe().asProcessor().process(null));
}
public void testIndexOfFunctionInputsValidation() {
QlIllegalArgumentException siae = expectThrows(QlIllegalArgumentException.class,
() -> new IndexOf(EMPTY, l(5), l("foo"), l(null)).makePipe().asProcessor().process(null));
() -> new IndexOf(EMPTY, l(5), l("foo"), l(null), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(QlIllegalArgumentException.class,
() -> new IndexOf(EMPTY, l("bar"), l(false), l(2)).makePipe().asProcessor().process(null));
() -> new IndexOf(EMPTY, l("bar"), l(false), l(2), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals("A string/char is required; received [false]", siae.getMessage());
siae = expectThrows(QlIllegalArgumentException.class,
() -> new IndexOf(EMPTY, l("bar"), l("a"), l("1")).makePipe().asProcessor().process(null));
() -> new IndexOf(EMPTY, l("bar"), l("a"), l("1"), caseInsensitive).makePipe().asProcessor().process(null));
assertEquals("A number is required; received [1]", siae.getMessage());
}
public void testStartsWithFunctionWithRandomInvalidDataType() {
public void testIndexOfFunctionWithRandomInvalidDataType() {
Literal stringLiteral = randomValueOtherThanMany(v -> v.dataType() == KEYWORD, () -> LiteralTests.randomLiteral());
QlIllegalArgumentException siae = expectThrows(QlIllegalArgumentException.class,
() -> new IndexOf(EMPTY, stringLiteral, l("foo"), l(1)).makePipe().asProcessor().process(null));
() -> new IndexOf(EMPTY, stringLiteral, l("foo"), l(1), caseInsensitive).makePipe().asProcessor().process(null));
assertThat(siae.getMessage(), startsWith("A string/char is required; received"));
siae = expectThrows(QlIllegalArgumentException.class,
() -> new IndexOf(EMPTY, l("foo"), stringLiteral, l(2)).makePipe().asProcessor().process(null));
() -> new IndexOf(EMPTY, l("foo"), stringLiteral, l(2), caseInsensitive).makePipe().asProcessor().process(null));
assertThat(siae.getMessage(), startsWith("A string/char is required; received"));
Literal numericLiteral = randomValueOtherThanMany(v -> v.dataType().isNumeric(), () -> LiteralTests.randomLiteral());
siae = expectThrows(QlIllegalArgumentException.class,
() -> new IndexOf(EMPTY, l("foo"), l("o"), numericLiteral).makePipe().asProcessor().process(null));
() -> new IndexOf(EMPTY, l("foo"), l("o"), numericLiteral, caseInsensitive).makePipe().asProcessor().process(null));
assertThat(siae.getMessage(), startsWith("A number is required; received"));
}
}
}

View File

@ -102,24 +102,44 @@ process where cidrMatch(source_address, "10.0.0.0/8") != false
{"terms":{"source_address":["10.0.0.0/8"]
;
twoFunctionsEqualsBooleanLiterals
twoFunctionsEqualsBooleanLiterals-caseSensitive
process where endsWith(process_path, 'x') == true and endsWith(process_path, 'yx') == false
;
{"bool":{"must":[{"term":{"event.category":{"value":"process",
{"bool":{"must":[{"script":{"script":{"source":"InternalQlScriptUtils.nullSafeFilter(
InternalEqlScriptUtils.endsWith(InternalQlScriptUtils.docValue(doc,params.v0),params.v1))","lang":"painless",
"params":{"v0":"process_path","v1":"x"}}
{"script":{"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.not(
InternalEqlScriptUtils.endsWith(InternalQlScriptUtils.docValue(doc,params.v0),params.v1)))","lang":"painless",
"params":{"v0":"process_path","v1":"yx"}}
"bool":{"must":[{"term":{"event.category":{"value":"process"
"must":[{"script":{"script":{"source":"InternalQlScriptUtils.nullSafeFilter(
InternalEqlScriptUtils.endsWith(InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2))","lang":"painless",
"params":{"v0":"process_path","v1":"x","v2":true}}
"script":{"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.not(
InternalEqlScriptUtils.endsWith(InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2)))","lang":"painless",
"params":{"v0":"process_path","v1":"yx","v2":true}}
;
endsWithFunction
twoFunctionsEqualsBooleanLiterals-caseInsensitive
process where endsWith(process_path, 'x') == true and endsWith(process_path, 'yx') == false
;
"bool":{"must":[{"term":{"event.category":{"value":"process"
"must":[{"script":{"script":{"source":"InternalQlScriptUtils.nullSafeFilter(
InternalEqlScriptUtils.endsWith(InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2))","lang":"painless",
"params":{"v0":"process_path","v1":"x","v2":false}}
"script":{"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.not(
InternalEqlScriptUtils.endsWith(InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2)))","lang":"painless",
"params":{"v0":"process_path","v1":"yx","v2":false}}
;
endsWithFunction-caseSensitive
process where endsWith(user_name, 'c')
;
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalEqlScriptUtils.endsWith(
InternalQlScriptUtils.docValue(doc,params.v0),params.v1))",
"params":{"v0":"user_name","v1":"c"}
InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2))",
"params":{"v0":"user_name","v1":"c","v2":true}}
;
endsWithFunction-caseInsensitive
process where endsWith(user_name, 'c')
;
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalEqlScriptUtils.endsWith(
InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2))",
"params":{"v0":"user_name","v1":"c","v2":false}}
;
lengthFunctionWithExactSubField
@ -146,7 +166,7 @@ InternalEqlScriptUtils.length(InternalQlScriptUtils.docValue(doc,params.v0)),par
"params":{"v0":"constant_keyword","v1":5}
;
startsWithFunction-caseInSensitive
startsWithFunction-caseInsensitive
process where startsWith(user_name, 'A')
;
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.startsWith(
@ -185,14 +205,21 @@ InternalEqlScriptUtils.string(InternalQlScriptUtils.docValue(doc,params.v0)),par
"params":{"v0":"pid","v1":"123"}
;
indexOfFunction
indexOfFunction-caseSensitive
process where indexOf(user_name, 'A', 2) > 0
;
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.gt(
InternalEqlScriptUtils.indexOf(InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2),params.v3))",
"params":{"v0":"user_name","v1":"A","v2":2,"v3":0}
InternalEqlScriptUtils.indexOf(InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2,params.v3),params.v4))",
"params":{"v0":"user_name","v1":"A","v2":2,"v3":true,"v4":0}
;
indexOfFunction-caseInsensitive
process where indexOf(user_name, 'A', 2) > 0
;
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.gt(
InternalEqlScriptUtils.indexOf(InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2,params.v3),params.v4))",
"params":{"v0":"user_name","v1":"A","v2":2,"v3":false,"v4":0}
;
substringFunction
process where substring(file_name, -4) == '.exe'

View File

@ -394,6 +394,28 @@ public class FunctionRegistry {
T build(Source source, Expression src, Expression exp1, Expression exp2);
}
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
public static <T extends Function> FunctionDefinition def(Class<T> function,
ScalarTriFunctionConfigurationAwareBuilder<T> ctorRef, String... names) {
FunctionBuilder builder = (source, children, distinct, cfg) -> {
boolean hasMinimumTwo = OptionalArgument.class.isAssignableFrom(function);
if (hasMinimumTwo && (children.size() > 3 || children.size() < 2)) {
throw new QlIllegalArgumentException("expects two or three arguments");
} else if (!hasMinimumTwo && children.size() != 3) {
throw new QlIllegalArgumentException("expects exactly three arguments");
}
if (distinct) {
throw new QlIllegalArgumentException("does not support DISTINCT yet it was specified");
}
return ctorRef.build(source, children.get(0), children.get(1), children.size() == 3 ? children.get(2) : null, cfg);
};
return def(function, builder, false, names);
}
protected interface ScalarTriFunctionConfigurationAwareBuilder<T> {
T build(Source source, Expression exp1, Expression exp2, Expression exp3, Configuration configuration);
}
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
public static <T extends Function> FunctionDefinition def(Class<T> function,
FourParametersFunctionBuilder<T> ctorRef, String... names) {