-
Notifications
You must be signed in to change notification settings - Fork 25.2k
EQL: Add string function #54470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EQL: Add string function #54470
Changes from 2 commits
0b310e3
17dd8d3
ee2ab21
169dc2a
de21d44
4c66c87
8b5fac0
a037dda
509ffd9
9ca95a6
25f9e6b
dedf571
2f08b0c
bd9ecce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/* | ||
* 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.string; | ||
|
||
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.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.Collections; | ||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
import static java.lang.String.format; | ||
import static org.elasticsearch.xpack.eql.expression.function.scalar.string.ToStringFunctionProcessor.doProcess; | ||
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isExact; | ||
import static org.elasticsearch.xpack.ql.expression.gen.script.ParamsBuilder.paramsBuilder; | ||
|
||
/** | ||
* EQL specific string function that wraps object.toString. | ||
*/ | ||
public class ToString extends ScalarFunction { | ||
|
||
private final Expression source; | ||
|
||
public ToString(Source source, Expression src) { | ||
super(source, Collections.singletonList(src)); | ||
this.source = src; | ||
} | ||
|
||
@Override | ||
protected TypeResolution resolveType() { | ||
if (!childrenResolved()) { | ||
return new TypeResolution("Unresolved children"); | ||
} | ||
|
||
return isExact(source, sourceText(), ParamOrdinal.FIRST); | ||
} | ||
|
||
@Override | ||
protected Pipe makePipe() { | ||
return new ToStringFunctionPipe(source(), this, Expressions.pipe(source)); | ||
} | ||
|
||
@Override | ||
public boolean foldable() { | ||
return source.foldable(); | ||
} | ||
|
||
@Override | ||
public Object fold() { | ||
return doProcess(source.fold()); | ||
} | ||
|
||
@Override | ||
protected NodeInfo<? extends Expression> info() { | ||
return NodeInfo.create(this, ToString::new, source); | ||
} | ||
|
||
@Override | ||
public ScriptTemplate asScript() { | ||
ScriptTemplate sourceScript = asScript(source); | ||
|
||
return asScriptFrom(sourceScript); | ||
} | ||
|
||
protected ScriptTemplate asScriptFrom(ScriptTemplate sourceScript) { | ||
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{eql}.%s(%s)"), | ||
"string", | ||
sourceScript.template()), | ||
paramsBuilder() | ||
.script(sourceScript.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.KEYWORD; | ||
} | ||
|
||
@Override | ||
public Expression replaceChildren(List<Expression> newChildren) { | ||
if (newChildren.size() != 1) { | ||
throw new IllegalArgumentException("expected [1] children but received [" + newChildren.size() + "]"); | ||
} | ||
|
||
return new ToString(source(), newChildren.get(0)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* 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.string; | ||
|
||
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.Collections; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
public class ToStringFunctionPipe extends Pipe { | ||
|
||
private final Pipe source; | ||
|
||
public ToStringFunctionPipe(Source source, Expression expression, Pipe src) { | ||
super(source, expression, Collections.singletonList(src)); | ||
this.source = src; | ||
} | ||
|
||
@Override | ||
public final Pipe replaceChildren(List<Pipe> newChildren) { | ||
if (newChildren.size() != 1) { | ||
throw new IllegalArgumentException("expected [1] children but received [" + newChildren.size() + "]"); | ||
} | ||
return new ToStringFunctionPipe(source(), expression(), newChildren.get(0)); | ||
} | ||
|
||
@Override | ||
public final Pipe resolveAttributes(AttributeResolver resolver) { | ||
Pipe newSource = source.resolveAttributes(resolver); | ||
if (newSource == source) { | ||
return this; | ||
} | ||
return replaceChildren(Collections.singletonList(newSource)); | ||
} | ||
|
||
@Override | ||
public boolean supportedByAggsOnlyQuery() { | ||
return source.supportedByAggsOnlyQuery(); | ||
} | ||
|
||
@Override | ||
public boolean resolved() { | ||
return source.resolved(); | ||
} | ||
|
||
@Override | ||
public final void collectFields(QlSourceBuilder sourceBuilder) { | ||
source.collectFields(sourceBuilder); | ||
} | ||
|
||
@Override | ||
protected NodeInfo<ToStringFunctionPipe> info() { | ||
return NodeInfo.create(this, ToStringFunctionPipe::new, expression(), source); | ||
} | ||
|
||
@Override | ||
public ToStringFunctionProcessor asProcessor() { | ||
return new ToStringFunctionProcessor(source.asProcessor()); | ||
} | ||
|
||
public Pipe src() { | ||
return source; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(source); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (this == obj) { | ||
return true; | ||
} | ||
|
||
if (obj == null || getClass() != obj.getClass()) { | ||
return false; | ||
} | ||
|
||
ToStringFunctionPipe other = (ToStringFunctionPipe) obj; | ||
return Objects.equals(source, other.source); | ||
rw-access marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* 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.string; | ||
|
||
import org.elasticsearch.common.io.stream.StreamInput; | ||
import org.elasticsearch.common.io.stream.StreamOutput; | ||
import org.elasticsearch.xpack.ql.expression.gen.processor.Processor; | ||
|
||
import java.io.IOException; | ||
import java.util.Objects; | ||
|
||
public class ToStringFunctionProcessor implements Processor { | ||
|
||
public static final String NAME = "sstr"; | ||
|
||
private final Processor source; | ||
|
||
public ToStringFunctionProcessor(Processor source) { | ||
this.source = source; | ||
} | ||
|
||
public ToStringFunctionProcessor(StreamInput in) throws IOException { | ||
source = in.readNamedWriteable(Processor.class); | ||
} | ||
|
||
@Override | ||
public final void writeTo(StreamOutput out) throws IOException { | ||
out.writeNamedWriteable(source); | ||
} | ||
|
||
@Override | ||
public Object process(Object input) { | ||
return doProcess(source.process(input)); | ||
} | ||
|
||
public static Object doProcess(Object source) { | ||
return source == null ? "null" : source.toString(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. per #54465 and https://github.com/endgameinc/eql/blob/master/eql/functions.py#L626 I was following a contract (we can change it, of course) that this function always returns a string. So I catch >>> import eql
>>> eql.parse_expression("string(null)")
String(value='None') There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're not using some concept of |
||
} | ||
|
||
protected Processor source() { | ||
return source; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (this == obj) { | ||
return true; | ||
} | ||
|
||
if (obj == null || getClass() != obj.getClass()) { | ||
return false; | ||
} | ||
|
||
ToStringFunctionProcessor other = (ToStringFunctionProcessor) obj; | ||
return Objects.equals(source(), other.source()); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(source()); | ||
} | ||
|
||
|
||
@Override | ||
public String getWriteableName() { | ||
return NAME; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename
source
to something else (delegate, target, value?) since it conflicts withSource source
convention. Whilesource
was used in a couple of Processors, it was mainly to follow the function delegation and their official param names.This applies to the whole PR.