Expression
Constant values, entity members, operators, query executions and function calls can be combined into sequences known as expressions. Among other things, expressions are used to navigate and query a domain model.
An expression is evaluated to a value, which can be primitive value (e.g. 42
or "apple"
), entity instance or a collection of instances.
Data expressions returns values of primitive types, such as strings or integers. A data expression can be
-
a literal (e.g.
"Hello world"
,3.14
), -
a field selector (
self.firstName
,self.orders.orderItems
) -
an expression containing other data expressions and operators, such as
self.amount * self.productPrice
Reference expressions returns an entity instance or collection of entity instances.
Examples:
// it is a single line expression
derived String fullName => self.firstName + " " + self.lastName;
// it is a multi-line expression
derived String fullName => self.firstName +
" " +
self.lastName;
Operators
Operators usually perform a function on two operands. Operators that require two operands are binary operators. For example, the *
operator is a binary operator that multiplies the values of the right and left operands.
There are four exceptions that have different numbers of operands. The logical negation (not
) has only one operand, the arithmetic minus (-
) and plus (+
) may have only one operand (e.g. -3
, +3
), the conditional (ternary) operator has three operands.
An operator also returns a value. The value and its type depends on the operator and the type of its operands. For example, the arithmetic operators (perform basic arithmetic operations such as addition and subtraction) return numbers. The data type returned by the arithmetic operators depends on the type of its operands: if you add two integers, you get an integer back. An operation is said to evaluate to its result.
We can divide operators into these categories:
-
navigation, field selection and query execution (
.
) -
function call (
!
) -
assignment (
=
) -
arithmetic (
+
,-
,*
,/
,div
,mod
,^
) -
relational (
==
,!=
,<
,>
,>=
,<=
) -
conditional (
? :
) and -
logical (
not
,and
,or
,xor
,implies
).
The following table summarizes the precedence of operators used in expressions.
Precedence | Operator |
---|---|
10 |
|
9 |
|
8 |
|
7 |
|
6 |
|
5 |
|
4 |
|
3 |
|
2 |
|
1 |
|
Navigation
Navigation is used to traverse model relations to access the instance(s) linked to an instance or set of instances.
In expressions, the .
(dot) character is used to navigate from an instance or a collection of instances.
The starting point of the navigation can be
-
a static collection of references using the syntax
shop::Order
, which means all instances of the Order entity, or -
a static query execution using the syntax
shop::MyQuery()
, which means all entity instances returned by the queryshop::MyQuery
-
the predefined variable called
self
. Theself
keyword refers to the current instance in an expression.
Examples:
// a navigation starting from static collection
derived Products[] orderedProducts => shop::Order.orderItems.product;
// a navigation starting from self variable in Order entity
derived Products[] orderedProducts => self.orderItems.product;
Navigation can be chained, which means that you can navigate further from a navigation result.
In the example above, we first navigate from self
to the list of order items. We then proceed to the product
of the OrderItem. This navigation will result a list of products that are in the order.
Navigation types can also be classified as follows.
Instance navigation
When navigating from a single instance, the result of the navigation depends on the cardinality of the relation being navigated.
-
If the cardinality of the relation being navigated is single, the navigation returns a single entity instance or undefined.
-
If the cardinality of the relation being navigated is collection, the navigation returns a collection even if there is no actual instance or there is only one instance in the collection.
Collection navigation
When navigating from an instance collection, regardless of the cardinality of relation being navigated, the result is always a collection.
Field selection
Both derived and stored field values of an entity can be read in expressions. To read a field value use the .
(dot) character. The field selection can be chained for navigation expression. The field selection can only be chained for navigation that returns a single instance.
Examples:
derived String productName => self.product.name;
In the example above, the expression returns the product’s name.
Query execution
Static and instance queries can be used in expressions. To execute an instance query use the .
(dot) character before the query name and append the arguments in parentheses. To execute a static query use its name as a navigation starting point and append the arguments in parentheses. If you do not pass arguments to a query append parentheses with no arguments between them.
Query arguments can be literals only (e.g. 121
, "apple"
, true
etc.).
Examples:
derived Customer[] havingOrdersBetween0and10 => shop::ordersBetween(min = 0, max = 10).customer;
In the example above, the ordersBetween
query is executed with two arguments.
Built-in functions
There are a number of built-in functions that are always available in expressions. To invoke a built-in function use the !
character after a navigation, field or type selection.
Syntax:
<navigation>!<function>([<parameter>],...)
where <navigation> is a valid navigation expression and <function> is the name of the function to be invoked. After the function name, the input parameters of the function are in parentheses.
Examples:
derived Integer count => self.orderItems!size();
derived Integer nameLength => self.fullName!size();
The self.orderItems!size()
evaluates to an integer, it returns the number of the ordered items in the order. The expression self.fullName!size()
will return the number of characters in the customers’s name field (assuming it is a String type).
Static functions
A special group of functions is static functions. Static functions are invoked on types rather than on instances or collections.
Syntax:
<type>!<function>([<parameter>],...)
where <type> is a valid domain model primitive and <function> is the name of the function to be invoked. After the function name, the input parameters of the function are in parentheses.
Example:
field Time tm = Time!of(hour = 23, minute = 59, second = 59);
The above example sets the tm
field to `23:59:59`
as default value.
Iterator functions
Functions that handle individual elements in a collection use an iterator variable.
Syntax:
<navigation>!<function>(<variable> | <expr>)
where <variable> is the name of the iterator variable that will iterate through all elements on the result of the <navigation> collection. The <expr> is an expression that will provide the input list for the <function>.
Examples:
derived Integer averagePrice => self.orders!avg(field = price);
In the example above, we navigate to a collection orders and define an iterator variable named order
. Then, we read the price
field of orders and pass this list to the avg
built-in function. The example calculates the average price of the orders.
Functions can also be chained. For example the expression below filters the orders based on their price, then it returns the average price of the filtered orders.
self.orders!filter(order | order.price > 100)!avg(field = price)
The list of available functions can be found in section List of functions.
Iterator variables are only accessible in parentheses where they have been defined. In other words, the scope of the iterator variable is limited to after the |
(pipe) character. If the iterator expression contains another iterator function, the iterator variable of the embedding iterator function is not accessible within the embedded iterator function.
Using self
in iterator expressions is currently not allowed.
Even though The following example selects a customer’s orders that are cheaper than average order prices.
|
External variables
Some expressions require dynamic values that are provided by the surrounding environment and cannot be "wired" to the model as a constant, e.g. current time or OS environment variables. Variables of this type are referred to as external variables.
External variables can be accessed from the model using a category and key pair that are evaluated at runtime.
Syntax:
<primitive>!getVariable(category = <category>, key = <key>)
where <primitive> is a domain primitive that defines the type of the returned value. The valid <category> and <key> pairs a listed in the table below.
Domain primitive constraints are evaluated when “getVariable” is called. If the return value does not meet the primitive constraints, an error occurs. |
Category | Key | Description |
---|---|---|
|
|
Returns the current date.
Shortcut: |
|
|
Returns the current time.
Shortcut: |
|
|
Returns the current time.
Shortcut: |
|
any |
Returns an OS environment variable. Path for the current working directory: UID of running OS user: |
|
sequence name |
A continuously increasing integer is returned with each call. The maximum value cannot exceed the range of an eight-byte integer. Sequences are constant and optimized for concurrency. Sequences cannot be used for "gap-free" assignment of sequence numbers. If the sequence does not already exist, it creates the sequence and returns the first sequence value. Get next Order number: |
Undefined
Field selectors, navigations and expressions can evaluate to undefined. Since undefined is not a possible value of any data type, it is not considered a value, but rather a marker (or placeholder) indicating the absence of value.
In arithmetic expressions, any occurrence of undefined results in an undefined value. The expression self.a + self.b
results in undefined if self.a
or self.b
is undefined.
The expression self.firstName + " " + self.lastName
results in an undefined string if self.firstName
or self.lastName
is undefined.
In the case of logical values, the comparison with undefined can never result in either true or false, but always a third logical result, undefined.
Navigations from undefined references will also result in undefined. In other words, if any part of a navigation or function chain results undefined, the result of the whole navigation will be undefined. For example, self.customer.name
will evaluate to undefined, when self.customer
is undefined.
Functions applied on undefined references will also result in undefined. For example, self.name!size()
will evaluate to undefined instead of 0, when self.name
is undefined. The only exceptions are the isDefined()
, isUndefined()
and orElse()
functions:
-
self.name!isDefined()
will evaluate tofalse
ifself.name
is undefined. -
self.name!isUndefined()
will evaluate totrue
ifself.name
is undefined. -
self.name!orElse(value = "unknown")
will evaluate to"unknown"
ifself.name
is undefined.
The conditional ternary operator executes the false branch if the condition evaluates to undefined. self.name == "" ? "A" : "B"
is "B"
if the self.name
is undefined.
There is no literal for undefined in JSL, thus to check if a field, relation or the result of an expression is undefined use the isDefined()
or isUndefined()
functions. See Any type functions.
List of functions
Any type functions
Any type functions can be applied to any type or instance except collections.
isDefined() : boolean |
---|
Evaluates to true if the value exists. Note, that |
Example: |
|
|
isUndefined() : boolean |
---|
Evaluates to true if the value does not exist, e.g. it is undefined. Note, that |
Example: |
|
|
orElse(value = <value>) : <type> |
---|
Returns <value> if the given expression to which it is applied returns undefined. Otherwise, it returns the value of the given expression. The type of <value> must be the same as the given expression. |
Examples: |
|
|
|
getVariable(category = <value>, key = <key>) : <type> |
---|
Static function, returns the value of an external variable using <category> and <key> pair that are evaluated at runtime. |
See more details and the examples in chapter External variables. |
String functions
size() : numeric |
---|
Returns the number of characters in the string. |
Example: |
first(count = <count>) : string |
---|
Returns the first <count> characters of the original string, or returns the original string if its size is less than <count>. <count> must be an integer. If <count> is less than 1, it returns an empty string. |
Example: |
last(count = <n>) : string |
---|
Returns the last <count> characters of the original string, or returns the original string if its size is less than <count>. <count> must be an integer. If <count> is less than 1, returns an empty string. |
Example: |
position(substring = <substring>) : numeric |
---|
Returns the position of the first occurrence of the <substring> in the original string, or 0 if the original string does not contain the <substring>. <substring> must be a string. Position of the first character is 1. |
Example: |
substring(offset = <offset>, count = <count>) : string |
---|
Returns a substring of length <count> starting at <offset>. The offset of the first character is 1. Both <offset> and <count> must be integers. |
Example: |
lower() : string |
---|
Returns the original string with all alphabetic characters in lowercase. Note that the actual locale of the database affects the behavior of this function. |
Example: |
upper() : string |
---|
Returns the original string with all alphabetic characters in uppercase. Note that the actual locale of the database affects the behavior of this function. |
Example: |
capitalize() : string |
---|
Returns a string with the first letter capitalized and all other characters lowercased. Note that the actual locale of the database affects the behavior of this function. |
Example: |
matches(pattern = <pattern>) : boolean |
---|
Returns |
Example: |
like(pattern = <pattern> [, exact = <exact>]) : boolean |
---|
Returns There are two wildcards used in the <pattern>:
The percent sign represents zero, one, or multiple numbers or characters. The underscore represents a single number or character. These symbols can be used in combinations. If either of these two signs is not used in the <pattern>, then the function acts like the equals operator. |
Examples: |
|
|
replace(oldstring = <oldstring>, newstring = <newstring>) : string |
---|
Returns a string where all occurrences of <oldstring> in the original string replaced by <newstring>. Note that <oldstring> is not a regular expression pattern or a like pattern, but a simple string. |
Example: |
trim() : string |
---|
Returns the original string with all leading and trailing whitespace characters removed. |
Example: |
ltrim() : string |
---|
Returns the original string with all leading whitespace characters removed. |
Example: |
rtrim() : string |
---|
Returns the original string with all trailing whitespace characters removed. |
Example: |
lpad(size = <size> [, padstring = <padstring>]) : string |
---|
Fills up a string on the left to the specified <size> with <padstring>. If the length of <padstring> is less than the remaining length, the <padstring> is repeated until it is not filling up. If the length of <padstring> is longer than the remaining length, it will be truncated on the right. If <padstring> is not defined, one space character ( |
Examples: |
|
|
|
|
rpad(size = <size> [, padstring = <padstring>]) : string |
---|
Fills up a string on the right to the specified <size> with <padstring>. If the length of <padstring> is less than the remaining length, the <padstring> is repeated until it is not filling up. If the length of <padstring> is longer than the remaining length, it will be truncated on the left. If <padstring> is not defined, one space character ( |
Examples: |
|
|
|
|
Numeric functions
round([scale = <scale>]) : numeric |
---|
Returns the closest number. The optional <scale> argument is an integer that determines the number of decimal places of rounding. The default value of <scale> is 0. |
Example: |
|
|
|
|
|
|
floor() : numeric |
---|
Returns the largest integer that is less than or equal to the argument. |
Example: |
|
|
|
ceil() : numeric |
---|
Returns the smallest integer that is greater than or equal to the argument. |
Example: |
|
|
|
abs() : numeric |
---|
Returns the absolute value of the argument. |
Example: |
|
|
|
asString() : string |
---|
Numeric value as string. |
Example: |
Be careful when calling numeric functions on negative constant values! Since the precedence of the function call ( |
Date functions
year() : numeric |
---|
Returns the year part of date. |
Example: |
month() : numeric |
---|
Returns the month part of date. |
Example: |
day() : numeric |
---|
Returns the day part of date. |
Example: |
of(year = <year>, month = <month>, day = <day>) : date |
---|
Static function, constructs a date value from the numeric parameters <year>, <month> and <day>. If any of the parameters is not integer, the result will be undefined. If <month> or <day> is less than 1, the result will be undefined. If <month> is greater than 12, or <day> is greater than the last day of the month, it gives an undefined result. |
Example: |
dayOfWeek() : numeric |
---|
Returns an integer representing the weekday index for a date. The return value follows the ISO-8601 standard, from 1 (Monday) to 7 (Sunday). |
Example: |
dayOfYear() : numeric |
---|
Returns the day of the year for a given date (a number from 1 to 366). |
Example: |
asString() : string |
---|
Date value as string according to the ISO-8601 standard. |
Example: |
Time functions
hour() : numeric |
---|
Returns the hour part of time. |
Example: |
minute() : numeric |
---|
Returns the minute part of time. |
Example: |
second() : numeric |
---|
Returns the second part of time. |
Example: |
of(hour = <hour>, minute = <minute>, second = <second>) : time |
---|
Static function, constructs a time value from the numeric parameters <hour>, <minute> and <second>. If any of the parameters is not integer, the result will be undefined. If any parameter is less than 0, the result will be undefined. If <hour> is greater than 23, or <minute> or <second> is greater than 59, it gives an undefined result. |
Example: |
asString() : string |
---|
Time value as string according to the ISO-8601 standard. |
Example: |
Timestamp functions
date() : date |
---|
Returns the date part of the timestamp in UTC+0 timezone. |
Example: |
|
|
time() : time |
---|
Returns the time part of the timestamp in UTC+0 timezone. |
Example: |
|
|
of(date = <date> [, time = <time>]) : timestamp |
---|
Static function, constructs a timestamp value from the parameters <date> and <time>, where <date> is a date primitive and <time> is a time primitive. The <time> parameter is optional, if <time> is omitted the time part of the timestamp will be set to |
Example: |
|
|
asMilliseconds() : numeric |
---|
Returns the timestamp in milliseconds from 1970-01-01T00:00:00Z. |
Example: |
fromMilliseconds(milliseconds = <milliseconds>) : timestamp |
---|
Static function, creates a timestamp and sets the value in <milliseconds> from 1970-01-01T00:00:00Z. <milliseconds> must be an integer |
Example: |
plus([milliseconds = <milliseconds>][, seconds = <seconds>][, minutes = <minutes>][, hours = <hours>][, days = <days>][, months = <months>][, years = <years>]) : timestamp |
---|
Adds duration expressed in <milliseconds>, <seconds>, <minutes>, <hours>, <days>, <months> or <days> to the original timestamp. At least one parameter is required, but any parameter can be used in conjunction with the others. All arguments passed must be integers. If any argument is less than 0, its value is subtracted from the original timestamp. Examples:
|
asString() : string |
---|
Timestamp value as string according to the ISO-8601 standard. |
Example: |
Enumeration functions
asString() : string |
---|
Enumeration literal as string. |
Example: |
Instance functions
typeOf(entityType = <entityType>) : boolean |
---|
Evaluates to |
Example: |
|
kindOf(entityType = <entityType>) : boolean |
---|
Evaluates to |
Example: |
|
container(entityType = <entityType>) : instance |
---|
Returns the container of the instance if that matches the given <entityType>. Type matching works according to the |
Example: |
|
asType(entityType = <entityType>) : instance |
---|
Returns the given instance as the <entityType> if the type of the given instance matches to <entityType>, otherwise evaluates as undefined. In other words, the function "casts" the instance. Type matching works according to the |
Example: |
|
memberOf(instances = <instances>) : boolean |
---|
Indicates if the <instances> collection contains the given instance. |
Example: |
|
Collection functions
head(field = <field> [, reverse = <reverse>]) : collection |
---|
First, it sorts the given collection by the <field>, and then it returns a collection containing the first elements. If there is more than one element in the first place, all elements in the first place will be included in the result. The <field> is the name of an entity field or derived. The field must have a primitive type. The optional <reverse> is a boolean value. If <reverse> is true the collection will be sorted in descending order, otherwise in ascending order. |
Example: |
|
tail(field = <field> [, reverse = <reverse>]) : collection |
---|
First, it sorts the given collection by the <field>, and then it returns a collection containing all of the non-first elements. If there is more than one element in the first place, all elements in the first place will be omitted from the result. The <field> is the name of an entity field or derived. The field must have a primitive type. The optional <reverse> is a boolean value. If <reverse> is true the collection will be sorted in descending order, otherwise in ascending order. |
Example: |
|
any() : instance |
---|
Returns an arbitrary element of a collection. |
Example: |
|
size() : numeric |
---|
Returns the number of elements of a collection. |
|
asCollection(entityType = <entityType>) : collection |
---|
Filters the collection according to <entityType> and returns a collection of the parameter <entityType>. Members of the collection that are not of that <entityType> are not included in the result. Type matching works according to the kindOf() function. |
Example: |
|
contains(instance = <instance>) : boolean |
---|
Indicates if the given collection contains the given <instance>. |
Example: |
|
filter(<variable> | <logical expression>) : collection |
---|
Filters the collection according to the <logical expression> and returns a collection. Members of the collection that do not match the logical expression are not included in the result. |
Example: |
|
anyTrue(<variable> | <logical expression>) : boolean |
---|
Returns true if the logical expression is true for any element in the collection. Note that the return value is false for an empty collection. |
Example: |
|
allTrue(<variable> | <logical expression>) : boolean |
---|
Returns true if the logical expression is true for all the collection elements. If there is an element for which <logical expression> is false or undefined, then the return value is false. Note that the return value is true for an empty collection. |
Example: |
|
anyFalse(<variable> | <logical expression>) : boolean |
---|
Returns true if the logical expression is false for any element in the collection. Note that the return value is false for an empty collection. |
Example: |
|
allFalse(<variable> | <logical expression>) : boolean |
---|
Returns true if the logical expression is false for all the collection elements. If there is an element for which <logical expression> is true or undefined, then the return value is false. Note that the return value is true for an empty collection. |
Example: |
|
min(field = <field>) : numeric |
---|
Returns the minimum value from the set of values selected by the <field>. The <field> is the name of an entity field or derived. The field must have a numeric type. |
Example: |
|
max(field = <field>) : numeric |
---|
Returns the maximum value from the set of values selected by the <field>. The <field> is the name of an entity field or derived. The field must have a numeric type. |
Example: |
|
avg(field = <field>) : numeric |
---|
Returns the average of the set of values selected by the <field>. The <field> is the name of an entity field or derived. The field must have a numeric type. |
Example: |
|
sum(field = <field>) : numeric |
---|
Returns the sum of the set of values selected by the <field>. The <field> is the name of an entity field or derived. The field must have a numeric type. |
Example: |
|