Browse Source

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

* EQL: implement case sensitivity for indexOf and endsWith string functions
Aleksandr Maus 5 years ago
parent
commit
e808026cb4
12 changed files with 287 additions and 131 deletions
  1. 23 14
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWith.java
  2. 10 7
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionPipe.java
  3. 21 8
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithFunctionProcessor.java
  4. 27 17
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOf.java
  5. 10 7
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionPipe.java
  6. 24 8
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfFunctionProcessor.java
  7. 4 4
      x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/expression/function/scalar/whitelist/InternalEqlScriptUtils.java
  8. 2 2
      x-pack/plugin/eql/src/main/resources/org/elasticsearch/xpack/eql/plugin/eql_whitelist.txt
  9. 46 21
      x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/EndsWithProcessorTests.java
  10. 56 28
      x-pack/plugin/eql/src/test/java/org/elasticsearch/xpack/eql/expression/function/scalar/string/IndexOfProcessorTests.java
  11. 42 15
      x-pack/plugin/eql/src/test/resources/queryfolder_tests.txt
  12. 22 0
      x-pack/plugin/ql/src/main/java/org/elasticsearch/xpack/ql/expression/function/FunctionRegistry.java

+ 23 - 14
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<? 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());
     }
 
-}
+}

+ 10 - 7
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<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);
     }
-}
+}

+ 21 - 8
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;
     }
-}
+}

+ 27 - 17
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<? 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());
     }
 
-}
+}

+ 10 - 7
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<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);
     }
-}
+}

+ 24 - 8
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;
     }
-}
+}

+ 4 - 4
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) {

+ 2 - 2
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)

+ 46 - 21
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 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 testStartsWithFunctionInputsValidation() {
+
+    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"));
     }
-}
+}

+ 56 - 28
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 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 testStartsWithFunctionInputsValidation() {
+
+    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"));
     }
-}
+}

+ 42 - 15
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'

+ 22 - 0
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 <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) {