Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -824,13 +824,6 @@ file where opcode=0 and serial_event_id = 88 and startsWith('explorer.exeaAAAA',
expected_event_ids = [88]
description = "check built-in string functions"

[[queries]]
query = '''
file where opcode=0 and stringContains('ABCDEFGHIexplorer.exeJKLMNOP', file_name)
'''
expected_event_ids = [88]
description = "check built-in string functions"

[[queries]]
query = '''
file where opcode=0 and indexOf(file_name, 'plore') == 2 and not indexOf(file_name, '.pf')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package org.elasticsearch.xpack.eql.expression.function;

import org.elasticsearch.xpack.eql.expression.function.scalar.string.Substring;
import org.elasticsearch.xpack.eql.expression.function.scalar.stringcontains.StringContains;
import org.elasticsearch.xpack.ql.expression.function.FunctionDefinition;
import org.elasticsearch.xpack.ql.expression.function.FunctionRegistry;

Expand All @@ -23,6 +24,7 @@ private static FunctionDefinition[][] functions() {
// Scalar functions
// String
new FunctionDefinition[] {
def(StringContains.class, StringContains::new, "stringcontains"),
def(Substring.class, Substring::new, "substring"),
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,4 @@ public Expression replaceChildren(List<Expression> newChildren) {

return new Substring(source(), newChildren.get(0), newChildren.get(1), newChildren.get(2));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.eql.expression.function.scalar.stringcontains;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package I think should be org.elasticsearch.xpack.eql.expression.function.scalar.string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to the scalar.string package.


import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.expression.Expressions;
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.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.tree.NodeInfo;
import org.elasticsearch.xpack.ql.tree.Source;
import org.elasticsearch.xpack.ql.type.DataType;
import org.elasticsearch.xpack.ql.type.DataTypes;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import static java.lang.String.format;
import static org.elasticsearch.xpack.eql.expression.function.scalar.stringcontains.StringContainsFunctionProcessor.doProcess;
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isBoolean;
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isStringAndExact;
import static org.elasticsearch.xpack.ql.expression.gen.script.ParamsBuilder.paramsBuilder;

/**
* EQL specific stringContains function.
* https://eql.readthedocs.io/en/latest/query-guide/functions.html#stringContains
* stringContains(a, b)
* Returns true if b is a substring of a
*/
public class StringContains extends ScalarFunction implements OptionalArgument {

private final Expression haystack, needle, caseSensitive;

public StringContains(Source source, Expression haystack, Expression needle, Expression caseSensitive) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think (per a slack conversation) that we'll handle case-sensitivity separately, so no need for the argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, was not clear on this last week, so left it here for now. will remove as needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the optional parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I find haystack and needle inappropriate here. I know that it fits with the purpose of the function :-), but I prefer string and pattern or substring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed params to string and substring.

super(source, Arrays.asList(haystack, needle, toDefault(caseSensitive)));
this.haystack = haystack;
this.needle = needle;
this.caseSensitive = arguments().get(2);
}

private static Expression toDefault(Expression exp) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced this method is really needed. It's just a short one-liner... Maybe other reviewers thing otherwise, but I would keep using the ternary operator inline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the method since no more optional param.

return exp != null ? exp : Literal.FALSE;
}

@Override
protected TypeResolution resolveType() {
if (!childrenResolved()) {
return new TypeResolution("Unresolved children");
}

TypeResolution resolution = isStringAndExact(haystack, sourceText(), Expressions.ParamOrdinal.FIRST);
if (resolution.unresolved()) {
return resolution;
}

resolution = isStringAndExact(needle, sourceText(), Expressions.ParamOrdinal.SECOND);
if (resolution.unresolved()) {
return resolution;
}
return isBoolean(caseSensitive, sourceText(), Expressions.ParamOrdinal.THIRD);
}

@Override
protected Pipe makePipe() {
return new StringContainsFunctionPipe(source(), this,
Expressions.pipe(haystack), Expressions.pipe(needle), Expressions.pipe(caseSensitive));
}

@Override
public boolean foldable() {
return haystack.foldable() && needle.foldable() && caseSensitive.foldable();
}

@Override
public Object fold() {
return doProcess(haystack.fold(), needle.fold(), caseSensitive.fold());
}

@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, StringContains::new, haystack, needle, caseSensitive);
}

@Override
public ScriptTemplate asScript() {
ScriptTemplate haystackScript = asScript(haystack);
ScriptTemplate needleScript = asScript(needle);
ScriptTemplate caseSensitiveScript = asScript(caseSensitive);

return asScriptFrom(haystackScript, needleScript, caseSensitiveScript);
}

protected ScriptTemplate asScriptFrom(ScriptTemplate haystackScript, ScriptTemplate needleScript, ScriptTemplate caseSensitiveScript) {
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{eql}.%s(%s,%s,%s)"),
"stringContains",
haystackScript.template(),
needleScript.template(),
caseSensitiveScript.template()),
paramsBuilder()
.script(haystackScript.params())
.script(needleScript.params())
.script(caseSensitiveScript.params())
.build(), dataType());
}

@Override
public ScriptTemplate scriptWithField(FieldAttribute field) {
return new ScriptTemplate(processScript(Scripts.DOC_VALUE),
paramsBuilder().variable(field.exactAttribute().name()).build(),
dataType());
}

@Override
public DataType dataType() {
return DataTypes.BOOLEAN;
}

@Override
public Expression replaceChildren(List<Expression> newChildren) {
if (newChildren.size() != 3) {
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
}

return new StringContains(source(), newChildren.get(0), newChildren.get(1), newChildren.get(2));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.eql.expression.function.scalar.stringcontains;

import org.elasticsearch.xpack.ql.execution.search.QlSourceBuilder;
import org.elasticsearch.xpack.ql.expression.Expression;
import org.elasticsearch.xpack.ql.expression.gen.pipeline.Pipe;
import org.elasticsearch.xpack.ql.tree.NodeInfo;
import org.elasticsearch.xpack.ql.tree.Source;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

public class StringContainsFunctionPipe extends Pipe {

private final Pipe haystack, needle, caseSensitive;

public StringContainsFunctionPipe(Source source, Expression expression, Pipe haystack, Pipe needle, Pipe caseSensitive) {
super(source, expression, Arrays.asList(haystack, needle));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No caseSensitive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed caseSensitive param completely.

this.haystack = haystack;
this.needle = needle;
this.caseSensitive = caseSensitive;
}

@Override
public final Pipe replaceChildren(List<Pipe> newChildren) {
if (newChildren.size() != 3) {
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
}
return replaceChildren(newChildren.get(0), newChildren.get(1), newChildren.get(2));
}

@Override
public final Pipe resolveAttributes(AttributeResolver resolver) {
Pipe newHaystack = haystack.resolveAttributes(resolver);
Pipe newNeedle = needle.resolveAttributes(resolver);
Pipe newCaseSensitive = caseSensitive.resolveAttributes(resolver);
if (newHaystack == haystack && newNeedle == needle && newCaseSensitive == caseSensitive) {
return this;
}
return replaceChildren(newHaystack, newNeedle, newCaseSensitive);
}

@Override
public boolean supportedByAggsOnlyQuery() {
return haystack.supportedByAggsOnlyQuery() && needle.supportedByAggsOnlyQuery() && caseSensitive.supportedByAggsOnlyQuery();
}

@Override
public boolean resolved() {
return haystack.resolved() && needle.resolved() && caseSensitive.resolved();
}

protected Pipe replaceChildren(Pipe haystack, Pipe needle, Pipe caseSensitive) {
return new StringContainsFunctionPipe(source(), expression(), haystack, needle, caseSensitive);
}

@Override
public final void collectFields(QlSourceBuilder sourceBuilder) {
haystack.collectFields(sourceBuilder);
needle.collectFields(sourceBuilder);
caseSensitive.collectFields(sourceBuilder);
}

@Override
protected NodeInfo<StringContainsFunctionPipe> info() {
return NodeInfo.create(this, StringContainsFunctionPipe::new, expression(), haystack, needle, caseSensitive);
}

@Override
public StringContainsFunctionProcessor asProcessor() {
return new StringContainsFunctionProcessor(haystack.asProcessor(), needle.asProcessor(), caseSensitive.asProcessor());
}

public Pipe haystack() {
return haystack;
}

public Pipe needle() {
return needle;
}

public Pipe caseSensitive() {
return caseSensitive;
}

@Override
public int hashCode() {
return Objects.hash(source(), haystack(), needle(), caseSensitive());
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}

if (obj == null || getClass() != obj.getClass()) {
return false;
}

StringContainsFunctionPipe other = (StringContainsFunctionPipe) obj;
return Objects.equals(source(), other.source())
&& Objects.equals(haystack(), other.haystack())
&& Objects.equals(needle(), other.needle())
&& Objects.equals(caseSensitive(), other.caseSensitive());
}
}
Loading