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

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

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

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

View File

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

View File

@ -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",
@ -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 " + assertEquals("1:11: Cannot use field [multi_field_nested.dep_name] type [text] with unsupported nested type in hierarchy " +
"(field [multi_field_nested])", "(field [multi_field_nested])",
error(idxr, "foo where multi_field_nested.dep_name == 'bar'")); 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])", "hierarchy (field [multi_field_nested])",
error(idxr, "foo where multi_field_nested.dep_id.keyword == 'bar'")); 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])", "hierarchy (field [multi_field_nested])",
error(idxr, "foo where multi_field_nested.end_date == ''")); 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])", "hierarchy (field [multi_field_nested])",
error(idxr, "foo where multi_field_nested.start_date == 'bar'")); 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); "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()"));

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