From ec60335496a502c28c676becf476d18a172a4456 Mon Sep 17 00:00:00 2001 From: Aleksandr Maus Date: Wed, 10 Jun 2020 08:55:49 -0400 Subject: [PATCH] EQL: implement case sensitivity for indexOf and endsWith string functions (#57707) (#57908) * EQL: implement case sensitivity for indexOf and endsWith string functions --- .../function/scalar/string/EndsWith.java | 37 ++++---- .../scalar/string/EndsWithFunctionPipe.java | 17 ++-- .../string/EndsWithFunctionProcessor.java | 29 +++++-- .../function/scalar/string/IndexOf.java | 44 ++++++---- .../scalar/string/IndexOfFunctionPipe.java | 17 ++-- .../string/IndexOfFunctionProcessor.java | 32 +++++-- .../whitelist/InternalEqlScriptUtils.java | 8 +- .../xpack/eql/plugin/eql_whitelist.txt | 4 +- .../scalar/string/EndsWithProcessorTests.java | 67 ++++++++++----- .../scalar/string/IndexOfProcessorTests.java | 84 ++++++++++++------- .../src/test/resources/queryfolder_tests.txt | 57 +++++++++---- .../expression/function/FunctionRegistry.java | 22 +++++ 12 files changed, 287 insertions(+), 131 deletions(-) diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWith.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWith.java index 4eec4c39426..99d0a04c80a 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWith.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWith.java @@ -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 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()); } -} \ No newline at end of file +} diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionPipe.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionPipe.java index d69de471cee..48aef57cecf 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionPipe.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionPipe.java @@ -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 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); } -} \ No newline at end of file +} diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionProcessor.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionProcessor.java index b9896c35ced..ff0cb1ab5c3 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionProcessor.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionProcessor.java @@ -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; } -} \ No newline at end of file +} diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOf.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOf.java index da6efd041d8..1fd512d142b 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOf.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOf.java @@ -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 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()); } -} \ No newline at end of file +} diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionPipe.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionPipe.java index 22c3c6be91e..f0463134de9 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionPipe.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionPipe.java @@ -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 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); } -} \ No newline at end of file +} diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionProcessor.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionProcessor.java index 58f7c42a58f..93e2ac2b869 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionProcessor.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionProcessor.java @@ -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; } -} \ No newline at end of file +} diff --git a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/whitelist/InternalEqlScriptUtils.java b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/whitelist/InternalEqlScriptUtils.java index 94473308e1b..692f4641671 100644 --- a/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/whitelist/InternalEqlScriptUtils.java +++ b/x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/whitelist/InternalEqlScriptUtils.java @@ -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) { diff --git a/x-pack/plugin/eql/src/main/resources/org/elasticsearch/xpack/eql/plugin/eql_whitelist.txt b/x-pack/plugin/eql/src/main/resources/org/elasticsearch/xpack/eql/plugin/eql_whitelist.txt index 616a19873d5..bab2baa8ca6 100644 --- a/x-pack/plugin/eql/src/main/resources/org/elasticsearch/xpack/eql/plugin/eql_whitelist.txt +++ b/x-pack/plugin/eql/src/main/resources/org/elasticsearch/xpack/eql/plugin/eql_whitelist.txt @@ -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) diff --git a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithProcessorTests.java b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithProcessorTests.java index 0f6b4c15a16..bafc1b23338 100644 --- a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithProcessorTests.java +++ b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithProcessorTests.java @@ -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")); } -} \ No newline at end of file +} diff --git a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfProcessorTests.java b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfProcessorTests.java index 564a803a87f..61bf39a8c0f 100644 --- a/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfProcessorTests.java +++ b/x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfProcessorTests.java @@ -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")); } -} \ No newline at end of file +} diff --git a/x-pack/plugin/eql/src/test/resources/queryfolder_tests.txt b/x-pack/plugin/eql/src/test/resources/queryfolder_tests.txt index eb45d665088..1854d1961e2 100644 --- a/x-pack/plugin/eql/src/test/resources/queryfolder_tests.txt +++ b/x-pack/plugin/eql/src/test/resources/queryfolder_tests.txt @@ -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' diff --git a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/function/FunctionRegistry.java b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/function/FunctionRegistry.java index c5d52bab956..08f7547c7c3 100644 --- a/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/function/FunctionRegistry.java +++ b/x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/function/FunctionRegistry.java @@ -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 FunctionDefinition def(Class function, + ScalarTriFunctionConfigurationAwareBuilder 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 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 FunctionDefinition def(Class function, FourParametersFunctionBuilder ctorRef, String... names) {