* EQL: implement math functions: add, divide, module, multiply, subtract
This commit is contained in:
parent
c1b0548db0
commit
ad54cca823
|
@ -17,3 +17,40 @@ file where between(file_path, "dev", ".json", true) == "\\TestLogs\\something"
|
||||||
[[queries]]
|
[[queries]]
|
||||||
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]]
|
||||||
|
# Basic test for modulo function
|
||||||
|
query = '''
|
||||||
|
process where modulo(11, 10) == serial_event_id'''
|
||||||
|
expected_event_ids = [1]
|
||||||
|
description = "test built-in modulo math functions"
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
# This query give a different result with ES EQL implementation because it doesn't convert to float data types for division
|
||||||
|
expected_event_ids = [82, 83]
|
||||||
|
query = "file where serial_event_id / 2 == 41"
|
||||||
|
|
||||||
|
# Additional EQL queries with arithmetic operations that were not part of the original EQL implementation
|
||||||
|
[[queries]]
|
||||||
|
expected_event_ids = [82]
|
||||||
|
query = "file where 83 - serial_event_id == 1"
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
expected_event_ids = [82]
|
||||||
|
query = "file where 1 + serial_event_id == 83"
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
expected_event_ids = [82]
|
||||||
|
query = "file where -serial_event_id + 100 == 18"
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
expected_event_ids = [82]
|
||||||
|
query = "file where 2 * serial_event_id == 164"
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
expected_event_ids = [66]
|
||||||
|
query = "file where 66.0 / serial_event_id == 1"
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
expected_event_ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 46]
|
||||||
|
query = "process where serial_event_id + ((1 + 3) * 2 / (3 - 1)) * 2 == 54 or 70 + serial_event_id < 100"
|
||||||
|
|
|
@ -826,24 +826,12 @@ expected_event_ids = []
|
||||||
description = "check built-in string functions"
|
description = "check built-in string functions"
|
||||||
|
|
||||||
[[queries]]
|
[[queries]]
|
||||||
query = '''
|
# This query give a different result with ES EQL implementation because it doesn't convert to float data types for division
|
||||||
process where add(serial_event_id, 0) == 1 and add(0, 1) == serial_event_id'''
|
expected_event_ids = [82]
|
||||||
expected_event_ids = [1]
|
query = "file where serial_event_id / 2 == 41"
|
||||||
description = "test built-in math functions"
|
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
query = '''
|
|
||||||
process where subtract(serial_event_id, -5) == 6'''
|
|
||||||
expected_event_ids = [1]
|
|
||||||
description = "test built-in math functions"
|
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
query = '''
|
|
||||||
process where multiply(6, serial_event_id) == 30 and divide(30, 4.0) == 7.5'''
|
|
||||||
expected_event_ids = [5]
|
|
||||||
description = "test built-in math functions"
|
|
||||||
|
|
||||||
[[queries]]
|
[[queries]]
|
||||||
|
# Error: Line 1:42: Comparisons against variables are not (currently) supported; offender [serial_event_id] in [==]
|
||||||
query = '''
|
query = '''
|
||||||
process where modulo(11, add(serial_event_id, 1)) == serial_event_id'''
|
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]
|
||||||
|
@ -1002,26 +990,6 @@ query = '''
|
||||||
registry where arrayContains(bytes_written_string_list, "missing", "en-US")
|
registry where arrayContains(bytes_written_string_list, "missing", "en-US")
|
||||||
'''
|
'''
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
expected_event_ids = [82]
|
|
||||||
query = "file where serial_event_id - 1 == 81"
|
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
expected_event_ids = [82]
|
|
||||||
query = "file where serial_event_id + 1 == 83"
|
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
expected_event_ids = [82]
|
|
||||||
query = "file where serial_event_id * 2 == 164"
|
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
expected_event_ids = [82]
|
|
||||||
query = "file where serial_event_id / 2 == 41"
|
|
||||||
|
|
||||||
[[queries]]
|
|
||||||
expected_event_ids = [82]
|
|
||||||
query = "file where serial_event_id % 40 == 2"
|
|
||||||
|
|
||||||
# The following two "between" queries behave slightly different with elasticsearch
|
# The following two "between" queries behave slightly different with elasticsearch
|
||||||
# due to comparison on keyword field would be case-sensitive and would need to be
|
# due to comparison on keyword field would be case-sensitive and would need to be
|
||||||
# file where between(file_path, "dev", ".json", false) == "\\TestLogs\\something"
|
# file where between(file_path, "dev", ".json", false) == "\\TestLogs\\something"
|
||||||
|
|
|
@ -18,6 +18,11 @@ 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;
|
||||||
import org.elasticsearch.xpack.ql.expression.function.FunctionRegistry;
|
import org.elasticsearch.xpack.ql.expression.function.FunctionRegistry;
|
||||||
|
import org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.Add;
|
||||||
|
import org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.Div;
|
||||||
|
import org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.Mod;
|
||||||
|
import org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.Mul;
|
||||||
|
import org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.Sub;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
@ -42,6 +47,14 @@ public class EqlFunctionRegistry extends FunctionRegistry {
|
||||||
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
|
||||||
|
new FunctionDefinition[] {
|
||||||
|
def(Add.class, Add::new, "add"),
|
||||||
|
def(Div.class, Div::new, "divide"),
|
||||||
|
def(Mod.class, Mod::new, "modulo"),
|
||||||
|
def(Mul.class, Mul::new, "multiply"),
|
||||||
|
def(Sub.class, Sub::new, "subtract"),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,12 @@ class org.elasticsearch.xpack.ql.expression.function.scalar.whitelist.InternalQl
|
||||||
#
|
#
|
||||||
# Math
|
# Math
|
||||||
#
|
#
|
||||||
|
Number add(Number, Number)
|
||||||
|
Number div(Number, Number)
|
||||||
|
Number mod(Number, Number)
|
||||||
|
Number mul(Number, Number)
|
||||||
Number neg(Number)
|
Number neg(Number)
|
||||||
|
Number sub(Number, Number)
|
||||||
}
|
}
|
||||||
|
|
||||||
class org.elasticsearch.xpack.eql.expression.function.scalar.whitelist.InternalEqlScriptUtils {
|
class org.elasticsearch.xpack.eql.expression.function.scalar.whitelist.InternalEqlScriptUtils {
|
||||||
|
|
|
@ -119,14 +119,6 @@ public class VerifierTests extends ESTestCase {
|
||||||
|
|
||||||
// Test the known EQL functions that are not supported
|
// Test the known EQL functions that are not supported
|
||||||
public void testFunctionVerificationUnknown() {
|
public void testFunctionVerificationUnknown() {
|
||||||
assertEquals("1:15: Unknown function [add]",
|
|
||||||
error("process where add(serial_event_id, 0) == 1"));
|
|
||||||
assertEquals("1:15: Unknown function [subtract], did you mean [substring]?",
|
|
||||||
error("process where subtract(serial_event_id, -5) == 6"));
|
|
||||||
assertEquals("1:15: Unknown function [multiply]",
|
|
||||||
error("process where multiply(6, serial_event_id) == 30"));
|
|
||||||
assertEquals("1:15: Unknown function [divide]",
|
|
||||||
error("process where divide(30, 4.0) == 7.5"));
|
|
||||||
assertEquals("1:34: Unknown function [number]",
|
assertEquals("1:34: Unknown function [number]",
|
||||||
error("process where serial_event_id == number('5')"));
|
error("process where serial_event_id == number('5')"));
|
||||||
assertEquals("1:15: Unknown function [concat]",
|
assertEquals("1:15: Unknown function [concat]",
|
||||||
|
|
|
@ -185,3 +185,164 @@ process where wildcard(process_path, "*\\red_ttp\\wininit.*", "*\\abc\\*", "*def
|
||||||
"wildcard":{"process_path":{"wildcard":"*\\\\abc\\\\*"
|
"wildcard":{"process_path":{"wildcard":"*\\\\abc\\\\*"
|
||||||
"wildcard":{"process_path":{"wildcard":"*def*"
|
"wildcard":{"process_path":{"wildcard":"*def*"
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
|
addOperator
|
||||||
|
process where serial_event_id + 2 == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.add(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
addOperatorReversed
|
||||||
|
process where 2 + serial_event_id == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.add(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":2,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
addFunction
|
||||||
|
process where add(serial_event_id, 2) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.add(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
addFunctionReversed
|
||||||
|
process where add(2, serial_event_id) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.add(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":2,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
divideOperator
|
||||||
|
process where serial_event_id / 2 == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.div(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
divideOperatorReversed
|
||||||
|
process where 82 / serial_event_id == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.div(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":82,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
divideFunction
|
||||||
|
process where divide(serial_event_id, 2) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.div(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
divideFunctionReversed
|
||||||
|
process where divide(82, serial_event_id) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.div(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":82,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
moduloOperator
|
||||||
|
process where serial_event_id % 2 == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.mod(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
moduloOperatorReversed
|
||||||
|
process where 42 % serial_event_id == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.mod(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":42,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
moduloFunction
|
||||||
|
process where modulo(serial_event_id, 2) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.mod(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
moduloFunctionReversed
|
||||||
|
process where modulo(42, serial_event_id) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.mod(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":42,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
multiplyOperator
|
||||||
|
process where serial_event_id * 2 == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.mul(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
multiplyOperatorReversed
|
||||||
|
process where 2 * serial_event_id == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.mul(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":2,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
multiplyFunction
|
||||||
|
process where multiply(serial_event_id, 2) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.mul(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
multiplyFunctionReversed
|
||||||
|
process where multiply(2, serial_event_id) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.mul(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":2,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
subtractOperator
|
||||||
|
process where serial_event_id - 2 == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.sub(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
subtractOperatorReversed
|
||||||
|
process where 43 - serial_event_id == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.sub(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":43,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
subtractFunction
|
||||||
|
process where subtract(serial_event_id, 2) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.sub(InternalQlScriptUtils.docValue(doc,params.v0),params.v1),params.v2))",
|
||||||
|
"params":{"v0":"serial_event_id","v1":2,"v2":41}
|
||||||
|
;
|
||||||
|
|
||||||
|
subtractFunctionReversed
|
||||||
|
process where subtract(43, serial_event_id) == 41
|
||||||
|
;
|
||||||
|
"script":{"source":"InternalQlScriptUtils.nullSafeFilter(InternalQlScriptUtils.eq(
|
||||||
|
InternalQlScriptUtils.sub(params.v0,InternalQlScriptUtils.docValue(doc,params.v1)),params.v2))",
|
||||||
|
"params":{"v0":43,"v1":"serial_event_id","v2":41}
|
||||||
|
;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import org.elasticsearch.index.fielddata.ScriptDocValues;
|
||||||
import org.elasticsearch.xpack.ql.expression.predicate.logical.BinaryLogicProcessor.BinaryLogicOperation;
|
import org.elasticsearch.xpack.ql.expression.predicate.logical.BinaryLogicProcessor.BinaryLogicOperation;
|
||||||
import org.elasticsearch.xpack.ql.expression.predicate.logical.NotProcessor;
|
import org.elasticsearch.xpack.ql.expression.predicate.logical.NotProcessor;
|
||||||
import org.elasticsearch.xpack.ql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
|
import org.elasticsearch.xpack.ql.expression.predicate.nulls.CheckNullProcessor.CheckNullOperation;
|
||||||
|
import org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.DefaultBinaryArithmeticOperation;
|
||||||
import org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.UnaryArithmeticProcessor.UnaryArithmeticOperation;
|
import org.elasticsearch.xpack.ql.expression.predicate.operator.arithmetic.UnaryArithmeticProcessor.UnaryArithmeticOperation;
|
||||||
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
|
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.BinaryComparisonProcessor.BinaryComparisonOperation;
|
||||||
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.InProcessor;
|
import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.InProcessor;
|
||||||
|
@ -119,7 +120,27 @@ public class InternalQlScriptUtils {
|
||||||
//
|
//
|
||||||
// Math
|
// Math
|
||||||
//
|
//
|
||||||
|
public static Number add(Number left, Number right) {
|
||||||
|
return (Number) DefaultBinaryArithmeticOperation.ADD.apply(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Number div(Number left, Number right) {
|
||||||
|
return (Number) DefaultBinaryArithmeticOperation.DIV.apply(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Number mod(Number left, Number right) {
|
||||||
|
return (Number) DefaultBinaryArithmeticOperation.MOD.apply(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Number mul(Number left, Number right) {
|
||||||
|
return (Number) DefaultBinaryArithmeticOperation.MUL.apply(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
public static Number neg(Number value) {
|
public static Number neg(Number value) {
|
||||||
return UnaryArithmeticOperation.NEGATE.apply(value);
|
return UnaryArithmeticOperation.NEGATE.apply(value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static Number sub(Number left, Number right) {
|
||||||
|
return (Number) DefaultBinaryArithmeticOperation.SUB.apply(left, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue