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:
Ross Wolf 2020-05-13 14:07:37 -06:00
parent 5ca2ea2dde
commit 61e2cf89b5
No known key found for this signature in database
GPG Key ID: 6A4E50040D9A723A
12 changed files with 541 additions and 29 deletions

View File

@ -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"

View File

@ -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)'''

View File

@ -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"),
}
};
}

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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)

View File

@ -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'"));
}

View File

@ -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));
}
}

View File

@ -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()"));

View File

@ -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.*")
;