Expressions

In many programs, much work is performed by evaluating expressions, which are combinations of operators, operands, and punctuation. They are used to detect events of interest to the program, perform calculations, comparisons, invoke actions, invoke inbuilt methods, compute parameter values passed to action and method calls, and so on.

Introduction to expressions

EPL has several kinds of expressions:

  • Primary expressions, bitwise expressions, logical expressions etc. are used for computations.
  • In a monitor, a stream query definition creates a derived stream from an existing stream.
  • In a monitor, a stream source template creates a new stream from an event template.

Event expressions are used in on statements for event pattern matching and sequence detection. Event expressions are not ordinary EPL expressions. See Event expressions.

When an expression is evaluated (that is, it is executed), it will produce a result value if the expression is a variable, a literal, or a combination of values and operators. If the expression is an action or inbuilt method call, then evaluating the expression produces a result value when the action or inbuilt method returns a value, but if the action or inbuilt method does not return a value, then the expression does not produce a result. Note that when an expression includes action or method calls, then evaluating the expression might produce side effects. A side effect is a change in the state of the execution environment. For example, a called action might change the value of a global variable or generate a derived event. If evaluating an expression produces a result, then in addition to a value, the expression result has a type. This is the expression type. An expression’s type is always known at compile time.

The elements of an expression are evaluated roughly from left to right, taking into account parentheses and operator precedence. Binary operators have a left operand and a right operand. If an operator is left-associative, its left operand is evaluated first, followed by the right, and then the operation is performed. If an operator is right-associative, its right operand is evaluated first, followed by the left, then the operation is performed. In action calls, the actual parameter list expressions are evaluated from left to right. Many of the operators used in expressions are polymorphic and can operate on operands of several types. For example, the addition operator performs floating point addition when its operands are of type decimal or float and performs integer addition when its operands are of type integer. Here are some examples of expressions:

i := (a.size() + b[3]) / (n -1);
i := "foo" + s + " " + b.toString() + f.formatFixed(8);

Using an expression as a statement

Any expression can be used as a statement. If that expression returns a value, that value is discarded. Be careful not to discard a value that may contain important conditions, such as failure, that your program may rely on.

Example:

// Action has a side-effect and a return value we're not interested in
action doSomething() returns integer
{
...
   return result;
}

// Expression as a statement, discarding the return value.
doSomething();

Primary expressions

The primary expression is the simplest form of expression. It can take the following forms:

  • Identifier. In an expression, an identifier is a variable name, an instance method name, a type method name, or an action name.
  • Literal. A literal in an expression is a compile-time constant value as described in Literals.
  • Postfix expression. See Postfix expressions.
  • Action/method. See Action and method calls.

Bitwise logical operators

The bitwise logical operators examine one bit at a time in their operands and compute the corresponding bit value in the result.

The bitwise operators and, or, and xor are binary operators that have a left and right operand. The bitwise operator not is a unary operator that has only a right operand.

The result type of all four bitwise operators is integer. Note that EPL integers are 64 bits wide.

Bitwise intersection (and)

The bitwise intersection operator and produces a result by comparing all 64 bits of its left and right operands, which must be expressions of type integer, one bit at a time. For each bit in the two operands, the corresponding bit in the result value is set to 1 if both operand bit values are 1 and set to 0 if either operand bit value is 0.

Example

The following illustrates this using 64-bit binary values.

  • a := 42;

    0000000000000000000000000000000000000000000000000000000000101010

  • b := 642;

    0000000000000000000000000000000000000000000000000000001010000010

  • a and b

    0000000000000000000000000000000000000000000000000000000000000010

Bitwise union (or)

The bitwise union or produces a result by comparing all 64 bits of its left and right operands, which must be expressions of type integer, one bit at a time. For each bit in the two operands, the corresponding bit in the result value is set to 1 if either or both operands bit values is 1 and set to 0 if both operand bit values are 0.

Example

The following illustrates this using 64-bit binary values.

  • a := 42;

    0000000000000000000000000000000000000000000000000000000000101010

  • b := 642;

    0000000000000000000000000000000000000000000000000000001010000010

  • a or b

    0000000000000000000000000000000000000000000000000000001010101010

Bitwise exclusive (xor)

The bitwise exclusive or operator xor produces a result by comparing all 64 bits of its left and right operands, which must be expressions of type integer, one bit at a time. For each bit in the two operands, the corresponding bit in the result value is set to 1 if either operand’s bit value is 1 and the other is 0 and set to 0 if both operand bit values are 0 or both are 1. In other words, the result bit is 1 if both bit values are different and 0 if they are the same.

Example

The following illustrates this using 64-bit binary values.

  • a := 42;

    0000000000000000000000000000000000000000000000000000000000101010

  • b := 642;

    0000000000000000000000000000000000000000000000000000001010000010

  • a xor b

    0000000000000000000000000000000000000000000000000000001010101000

Note that the expression a xor b yields the same result as not (a and b).

Unary bitwise inverse

The unary bitwise not operator produces a result by computing the bitwise complement or inverse of its right operand, which must be an expression of type integer. For each bit in the operand’s value, the corresponding bit in the result value is set to 1 if the operand’s bit value is 0 and 0 if the operand’s bit value is 1.

Example

The following illustrates this using 64-bit binary values.

  • b := 42;

    0000000000000000000000000000000000000000000000000000000000101010

  • not b

    1111111111111111111111111111111111111111111111111111111111010101

Logical operators

The logical operators and, or, xor and not perform Boolean arithmetic on their operands.

The logical operators’ left and right operands are expressions whose result type must be boolean. The result type of all four operators is boolean.

Logical intersection (and)

The and operator produces a result of true if both of its operand values are true and false otherwise.

When the correlator evaluates a logical and expression, it evaluates the left operand first. If the left operand evaluates to false, then the correlator does not evaluate the right operand since the expression cannot be true. For example:

a and b

If a is false, then whether or not b is true, the expression will be false so the correlator does not evaluate b. This lets you write code such as the following:

if (dict.hasKey(k) and dict[k] = "someValue")

If k is not in the dictionary then the left operand evaluates to false and so the entire logical expression is false. The correlator never evaluates dict[k] = "someValue", which would cause an error if k is not in the dictionary.

Logical union (or)

The or operator produces a result of true if either of its operand values is true and false otherwise.

When the correlator evaluates a logical or expression, it evaluates the left operand first. If the left operand evaluates to true, then the correlator does not evaluate the right operand since the expression will always be true. For example:

a or b

If a is true, then regardless of what b evaluates to, the expression will be true so the correlator does not evaluate b.

Logical exclusive or (xor)

The xor operator produces a result of true if either of its operand values is true and the other is false and false if both are true or both are false.

Unary logical inverse (not)

The unary not operator produces the result true if its right operand value is false, and false if the operand value is true.

Shift operators

The shift operators << and >> perform a shift of an integral value, moving bits in the result a specified number of positions to the right or left. The result type of both shift operators is integer.

The left operand is an expression of type integer whose value is to be shifted. The right operand is the shift count, an expression of type integer whose value is the number of bits the left operand value is to be shifted.

The shift count must be a nonnegative value less than 64. If the shift value is zero, then the result value is equal to the left operand value. Values less than zero or greater than 63 will produce unpredictable results and should not be used.

Left shift operator

The left shift operator << produces a result by moving the left operand value’s bits to the left and filling the vacated bits on the right with 0 bits. Bits that are moved beyond the leftmost bit (the sign bit) position are discarded.

Example

The following illustrates this using 64-bit binary values.

  • i := 42;

    0000000000000000000000000000000000000000000000000000000000101010

  • i << 24

    0000000000000000000000000000000000101010000000000000000000000000

Right shift operator

The right shift operator >> produces its result by moving the left operand value’s bit to the right. The vacated bits on the left are filled with 0 bits if the left operand value is zero or positive and filled with 1 bits if the left operand value is negative. Bits that are moved to beyond the rightmost bit (the least significant bit) position are discarded.

Example

The following illustrate this using 64-bit binary values.

  • i := 42;

    0000000000000000000000000000000000000000000000000000000000101010

  • i >> 24

    0000000000000000000000000000000000000000000000000000000000000000

  • i := -42;

    1111111111111111111111111111111111111111111111111111111111010110

  • i >> 24

    1111111111111111111111111111111111111111111111111111111111111111

Comparison operators

The comparison operators are used to determine the equality, inequality, or relative values of their left and right operands.

The left and right operands must be expressions of the same type and the type must be allowed for that operator. You can use each comparison operator on decimal, float, integer, and string types. On boolean types, you can use the = and != comparison operators. See also the descriptions of these types in the API reference for EPL (ApamaDoc).

The result type of all comparison operators is boolean.

The comparison operators are:

Operator Operation Description
< Less than Produces the result true if the left operand’s value is smaller than the right operand’s value and false otherwise.
<= Less than or equal Produces the result true if the left operand’s value is smaller than or equal to the right operand’s value and false otherwise.
= Equality Produces the result true if the left operand’s value is equal to the right operand’s value and false if they are not equal.
!= Inequality Produces the result true if the left operand’s value is not equal to the right operand’s value and false if they are equal.
=> Greater than or equal to Produces the result true if the left operand’s value is larger than or equal to the right operand’s value and false otherwise.
> Greater than Produces the result true if the left operand’s value is larger than the right operand’s value and false otherwise.

Additive operators

The additive operators are used to perform arithmetic on two operands of matching type: both of type decimal, both of type integer, or both of type float. The result type of the additive operators is the same as the type of the operands.

The additive operators are:

Operator Operation Description
+ Addition Produces a result by computing the numeric sum of its left and right operands. If the two operands are both expressions of type integer, then integral addition is performed and the result is of type integer. If the two operands are both of type decimal or both of type float, then floating-point addition is performed and the result type is the same as the operand type.
Subtraction Produces a result by computing the numeric difference between the left and right operands by subtracting the value of the right operand from the left. If the two operands are both expressions of type integer, then integral subtraction is performed and the result is of type integer. If the two operands are both of type decimal or both of type float, then floating-point subtraction is performed and the result type is the same as the operand type.
+ String concatenation Produces a result by “adding” two strings or a string and an another type together. If only one operand expression is of type string, then string conversion is performed on the other operand to produce a string. The result is a new string whose value is the value or string value of the right operand appended to the value or string value of the left operand. The result type of the string concatenation operator is string. Operand expression of type chunk, stream or action cannot be concatenated to an operand expression of type string. If an operand expression is of type any, then the contents of the any are converted to string to produce an expression of type string.

Multiplicative operators

The multiplicative operators are used to perform arithmetic on two operands of matching type: both decimal, or both float, or both integer.

The left and right operands must both be expressions of type decimal, or both be of type float, or both be of type integer.

The result type of the multiplicative operators is the same as the type of the operands.

The multiplicative operators are:

Operator

Operation

Description

*

Multiplication

Produces a result by computing the numeric product of its two operands. If the two operands are both expressions of type integer, then integral multiplication is performed and the result is of type integer. If the two operands are both of type decimal or both of type float, then floating-point multiplication is performed and the result type is the same as the operand type.

/

Division

Produces a result by computing the numeric quotient of its two operands. The left operand value, the dividend, is divided by the right operand value, the divisor. If both operands are of type integer, any fractional part of the result value is discarded. In other words, the result is truncated toward zero. For example, the expression 13/5 yields a result of 2. If both operands are of type integer, then integral division is performed and the result is of type integer. If both operands are of type decimal or both are of type float, then floating-point division is performed and the result type is the same as the operand type. If the right operand’s value is zero, a runtime error is raised.

%

Remainder

Produces a result by computing the numeric remainder from dividing the left operand value by the right operand value. For example, the expression 13%5 yields a result of 3. If both operands are of type integer, then the integral remainder is computed and the result is of type integer. If both operands are of type decimal or both of type float, then the floating-point remainder is computed and the result type is the same as the operand type. If the right operand’s value is zero, a runtime error is raised.

Unary additive operators

The unary additive operators are used to perform arithmetic on one right operand of type decimal, float or integer. The result type of the unary arithmetic operators is the same as the type of the operand.

Both of the unary arithmetic operators have one operand, which must be an expression of type decimal, float or integer. The result type is the same as the type of the operand.

Unary inverse

The unary additive inverse operator produces a result that is its right operand value with the sign reversed. If the operand value is negative, the result value is positive. If the operand value is positive, the result value is negative. If the operand value is zero, the result value is zero.

Unary identity

The unary additive identity operator + produces a result that is its right operand value.

Expression operators

You can use the following operators wherever you can specify an expression. Note that they are all binary operators.

Operator Operation Description
+ Addition Returns a decimal, float or an integer according to the operands, or concatenation in the case of string operands
- Subtraction Returns a decimal, float or an integer according to the operands
% Modulus Returns an integer and is a valid operator only for integers
/ Division Returns a decimal, float or an integer according to the operands
* Multiplication Returns a decimal, float or an integer according to the operands
> Greater than Returns a boolean value indicating whether the condition expressed is true or false
< Less than Returns a boolean value indicating whether the condition expressed is true or false
>= Greater than or equal to Returns a boolean value indicating whether the condition expressed is true or false
<= Less than or equal to Returns a boolean value indicating whether the condition expressed is true or false
= Equivalence Returns a boolean value indicating whether the condition expressed is true or false
!= Not equals Returns a boolean value indicating whether the condition expressed is true or false
or Logical or, bitwise or On boolean types, on integers
and Logical and, bitwise and On boolean types, on integers
xor Logical xor, bitwise xor On boolean types, on integers
not Logical not On boolean types

Expression operator precedence

The following table lists the primary and bitwise expression operators in order by their precedence, from lowest to highest. See also Event expression operator precedence.

Operation Operator Precedence
Logical or bitwise union or 1
Logical or bitwise exclusive or xor 2
Logical or bitwise intersection and 3
Unary logical or bitwise inverse not 4
Relational <, <=, >, >=, !=, = 5
Additive +, – 6
String concatenation + 6
Multiplicative *, /, % 7
Unary additive +, – 8
Cast <*type*> 9
Name qualifier (dot) . 10
Object constructor new 10
Subscript [ ] 10
Action call *actionName*() 11
Parenthesized expression ( ) 11
Stream query from 11
Stream source template all 11

For clarity, it is strongly recommended to use brackets in expressions. This makes it very easy to understand what an expression means. For example:

(10 * 10) > (9 * 9)

Do not use un-bracketed expressions, except where trivial. The following is equivalent to the above expression and is just about acceptable:

10 * 10 > 9 * 9

It is bad practice, however, to use an expression such as the following as it relies on intimate knowledge of the precedence:

1 + 2 - 3 * 4 / 5 < -1 or 5

Postfix expressions

A primary followed by a "." symbol, and an identifier must represent a variable reference, an action call, or a method call. Action and method calls are described in Action and method calls.

An expression enclosed by the [ and ] symbols denotes a subscript operation for a sequence or dictionary. This can be used on the right or left side of an assignment statement.

The new operator is used to create an instance of a reference type or event type.

Action and method calls

An action call within an expression transfers control to the statements within the action body during expression evaluation and temporarily suspends the expression evaluation. If the action has parameters, then their values are copied to the action’s formal parameter variables. When the control flow reaches the action’s end or the action executes a return statement, control is transferred back to the expression and evaluation continues.

The actual parameters are a comma-separated list of expressions. The entire list is enclosed in parentheses. It forms the set of parameter values that are passed when the action is called. Each expression value is copied to the corresponding parameter variable specified in the action definition’s formal parameters, and the expression result type must match the parameter variable’s type. The number and order of actual parameters passed by a caller must also match those listed in the action definition’s formal parameters.

The action or method being invoked in the expression must return a value. The action’s return type becomes the expression result type.

The subscript operator [ ]

The subscript operator takes one operand. The operand can be an integer index into a sequence or a key type index of a dictionary. The subscript operator produces a result of the same type as the sequence’s entry type or dictionary’s item type.

The new object creation operator

The operator new produces a result whose type is the type of the object parameter. It has one operand, the name of the type of object to be created.

Stream queries

A stream query defines an operation that the correlator applies continuously to one or two streams of items. The output of a stream query is a continuous stream of derived items, stream<*X*>, where *X* is the type returned by the expression in the select clause. See also Defining stream queries.

Syntax diagram for stream queries

A from clause specifies a stream that the query is operating on.

An item in a stream can be an event, a simple type (boolean, decimal, float, integer or string) or a location type. The first *Identifier* is the identifier that represents the current item in the stream you are querying. You use this identifier in subsequent clauses in the stream query.

The first *Expression* identifies the stream that you want to query.

A stream query window definition is optional. If you do not specify any window then the stream query operates on only the items that arrive on the stream for a given activation of that query. See Stream query window definitions.

A subsequent from clause indicates a cross-join operation.

Alternatively, a subsequent join clause indicates an equi-join operation. An equi-join has a key expression for each of the two streams that are being joined. Two items are joined into an output item only if the values of their key expressions are equal.

A where clause qualifies the items produced from a window or a join operation.

A group by clause organizes the qualified items, or the items produced from a window or join operation.

A having clause filters the output items produced from the projection.

The required select clause specifies how to generate the output items.

Semantic constraints

  • from _Identifier_ in _Expression_ join _Identifier_ in _Expression_

    The identifier can be any legal identifier and, within the stream query’s scope, is associated with items from the source stream and therefore has their type. In a joined stream query, the two identifiers must be distinct.

    The expression’s result must be a value of some stream type. The correlator evaluates the expression outside the stream query’s scope. For example:

    stream<A> a := all A();
    from a in a...
    

    This is legal, because the identifier a is not in scope for evaluation of the expression a.

  • on _Expression1_ equals _Expression2_

    The correlator evaluates both expressions within the stream query’s scope.

    *Expression1* must contain the first item identifier and cannot contain the second. *Expression2* must contain the second item identifier and cannot contain the first.

    The two expressions must return the same type, and that type must be a comparable type.

  • where _Expression_ group by _Expression_, _Expression_,...

    The item identifier or identifiers are in scope and should be used in these expressions. The where expression must return a boolean value. The group by expressions can return any comparable types.

  • having _Expression_

    The item identifier or identifiers are in scope and can be used in this expression. The presence of this clause implies that the projection must be an aggregate projection. The expression must return a boolean value.

    You can use one or more aggregate functions in the having expression. In fact, you can use aggregate functions only in having expressions and select expressions.

  • select [rstream] _Expression_

    The item identifier or identifiers are in scope and can be used in this expression. The expression must return a value.

    You can use one or more aggregate functions in a select expression. In fact, you can use aggregate functions only in having expressions and select expressions. If you specify an aggregate function you cannot specify the rstream keyword.

  • Stream query window definitions

Stream query window definitions

In a stream query, the optional window definition specifies which items in a stream to operate on. See also Adding window definitions to from and join clauses.

Syntax diagram for stream query window definition

Typically, stream queries process a window over a stream. A stream is an ordered sequence of items over time. A window specifies which items to operate on. Windows can contain a portion of the stream based on number of items, time of item arrival, content of item, or other criteria.

When the stream query window definition is retain all, the window contains all items that have ever been in the stream. Conceptually, once an item enters a retain all window, it remains in the window indefinitely, or until the stream query is terminated. The retain all clause specifies an unbounded window. Unbounded windows have restrictions on their use:

  • You cannot have a partitioned or batched unbounded window.
  • You cannot perform a join operation on an unbounded window.
  • You cannot specify an unbounded window when you use rstream in the select clause of a stream query.

When you use a custom aggregate function in a stream query that contains an unbounded window, you cannot use a bounded aggregate function. You should also be aware that, if you use a badly implemented custom aggregate function in a stream query that contains an unbounded window, then this can result in uncontrolled memory usage.

A partition by clause divides the input data into several partitions and then applies the stream query window definition separately to each partition. The partition by expressions must be comparable types.

The retain clause specifies the maximum number of items to be retained by the window. The retain expression must be an integer expression. In a size-based window, as each new item arrives in the stream, it is added to the window. After the number of items in the window reaches the window size limit specified in the retain clause, the arrival of a new item causes removal of the oldest item from the window.

The within clause specifies the number of seconds to keep each new item in the window. The within expression must be a float expression. In a time-based window, as each new item arrives in the stream, it is added to the window. As soon as an item has been in the window for the number of seconds specified by the within expression, the correlator removes the item from the window.

By default, the contents of a window change upon the arrival of each item. The every keyword can be used to control when the contents of the window change, which causes the items to be added to the window in batches of several items at once. Time-based windows can be controlled to update only every p seconds and size-based windows can be controlled to update only every m events.

The contents of the window can also depend on the content of individual items in the stream. Specify with unique *Expression* to limit the window to containing only the most recent item for each key value identified by the expression.

Semantic constraints

In a stream query window definition for one of a joined stream query’s input streams, it is always an error to refer to the other input stream’s item identifier.

  • partition by _Expression_, _Expression_,...

    You should use the item identifier in each expression. Expressions can return any comparable types.

  • retain _Expression_ [every _Expression_]

    You cannot use the item identifier in these expressions. These expressions must return integer values.

  • within _Expression_ [every _Expression_]

    You cannot use the item identifier in these expressions. These expressions must return float values.

  • with unique _Expression_

    You should use the item identifier in this expression. The expression can return any comparable type.

Stream source templates

A stream can be created from an event template using the all keyword. This is referred to as a stream source template.

A stream source template is the all keyword followed by a single event template. The output of a stream source template is a continuous stream of items, stream<*X*>, where *X* is the type specified by the event template.

See also Creating streams from event templates.