EQL: Add number function (#55084)
* EQL: Add number function * EQL: Fix the locale used for number for deterministic functionality * EQL: Add more ToNumber tests * EQL: Add more number ToNumberProcessor unit tests * EQL: Remove unnecessary overrides, fix processor methods * EQL: Remove additional unnecessary overrides * EQL: Lint fixes for ToNumber * EQL: ToNumber renames from PR feedback * EQL: Remove NumberFormat locale handling * EQL: Removed NumberFormat from ToNumber * EQL: Add number function tests * EQL: ToNumberProcessorTests formatting * EQL: Remove newline in ToNumberProcessorTests * EQL: Add number(..., null) test * EQL: Create expression.function.scalar.math package * EQL: Remove painless whitespace for ToNumber.asScript * EQL: Add Long support
This commit is contained in:
parent
5ca2ea2dde
commit
61e2cf89b5
|
@ -51,6 +51,32 @@ expected_event_ids = [1, 2, 3, 4]
|
||||||
query = 'process where string(serial_event_id) = "1"'
|
query = 'process where string(serial_event_id) = "1"'
|
||||||
expected_event_ids = [1]
|
expected_event_ids = [1]
|
||||||
|
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
query = 'any where number(string(serial_event_id)) == 17'
|
||||||
|
expected_event_ids = [17]
|
||||||
|
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
query = 'any where number(string(serial_event_id), null) == 17'
|
||||||
|
expected_event_ids = [17]
|
||||||
|
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
query = 'any where number(string(serial_event_id), 10) == 17'
|
||||||
|
expected_event_ids = [17]
|
||||||
|
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
query = 'any where number(string(serial_event_id), 13) == number("31", 13)'
|
||||||
|
expected_event_ids = [31]
|
||||||
|
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
query = 'any where number(string(serial_event_id), 16) == 17'
|
||||||
|
expected_event_ids = [11]
|
||||||
|
|
||||||
|
|
||||||
[[queries]]
|
[[queries]]
|
||||||
expected_event_ids = [98]
|
expected_event_ids = [98]
|
||||||
notes = "regexp doesn't support character classes"
|
notes = "regexp doesn't support character classes"
|
||||||
|
|
|
@ -822,24 +822,8 @@ process where modulo(11, add(serial_event_id, 1)) == serial_event_id'''
|
||||||
expected_event_ids = [1, 2, 3, 5, 11]
|
expected_event_ids = [1, 2, 3, 5, 11]
|
||||||
description = "test built-in math functions"
|
description = "test built-in math functions"
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
query = '''
|
|
||||||
process where serial_event_id == number('5')'''
|
|
||||||
expected_event_ids = [5]
|
|
||||||
description = "test string/number conversions"
|
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
expected_event_ids = [50]
|
|
||||||
description = "test string/number conversions"
|
|
||||||
query = '''
|
|
||||||
process where serial_event_id == number('0x32', 16)'''
|
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
expected_event_ids = [50]
|
|
||||||
description = "test string/number conversions"
|
|
||||||
query = '''
|
|
||||||
process where serial_event_id == number('32', 16)'''
|
|
||||||
|
|
||||||
|
# this should be removed and the number function should be updated to take only strings
|
||||||
[[queries]]
|
[[queries]]
|
||||||
query = '''
|
query = '''
|
||||||
process where number(serial_event_id) == number(5)'''
|
process where number(serial_event_id) == number(5)'''
|
||||||
|
|
|
@ -16,6 +16,7 @@ import org.elasticsearch.xpack.eql.expression.function.scalar.string.Match;
|
||||||
import org.elasticsearch.xpack.eql.expression.function.scalar.string.StartsWith;
|
import org.elasticsearch.xpack.eql.expression.function.scalar.string.StartsWith;
|
||||||
import org.elasticsearch.xpack.eql.expression.function.scalar.string.StringContains;
|
import org.elasticsearch.xpack.eql.expression.function.scalar.string.StringContains;
|
||||||
import org.elasticsearch.xpack.eql.expression.function.scalar.string.Substring;
|
import org.elasticsearch.xpack.eql.expression.function.scalar.string.Substring;
|
||||||
|
import org.elasticsearch.xpack.eql.expression.function.scalar.math.ToNumber;
|
||||||
import org.elasticsearch.xpack.eql.expression.function.scalar.string.ToString;
|
import org.elasticsearch.xpack.eql.expression.function.scalar.string.ToString;
|
||||||
import org.elasticsearch.xpack.eql.expression.function.scalar.string.Wildcard;
|
import org.elasticsearch.xpack.eql.expression.function.scalar.string.Wildcard;
|
||||||
import org.elasticsearch.xpack.ql.expression.function.FunctionDefinition;
|
import org.elasticsearch.xpack.ql.expression.function.FunctionDefinition;
|
||||||
|
@ -50,7 +51,7 @@ public class EqlFunctionRegistry extends FunctionRegistry {
|
||||||
def(ToString.class, ToString::new, "string"),
|
def(ToString.class, ToString::new, "string"),
|
||||||
def(StringContains.class, StringContains::new, "stringcontains"),
|
def(StringContains.class, StringContains::new, "stringcontains"),
|
||||||
def(Substring.class, Substring::new, "substring"),
|
def(Substring.class, Substring::new, "substring"),
|
||||||
def(Wildcard.class, Wildcard::new, "wildcard")
|
def(Wildcard.class, Wildcard::new, "wildcard"),
|
||||||
},
|
},
|
||||||
// Arithmetic
|
// Arithmetic
|
||||||
new FunctionDefinition[] {
|
new FunctionDefinition[] {
|
||||||
|
@ -58,7 +59,8 @@ public class EqlFunctionRegistry extends FunctionRegistry {
|
||||||
def(Div.class, Div::new, "divide"),
|
def(Div.class, Div::new, "divide"),
|
||||||
def(Mod.class, Mod::new, "modulo"),
|
def(Mod.class, Mod::new, "modulo"),
|
||||||
def(Mul.class, Mul::new, "multiply"),
|
def(Mul.class, Mul::new, "multiply"),
|
||||||
def(Sub.class, Sub::new, "subtract")
|
def(ToNumber.class, ToNumber::new, "number"),
|
||||||
|
def(Sub.class, Sub::new, "subtract"),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* 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.math;
|
||||||
|
|
||||||
|
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.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.math.ToNumberFunctionProcessor.doProcess;
|
||||||
|
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isInteger;
|
||||||
|
import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isStringAndExact;
|
||||||
|
import static org.elasticsearch.xpack.ql.expression.gen.script.ParamsBuilder.paramsBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EQL specific function for parsing strings into numbers.
|
||||||
|
*/
|
||||||
|
public class ToNumber extends ScalarFunction implements OptionalArgument {
|
||||||
|
|
||||||
|
private final Expression value, base;
|
||||||
|
|
||||||
|
public ToNumber(Source source, Expression value, Expression base) {
|
||||||
|
super(source, Arrays.asList(value, base != null ? base : new Literal(source, null, DataTypes.NULL)));
|
||||||
|
this.value = value;
|
||||||
|
this.base = arguments().get(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TypeResolution resolveType() {
|
||||||
|
if (!childrenResolved()) {
|
||||||
|
return new TypeResolution("Unresolved children");
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeResolution valueResolution = isStringAndExact(value, sourceText(), ParamOrdinal.FIRST);
|
||||||
|
if (valueResolution.unresolved()) {
|
||||||
|
return valueResolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isInteger(base, sourceText(), ParamOrdinal.SECOND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Pipe makePipe() {
|
||||||
|
return new ToNumberFunctionPipe(source(), this, Expressions.pipe(value), Expressions.pipe(base));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean foldable() {
|
||||||
|
return value.foldable() && base.foldable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object fold() {
|
||||||
|
return doProcess(value.fold(), base.fold());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NodeInfo<? extends Expression> info() {
|
||||||
|
return NodeInfo.create(this, ToNumber::new, value, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScriptTemplate asScript() {
|
||||||
|
ScriptTemplate valueScript = asScript(value);
|
||||||
|
ScriptTemplate baseScript = asScript(base);
|
||||||
|
|
||||||
|
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{eql}.%s(%s,%s)"),
|
||||||
|
"number",
|
||||||
|
valueScript.template(),
|
||||||
|
baseScript.template()),
|
||||||
|
paramsBuilder()
|
||||||
|
.script(valueScript.params())
|
||||||
|
.script(baseScript.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.DOUBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Expression replaceChildren(List<Expression> newChildren) {
|
||||||
|
if (newChildren.size() != 2) {
|
||||||
|
throw new IllegalArgumentException("expected [2] children but received [" + newChildren.size() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ToNumber(source(), newChildren.get(0), newChildren.get(1));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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.math;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public class ToNumberFunctionPipe extends Pipe {
|
||||||
|
|
||||||
|
private final Pipe value, base;
|
||||||
|
|
||||||
|
public ToNumberFunctionPipe(Source source, Expression expression, Pipe value, Pipe base) {
|
||||||
|
super(source, expression, Arrays.asList(value, base));
|
||||||
|
this.value = value;
|
||||||
|
this.base = base;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Pipe replaceChildren(List<Pipe> newChildren) {
|
||||||
|
if (newChildren.size() != 2) {
|
||||||
|
throw new IllegalArgumentException("expected [2] children but received [" + newChildren.size() + "]");
|
||||||
|
}
|
||||||
|
return new ToNumberFunctionPipe(source(), expression(), newChildren.get(0), newChildren.get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NodeInfo<ToNumberFunctionPipe> info() {
|
||||||
|
return NodeInfo.create(this, ToNumberFunctionPipe::new, expression(), value, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ToNumberFunctionProcessor asProcessor() {
|
||||||
|
return new ToNumberFunctionProcessor(value.asProcessor(), base.asProcessor());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* 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.math;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.xpack.eql.EqlIllegalArgumentException;
|
||||||
|
import org.elasticsearch.xpack.ql.expression.gen.processor.Processor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ToNumberFunctionProcessor implements Processor {
|
||||||
|
|
||||||
|
public static final String NAME = "num";
|
||||||
|
|
||||||
|
private final Processor value, base;
|
||||||
|
|
||||||
|
public ToNumberFunctionProcessor(Processor value, Processor base) {
|
||||||
|
this.value = value;
|
||||||
|
this.base = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ToNumberFunctionProcessor(StreamInput in) throws IOException {
|
||||||
|
value = in.readNamedWriteable(Processor.class);
|
||||||
|
base = in.readNamedWriteable(Processor.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void writeTo(StreamOutput out) throws IOException {
|
||||||
|
out.writeNamedWriteable(value);
|
||||||
|
out.writeNamedWriteable(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object process(Object input) {
|
||||||
|
return doProcess(value.process(input), base.process(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Number parseDecimal(String source) {
|
||||||
|
try {
|
||||||
|
return Long.valueOf(source);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return Double.valueOf(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object doProcess(Object value, Object base) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(value instanceof String || value instanceof Character)) {
|
||||||
|
throw new EqlIllegalArgumentException("A string/char is required; received [{}]", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean detectedHexPrefix = value.toString().startsWith("0x");
|
||||||
|
|
||||||
|
if (base == null) {
|
||||||
|
base = detectedHexPrefix ? 16 : 10;
|
||||||
|
} else if (base instanceof Integer == false) {
|
||||||
|
throw new EqlIllegalArgumentException("An integer base is required; received [{}]", base);
|
||||||
|
}
|
||||||
|
|
||||||
|
int radix = (Integer) base;
|
||||||
|
|
||||||
|
if (detectedHexPrefix && radix == 16) {
|
||||||
|
value = value.toString().substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (radix == 10) {
|
||||||
|
return parseDecimal(value.toString());
|
||||||
|
} else {
|
||||||
|
return Long.parseLong(value.toString(), radix);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new EqlIllegalArgumentException("Unable to convert [{}] to number of base [{}]", value, radix);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Processor value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Processor base() {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToNumberFunctionProcessor other = (ToNumberFunctionProcessor) obj;
|
||||||
|
return Objects.equals(value(), other.value()) && Objects.equals(base(), other.base());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(value, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWriteableName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import org.elasticsearch.xpack.eql.expression.function.scalar.string.IndexOfFunc
|
||||||
import org.elasticsearch.xpack.eql.expression.function.scalar.string.LengthFunctionProcessor;
|
import org.elasticsearch.xpack.eql.expression.function.scalar.string.LengthFunctionProcessor;
|
||||||
import org.elasticsearch.xpack.eql.expression.function.scalar.string.StringContainsFunctionProcessor;
|
import org.elasticsearch.xpack.eql.expression.function.scalar.string.StringContainsFunctionProcessor;
|
||||||
import org.elasticsearch.xpack.eql.expression.function.scalar.string.SubstringFunctionProcessor;
|
import org.elasticsearch.xpack.eql.expression.function.scalar.string.SubstringFunctionProcessor;
|
||||||
|
import org.elasticsearch.xpack.eql.expression.function.scalar.math.ToNumberFunctionProcessor;
|
||||||
import org.elasticsearch.xpack.eql.expression.function.scalar.string.ToStringFunctionProcessor;
|
import org.elasticsearch.xpack.eql.expression.function.scalar.string.ToStringFunctionProcessor;
|
||||||
import org.elasticsearch.xpack.ql.expression.function.scalar.whitelist.InternalQlScriptUtils;
|
import org.elasticsearch.xpack.ql.expression.function.scalar.whitelist.InternalQlScriptUtils;
|
||||||
|
|
||||||
|
@ -55,6 +56,10 @@ public class InternalEqlScriptUtils extends InternalQlScriptUtils {
|
||||||
return (Boolean) StringContainsFunctionProcessor.doProcess(string, substring);
|
return (Boolean) StringContainsFunctionProcessor.doProcess(string, substring);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Number number(String source, Number base) {
|
||||||
|
return (Number) ToNumberFunctionProcessor.doProcess(source, base);
|
||||||
|
}
|
||||||
|
|
||||||
public static String substring(String s, Number start, Number end) {
|
public static String substring(String s, Number start, Number end) {
|
||||||
return (String) SubstringFunctionProcessor.doProcess(s, start, end);
|
return (String) SubstringFunctionProcessor.doProcess(s, start, end);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ class org.elasticsearch.xpack.eql.expression.function.scalar.whitelist.InternalE
|
||||||
Boolean endsWith(String, String)
|
Boolean endsWith(String, String)
|
||||||
Integer indexOf(String, String, Number)
|
Integer indexOf(String, String, Number)
|
||||||
Integer length(String)
|
Integer length(String)
|
||||||
|
Number number(String, Number)
|
||||||
String string(Object)
|
String string(Object)
|
||||||
Boolean stringContains(String, String)
|
Boolean stringContains(String, String)
|
||||||
String substring(String, Number, Number)
|
String substring(String, Number, Number)
|
||||||
|
|
|
@ -116,12 +116,6 @@ public class VerifierTests extends ESTestCase {
|
||||||
error("network where safe(process_name)"));
|
error("network where safe(process_name)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the known EQL functions that are not supported
|
|
||||||
public void testFunctionVerificationUnknown() {
|
|
||||||
assertEquals("1:34: Unknown function [number]",
|
|
||||||
error("process where serial_event_id == number('5')"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test unsupported array indexes
|
// Test unsupported array indexes
|
||||||
public void testArrayIndexesUnsupported() {
|
public void testArrayIndexesUnsupported() {
|
||||||
assertEquals("1:84: Array indexes are not supported",
|
assertEquals("1:84: Array indexes are not supported",
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
/*
|
||||||
|
* 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.math;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.xpack.ql.QlIllegalArgumentException;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.ql.expression.function.scalar.FunctionTestUtils.l;
|
||||||
|
import static org.elasticsearch.xpack.ql.tree.Source.EMPTY;
|
||||||
|
|
||||||
|
|
||||||
|
public class ToNumberProcessorTests extends ESTestCase {
|
||||||
|
|
||||||
|
private static Object process(Object value, Object base) {
|
||||||
|
return new ToNumber(EMPTY, l(value), l(base)).makePipe().asProcessor().process(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String error(Object value, Object base) {
|
||||||
|
QlIllegalArgumentException saie = expectThrows(QlIllegalArgumentException.class,
|
||||||
|
() -> new ToNumber(EMPTY, l(value), l(base)).makePipe().asProcessor().process(null));
|
||||||
|
return saie.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithLongRange() {
|
||||||
|
long number = randomLongBetween(Integer.MAX_VALUE, Long.MAX_VALUE);
|
||||||
|
|
||||||
|
assertEquals(number, process(Long.toString(number), null));
|
||||||
|
assertEquals(number, process("0x" + Long.toHexString(number), null));
|
||||||
|
|
||||||
|
assertEquals(number, process(Long.toString(number), 10));
|
||||||
|
assertEquals(number, process(Long.toOctalString(number), 8));
|
||||||
|
assertEquals(number, process(Long.toHexString(number), 16));
|
||||||
|
assertEquals(number, process("0x" + Long.toHexString(number), 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithPositiveInteger() {
|
||||||
|
int number = randomIntBetween(0, 1000);
|
||||||
|
|
||||||
|
assertEquals(number, process(Integer.toString(number), null));
|
||||||
|
assertEquals(number, process("0x" + Integer.toHexString(number), null));
|
||||||
|
|
||||||
|
assertEquals(number, process(Integer.toString(number), 10));
|
||||||
|
assertEquals(number, process(Integer.toOctalString(number), 8));
|
||||||
|
assertEquals(number, process(Integer.toHexString(number), 16));
|
||||||
|
assertEquals(number, process("0x" + Integer.toHexString(number), 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithNegativeInteger() {
|
||||||
|
int posInt = randomIntBetween(1, 1000);
|
||||||
|
int negInt = -posInt;
|
||||||
|
|
||||||
|
assertEquals(negInt, process(Integer.toString(negInt), null));
|
||||||
|
|
||||||
|
assertEquals(negInt, process(Integer.toString(negInt), 10));
|
||||||
|
assertEquals(negInt, process("-" + Integer.toOctalString(posInt), 8));
|
||||||
|
assertEquals(negInt, process("-" + Integer.toHexString(posInt), 16));
|
||||||
|
|
||||||
|
assertEquals(negInt, process("-0x" + Integer.toHexString(posInt), 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithPositiveFloat() {
|
||||||
|
double number = randomDoubleBetween(0.0, 1000.0, true);
|
||||||
|
|
||||||
|
assertEquals(number, process(Double.toString(number), null));
|
||||||
|
assertEquals(number, process(Double.toString(number), 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithNegativeFloat() {
|
||||||
|
double number = randomDoubleBetween(-1000.0, -0.1, true);
|
||||||
|
|
||||||
|
assertEquals(number, process(Double.toString(number), null));
|
||||||
|
assertEquals(number, process(Double.toString(number), 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithMissingInput() {
|
||||||
|
assertNull(process(null, null));
|
||||||
|
assertNull(process(null, 8));
|
||||||
|
assertNull(process(null, 10));
|
||||||
|
assertNull(process(null, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithPositiveExponent() {
|
||||||
|
int number = randomIntBetween(-100, 100);
|
||||||
|
int exponent = randomIntBetween(0, 20);
|
||||||
|
|
||||||
|
double expected = Math.pow((double) number, (double) exponent);
|
||||||
|
|
||||||
|
assertEquals(expected, process(number + "e" + exponent, null));
|
||||||
|
assertEquals(expected, process(number + "e" + exponent, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithNegativeExponent() {
|
||||||
|
int number = randomIntBetween(-100, 100);
|
||||||
|
int exponent = randomIntBetween(-10, -1);
|
||||||
|
|
||||||
|
double expected = Math.pow(number, exponent);
|
||||||
|
|
||||||
|
assertEquals(expected, process(number + "e-" + exponent, null));
|
||||||
|
assertEquals(expected, process(number + "e-" + exponent, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithLocales() {
|
||||||
|
assertEquals("Unable to convert [1,000] to number of base [10]",
|
||||||
|
error("1,000", 7));
|
||||||
|
assertEquals("Unable to convert [1,000] to number of base [10]",
|
||||||
|
error("1,000,000", 7));
|
||||||
|
assertEquals("Unable to convert [1,000] to number of base [10]",
|
||||||
|
error("1.000.000", 7));
|
||||||
|
assertEquals("Unable to convert [1,000] to number of base [10]",
|
||||||
|
error("1,000.000.000", 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toNumberWithUnsupportedDoubleBase() {
|
||||||
|
// test that only base 10 fractions are supported
|
||||||
|
double decimal = randomDouble();
|
||||||
|
assertEquals("Unable to convert [1.0] to number of base [7]",
|
||||||
|
error(Double.toString(decimal), 7));
|
||||||
|
assertEquals("Unable to convert [1.0] to number of base [8]",
|
||||||
|
error(Double.toString(decimal), 8));
|
||||||
|
assertEquals("Unable to convert [1.0] to number of base [16]",
|
||||||
|
error(Double.toString(decimal), 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNegativeBase16() {
|
||||||
|
assertEquals("Unable to convert [-0x1] to number of base [16]",
|
||||||
|
error("-0x1", 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNumberInvalidDataType() {
|
||||||
|
assertEquals("A string/char is required; received [false]",
|
||||||
|
error(false, null));
|
||||||
|
assertEquals("A string/char is required; received [1.0]",
|
||||||
|
error(1.0, null));
|
||||||
|
assertEquals("A string/char is required; received [1]",
|
||||||
|
error(1, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInvalidBase() {
|
||||||
|
int number = randomIntBetween(-100, 100);
|
||||||
|
|
||||||
|
assertEquals("An integer base is required; received [foo]",
|
||||||
|
error(Integer.toString(number), "foo"));
|
||||||
|
assertEquals("An integer base is required; received [1.0]",
|
||||||
|
error(Integer.toString(number), 1.0));
|
||||||
|
assertEquals("An integer base is required; received [false]",
|
||||||
|
error(Integer.toString(number), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testInvalidSourceString() {
|
||||||
|
assertEquals("Unable to convert [] to number of base [10]",
|
||||||
|
error("", null));
|
||||||
|
assertEquals("Unable to convert [] to number of base [16]",
|
||||||
|
error("", 16));
|
||||||
|
assertEquals("Unable to convert [foo] to number of base [10]",
|
||||||
|
error("foo", null));
|
||||||
|
assertEquals("Unable to convert [foo] to number of base [16]",
|
||||||
|
error("foo", 16));
|
||||||
|
assertEquals("Unable to convert [1.2.3.4] to number of base [10]",
|
||||||
|
error("1.2.3.4", 10));
|
||||||
|
assertEquals("Unable to convert [1.2.3.4] to number of base [16]",
|
||||||
|
error("1.2.3.4", 16));
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,32 @@ public class QueryFolderFailTests extends AbstractQueryFolderTestCase {
|
||||||
"must be [string], found value [1] type [integer]", msg);
|
"must be [string], found value [1] type [integer]", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testNumberFunctionAlreadyNumber() {
|
||||||
|
VerificationException e = expectThrows(VerificationException.class,
|
||||||
|
() -> plan("process where number(pid) == 1"));
|
||||||
|
String msg = e.getMessage();
|
||||||
|
assertEquals("Found 1 problem\nline 1:15: first argument of [number(pid)] must be [string], "
|
||||||
|
+ "found value [pid] type [long]", msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNumberFunctionFloatBase() {
|
||||||
|
VerificationException e = expectThrows(VerificationException.class,
|
||||||
|
() -> plan("process where number(process_name, 1.0) == 1"));
|
||||||
|
String msg = e.getMessage();
|
||||||
|
assertEquals("Found 1 problem\nline 1:15: second argument of [number(process_name, 1.0)] must be [integer], "
|
||||||
|
+ "found value [1.0] type [double]", msg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testNumberFunctionNonString() {
|
||||||
|
VerificationException e = expectThrows(VerificationException.class,
|
||||||
|
() -> plan("process where number(plain_text) == 1"));
|
||||||
|
String msg = e.getMessage();
|
||||||
|
assertEquals("Found 1 problem\nline 1:15: [number(plain_text)] cannot operate on first argument field of data type "
|
||||||
|
+ "[text]: No keyword/multi-field defined exact matches for [plain_text]; define one or use MATCH/QUERY instead", msg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void testPropertyEquationFilterUnsupported() {
|
public void testPropertyEquationFilterUnsupported() {
|
||||||
QlIllegalArgumentException e = expectThrows(QlIllegalArgumentException.class,
|
QlIllegalArgumentException e = expectThrows(QlIllegalArgumentException.class,
|
||||||
() -> plan("process where (serial_event_id<9 and serial_event_id >= 7) or (opcode == pid)"));
|
() -> plan("process where (serial_event_id<9 and serial_event_id >= 7) or (opcode == pid)"));
|
||||||
|
@ -191,7 +217,6 @@ public class QueryFolderFailTests extends AbstractQueryFolderTestCase {
|
||||||
+ "[text]: No keyword/multi-field defined exact matches for [plain_text]; define one or use MATCH/QUERY instead", msg);
|
+ "[text]: No keyword/multi-field defined exact matches for [plain_text]; define one or use MATCH/QUERY instead", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void testStringContainsWrongParams() {
|
public void testStringContainsWrongParams() {
|
||||||
assertEquals("1:16: error building [stringcontains]: expects exactly two arguments",
|
assertEquals("1:16: error building [stringcontains]: expects exactly two arguments",
|
||||||
errorParsing("process where stringContains()"));
|
errorParsing("process where stringContains()"));
|
||||||
|
|
|
@ -257,6 +257,12 @@ process where match(command_line, "^.*?net.exe", "net\\.exe", "C:\\\\Windows\\\\
|
||||||
"regexp":{"command_line":{"value":"^.*?net.exe|net\\.exe|C:\\\\Windows\\\\system32\\\\net1\\s+"
|
"regexp":{"command_line":{"value":"^.*?net.exe|net\\.exe|C:\\\\Windows\\\\system32\\\\net1\\s+"
|
||||||
;
|
;
|
||||||
|
|
||||||
|
numberFunctionSingleArgument
|
||||||
|
process where number(process_name) == 1;
|
||||||
|
InternalEqlScriptUtils.number(InternalQlScriptUtils.docValue(doc,params.v0),params.v1)
|
||||||
|
"params":{"v0":"process_name","v1":null,"v2":1}
|
||||||
|
;
|
||||||
|
|
||||||
matchFunctionScalar
|
matchFunctionScalar
|
||||||
process where match(substring(command_line, 5), "^.*?net.exe", "net\\.exe", "C:\\\\Windows\\\\system32\\\\net1\\s+")
|
process where match(substring(command_line, 5), "^.*?net.exe", "net\\.exe", "C:\\\\Windows\\\\system32\\\\net1\\s+")
|
||||||
;
|
;
|
||||||
|
@ -265,6 +271,30 @@ InternalQlScriptUtils.docValue(doc,params.v0),params.v1,params.v2),params.v3))",
|
||||||
"params":{"v0":"command_line","v1":5,"v2":null,"v3":"^.*?net.exe|net\\.exe|C:\\\\Windows\\\\system32\\\\net1\\s+"}}
|
"params":{"v0":"command_line","v1":5,"v2":null,"v3":"^.*?net.exe|net\\.exe|C:\\\\Windows\\\\system32\\\\net1\\s+"}}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
|
numberFunctionTwoFieldArguments
|
||||||
|
process where number(process_name, pid) != null;
|
||||||
|
InternalEqlScriptUtils.number(InternalQlScriptUtils.docValue(doc,params.v0),InternalQlScriptUtils.docValue(doc,params.v1))))",
|
||||||
|
"params":{"v0":"process_name","v1":"pid"}
|
||||||
|
;
|
||||||
|
|
||||||
|
numberFunctionTwoArguments
|
||||||
|
process where number(process_name, 16) != null;
|
||||||
|
InternalEqlScriptUtils.number(InternalQlScriptUtils.docValue(doc,params.v0),params.v1)
|
||||||
|
"params":{"v0":"process_name","v1":16}
|
||||||
|
;
|
||||||
|
|
||||||
|
numberFunctionFoldedComparison
|
||||||
|
process where serial_event_id == number("-32.5");
|
||||||
|
{"term":{"serial_event_id":{"value":-32.5,
|
||||||
|
;
|
||||||
|
|
||||||
|
numberFunctionFoldedHexComparison
|
||||||
|
process where serial_event_id == number("0x32", 16);
|
||||||
|
{"term":{"serial_event_id":{"value":50,
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
wildcardFunctionSingleArgument
|
wildcardFunctionSingleArgument
|
||||||
process where wildcard(process_path, "*\\red_ttp\\wininit.*")
|
process where wildcard(process_path, "*\\red_ttp\\wininit.*")
|
||||||
;
|
;
|
||||||
|
|
Loading…
Reference in New Issue