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"'
|
||||
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]]
|
||||
expected_event_ids = [98]
|
||||
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]
|
||||
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]]
|
||||
query = '''
|
||||
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.StringContains;
|
||||
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.Wildcard;
|
||||
import org.elasticsearch.xpack.ql.expression.function.FunctionDefinition;
|
||||
|
@ -50,7 +51,7 @@ public class EqlFunctionRegistry extends FunctionRegistry {
|
|||
def(ToString.class, ToString::new, "string"),
|
||||
def(StringContains.class, StringContains::new, "stringcontains"),
|
||||
def(Substring.class, Substring::new, "substring"),
|
||||
def(Wildcard.class, Wildcard::new, "wildcard")
|
||||
def(Wildcard.class, Wildcard::new, "wildcard"),
|
||||
},
|
||||
// Arithmetic
|
||||
new FunctionDefinition[] {
|
||||
|
@ -58,7 +59,8 @@ public class EqlFunctionRegistry extends FunctionRegistry {
|
|||
def(Div.class, Div::new, "divide"),
|
||||
def(Mod.class, Mod::new, "modulo"),
|
||||
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.StringContainsFunctionProcessor;
|
||||
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.ql.expression.function.scalar.whitelist.InternalQlScriptUtils;
|
||||
|
||||
|
@ -55,6 +56,10 @@ public class InternalEqlScriptUtils extends InternalQlScriptUtils {
|
|||
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) {
|
||||
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)
|
||||
Integer indexOf(String, String, Number)
|
||||
Integer length(String)
|
||||
Number number(String, Number)
|
||||
String string(Object)
|
||||
Boolean stringContains(String, String)
|
||||
String substring(String, Number, Number)
|
||||
|
|
|
@ -116,12 +116,6 @@ public class VerifierTests extends ESTestCase {
|
|||
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
|
||||
public void testArrayIndexesUnsupported() {
|
||||
assertEquals("1:84: Array indexes are not supported",
|
||||
|
@ -323,13 +317,13 @@ public class VerifierTests extends ESTestCase {
|
|||
assertEquals("1:11: Cannot use field [multi_field_nested.dep_name] type [text] with unsupported nested type in hierarchy " +
|
||||
"(field [multi_field_nested])",
|
||||
error(idxr, "foo where multi_field_nested.dep_name == 'bar'"));
|
||||
assertEquals("1:11: Cannot use field [multi_field_nested.dep_id.keyword] type [keyword] with unsupported nested type in " +
|
||||
assertEquals("1:11: Cannot use field [multi_field_nested.dep_id.keyword] type [keyword] with unsupported nested type in " +
|
||||
"hierarchy (field [multi_field_nested])",
|
||||
error(idxr, "foo where multi_field_nested.dep_id.keyword == 'bar'"));
|
||||
assertEquals("1:11: Cannot use field [multi_field_nested.end_date] type [datetime] with unsupported nested type in " +
|
||||
assertEquals("1:11: Cannot use field [multi_field_nested.end_date] type [datetime] with unsupported nested type in " +
|
||||
"hierarchy (field [multi_field_nested])",
|
||||
error(idxr, "foo where multi_field_nested.end_date == ''"));
|
||||
assertEquals("1:11: Cannot use field [multi_field_nested.start_date] type [datetime] with unsupported nested type in " +
|
||||
assertEquals("1:11: Cannot use field [multi_field_nested.start_date] type [datetime] with unsupported nested type in " +
|
||||
"hierarchy (field [multi_field_nested])",
|
||||
error(idxr, "foo where multi_field_nested.start_date == 'bar'"));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
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() {
|
||||
QlIllegalArgumentException e = expectThrows(QlIllegalArgumentException.class,
|
||||
() -> 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);
|
||||
}
|
||||
|
||||
|
||||
public void testStringContainsWrongParams() {
|
||||
assertEquals("1:16: error building [stringcontains]: expects exactly two arguments",
|
||||
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+"
|
||||
;
|
||||
|
||||
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
|
||||
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+"}}
|
||||
;
|
||||
|
||||
|
||||
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
|
||||
process where wildcard(process_path, "*\\red_ttp\\wininit.*")
|
||||
;
|
||||
|
|
Loading…
Reference in New Issue