JSL

Introduction

JUDO Specification Language (JSL) is an implementation independent text-based modeling language. The overall goal of the language is to provide unambiguous and readable descriptions of different business domains and their incorporated business logic.

Because of the accuracy of JSL, models can be run, tested, and integrated into coding projects.

All concepts and rules described in this document are valid in JSL.

See the JUDO Java documentation for APIs and programming concepts.

JSL Syntax

This section describes the common elements of JSL syntax.

Conventions

The following conventions are used in the syntax description: angle brackets (< and >) indicate variable parts, brackets ([ and ]) indicate optional parts. Vertical lines (|) indicate that you must choose one alternative. Dots (…​) mean that the preceding element can be repeated.

All symbols (keywords, parentheses, dots, brackets, etc.) in bold purple must be taken literally.

For example, a simplified version of the well-known copy command looks like this with these conventions.

cp [-a|-l|-r]... <source>... <directory>

Overall structure

Domain models are defined by a sequence of JSL statements. Each domain model is in a text file. The extension of the model text files shall be "jsl". E.g. CRM.jsl file contains the model definition of the customer relationship management (CRM) model.

Domain models may refer to each other. References between models allow to partition domain models into multiple (sub)domains.

Statements

JSL statements are composed from:

  • JSL keywords

  • Expressions

  • Element names (entities, fields, enumerations etc.)

JSL statements are terminated by a semicolon (;). A semicolon is optional at the end of block declarations ({ …​ }). However, extra ; tokens after statements have no effect.

A statement can be broken into several lines.

The following example is syntactically valid.

Example:

model crm;

type string String(min-size = 0, max-size = 128);

entity Person {
    field required String firstName = "";
    field required String lastName = "";;;;;
    derived String fullName =
        self.firstName + " " + self.lastName;
};

Keywords

Keywords are predefined, reserved words used in JSL that have special meanings in the language. JSL keywords are always lowercase.

Keywords cannot be used directly as element names. If you still want to use these reserved words as element names, the words must be enclosed in back-tick (`) characters as shown in the example below.

field String `model`;

This is the list of keywords in the JSL.

abstract

false

opposite-add

and

field

or

as

identifier

query

constraint

implies

relation

derived

import

required

div

mod

self

entity

model

true

enum

not

type

error

onerror

xor

extends

opposite

Element names

The names of JSL domain elements must conform to the following rules:

  • Names are case-sensitive.

  • Name of elements must be unique within their scope in a case-insensitive manner.

  • A name can be a sequence of uppercase and lowercase English letters (A-Z, a-z) and digits (0-9).

  • The first character of a name must be a letter.

  • A name must have at least one character.

  • A name must not be longer than 128 characters.

Even though element names (e.g. entity names, field names, enumeration literals, etc.) are case-sensitive, it is not allowed to have two elements in the same scope (e.g. two fields within an entity) whose names differ only in upper and lower case.

Comments

Comments may be inserted by use of the // characters at any point in the line. When // characters are detected, the rest of the line is considered to be a comment and ignored.

Multi-line comments may be created by surrounding the lines by /* and */. Multi-line comments do not nest.

Model

A model is a collection of type definitions and execution (business) logic belonging to the same domain. Every model file must start with the definition of the model name.

The model keyword is used to start a model definition.

Syntax:

model <fq-name> ;

where the <fq-name> is the fully qualified name of the model. The fully qualified name consists of optional namespaces and the name of the model separated by double colons (::), for example demo::enterprise::crm. crm is the name of the model and the namespaces are demo and enterprise.

Namespace

Namespace is a naming hierarchy built from the names of the container namespaces starting at the root of the hierarchy.

Examples:

model demo::enterprise::crm;
model CustomerRelationshipManagement;

Import

To import an other model into your model, we need to use the import keyword which is used to access model and its types into the current model. Use import to access built-in and user-defined models into your model file.

The import statement is not transitive. Thus, if model B imports model A and model C imports model B, then elements of model A are not available in model C. To access elements of model A in model C, model C must also import model A. In other words, each model files must be explicitly imported to access its elements.

To import an existing model, use the import keyword.

Syntax:

import <model> [as <alias>] ;

where the <model> is the fully qualified name of the imported model.

Examples:

import judo::types;
import judo::user as user;

If the imported model is accessed successfully, it will be made available in the local namespace in one of two ways:

  • If the model name is followed by as, then the alias name following as is bound directly to the imported model.

Example:

import judo::types as types;

entity Person {
    field types::String name;
}
  • If no alias is specified, the elements defined in the imported model are directly accessible, so your statements can directly refer to the imported model elements using their names.

Example:

import judo::types;

entity Person {
    field String name;
}

Notice the difference between the field declarations in the examples above.

Primitives

In order to support JSL as a rigorous modeling formalism, the primitive data types in the model must be specified.

JSL allows the following “base types” for primitive specification:

  • boolean

  • binary

  • string

  • numeric

  • date

  • time

  • timestamp

The above base types cannot be used directly in domain models. However, you must define the primitive types used in your domain model.

To define a new domain model primitive, use the type keyword.

Syntax:

type <basetype> <name> [ (<parameter name> = <parameter value>], ...) ] ;

where the <basetype> is one of the base types defined above, the <name> is the name of the domain model primitive, and there can be an additional list of parameters for each base type. The optional comma-separated parameter list consists of name-value pairs, using = between name and value.

Parameters are constraints that restrict the use of the newly created primitive data type.

Example:

type boolean Boolean;
type numeric Integer(precision = 9, scale = 0);
type string String(min-size = 0, max-size = 128);

The Base types section provide examples of each base type and its possible constraints and operations.

Instead of defining the most common primitives in each project, there is a built-in judo::types model that you can import into models. It defines the following primitives:

type boolean Boolean;
type date Date;
type time Time;
type timestamp Timestamp;
type numeric Integer(precision = 15, scale = 0);
type string String(min-size = 0, max-size = 4000);

You can use these primitives a follows:

model crm;

import judo::types;

entity SalesPerson {
    field String firstName;
    field String lastName;
}

Enumeration

An enumeration is a primitive data type whose values are called enumeration members. Enumeration members are listed as a pair of a text (the literal) and an integer (the ordinal). An enumeration member is represented with the qualified name of enumeration tagged with literal, for example Titles#MR or example::Color#RED.

The literals of the enumeration members within an enumeration must be unique in case-insensitive manner. Enumeration literals are usually uppercase to improve readability.

The ordinal parts of the enumeration members within an enumeration must be unique. Enumeration members can be ordered by their ordinal.

An enumeration must have at least one enumeration member.

Ordinals are for run-time database storage, and to help model restructuring and data migration. When storing an enumeration value, only the ordinal is stored in the database. This storage mechanism allows the literal values to be changed without data migration and language-independent sorting of database records.

To define an enumeration, use the enum keyword.

Syntax:

enum <name> {
    [<literal> = <ordinal> ;] ...
}

where the <name> is the name of the enumeration, and the enumeration members are defined between { and }. Enumeration members are defined as a list of <literal> and <ordinal> pairs using = between them.

Example:

enum Direction {
    NORTH = 1;
    EAST = 2;
    SOUTH = 3;
    WEST = 4;
}

entity Ship {
    field Direction course;
}

In the example above, the Ship entity has a course field that shows its current direction.

The supported operators of enumeration are the following:

Operator Result Meaning Example

<, >, ==, !=, <=, >=

Boolean

Comparison. Enumeration members of two different enumerations are not comparable.

  • example::Title#MRS == example::Title#MS is false

  • example::Title#MR != `example::Title#MRS is true

  • example::Title#MR == example::Color#RED is not valid

Supported functions are listed in chapter Enumeration functions.

Entity

Data model is the core part of the domain model that captures the real-world entities and the rules that determine how data can be created, changed, and deleted.

The persistent data of a domain model is described in the form of entity types. Actual data is stored by creating instances of entity types.

An entity type (or shortly an entity) may have stored primitives and relations, derived fields and queries that are referred to as members of an entity.

We are aware that entity and entity type are not the same. However, for ease of reading, we use the word entity instead of entity type. When talking about instances, we always use the term entity instance.

To define an entity, use the entity keyword.

Syntax:

entity [abstract] <name> [extends <entity>, ...] {
    [member] ...
}

where the <name> is the name of the entity, and the entity members are defined between { and }. The keyword extends indicates that an entity is inherited from another entities. See the section Inheritance for more details.

Example:

entity Person {
}

The example above defines an empty entity named Person. This entity has no member declarations (fields or relations).

An entity can be defined as an abstract using the keyword abstract. An abstract entity cannot be instantiated, it is intended to be inherited by other entities.

Example:

entity abstract Employee {
}

Primitive fields

An entity instance can store primitive values in its fields. A field can be interpreted as a container in a specific entity instance where data values can be stored (e.g. numbers or text values like "red" or "blue").

A field definition of an entity specifies the name and the type of the data that are stored in the entity instances (e.g. color is the name of the field of the Pencil entity and this field has a string type).

A primitive field cannot store multiple primitive values, but only a single primitive value. To put it another way, there is no list of primitives.

Use the field keyword to specify a primitive field within an entity.

Syntax:

field [required] <primitive> <name> [= <default>] ;

where <primitive> is the name of a domain model primitive and <name> is the referable name of the primitive field.

The keyword required means that the value of the field must be specified, so the value is not allowed to be UNDEFINED. Each newly created entity instance must set this field.

Optionally, a <default> value can be specified as an expression. The <default> value expression is evaluated when a new instance of the entity is created, and the field is set according to the default value expression. See Expression later.

The self variable cannot be used in default expressions.

Example:

entity Person {
    field required String firstName;
    field required String lastName;
    field String midName;
    field Title title = Title#MX;
}

The example above defines an entity named Person. This entity has four primitive fields. firstName and lastName are two required strings, midName is an optional string with no default value and title is an optional enumeration field with a default value.

Identifier

Identifiers are regular primitive fields, but it is ensured that the value of the field for each entity instance is different (unique). Note that, the undefined value is considered different from any value. (Undefined is also considered to be different from undefined.)

Use the identifier keyword to specify a unique field within an entity.

Syntax:

identifier [required] <primitive> <name> [= <default>] ;

where <primitive> is the name of a domain model primitive and <name> is the referable name of the identifier. Any primitive type can be used as an identifier, except primitives of binary base type.

The keyword required means that the value of the identifier must be specified, so the value is not allowed to be UNDEFINED.

Optionally, a <default> value can be specified as an expression. The <default> value expression is evaluated when a new instance of the entity is created, and the identifier is set according to the default value expression. See Expression later.

The self variable cannot be used in default expressions.

Example:

entity Person {
    identifier required String email;
}

The example above defines email as a required string and serves as an identifier. This means you can find exactly one Person if you know an email address.

An important feature of entity instances is that they are uniquely identifiable, i.e. each instance of an entity has a (universally) unique system generated identifier that never changes. This system generated identifier is not available in JSL and should not be confused with the identifier members discussed in this chapter.

Relations

Before explaining relations between entities we need to understand references and collections.

When a new entity instance is created, its space is allocated in the persistent storage. To access an entity instance, we need a reference. The reference simply points to the entity instance (which is created in persistent storage).

References do not have to point to an entity instance, or in other words references can be undefined.

When you pass a reference value to another reference, the two references will point to the same entity instance. If you delete the entity instance through one of the references, both references will have an undefined value.

A collection is a set of references. Collections always contain unique references, which means that there are no two references in a collection that point to the same entity.

Collections cannot be undefined, but collections can be empty. A collection cannot contain undefined reference. Once an entity instance is deleted, references to it are deleted from all collections.

Unidirectional relation

Relation is a reference defined within an entity. The entity instance containing the reference is the owner of the relation. The owner has access to the instance pointed to by the reference.

Relations can be not only references to a single instance, but also collections. If the relation is a collection, the owner can access a set of instances at the same time.

Relations can be unidirectional or bidirectional.

An unidirectional relation can only be navigated in one direction: from the owner to the target of the relation. Navigation means that, at run-time, the owner of the relation gets the instance(s) of the target of the relation.

Use the relation keyword to specify a relation between two entities. The syntax of the unidirectional relation is as follows.

Syntax:

relation [required] <entity>[[]] <name> ;

where <entity> is the name of an entity to which the relation is targeted. The <name> is used to identify the relation within the entity, it is commonly referred to as role name.

The optional [] behind the <entity> indicates that the relation is a collection rather than a single reference to one <entity> instance. In other words, the cardinality of the relation is a collection.

The keyword required means that the value of the relation must be specified, so the value is not allowed to be UNDEFINED. Each newly created entity instance must set this field. The keyword required is not allowed for collections.

Example:

entity SalesPerson {
	relation Lead[] leads;
}

entity Lead {
	relation required Customer customer;
}

entity Customer {
}

The example above defines two unidirectional relations. leads is defined within SalesPerson entity and can refer to a list of Lead entity instances that belong to a particular salesperson. customer relation is defined within the Lead entity and targets the customer who would make the purchase.

Bidirectional relation

A bidirectional relation can be navigated in both directions.

The syntax of the bidirectional relation is an extension of unidirectional syntax and it has two forms. The first one is:

Syntax:

relation [required] <entity>[[]] <name> opposite <opposite> ;

The opposite keyword is used to link two relations into a bidirectional relation. The opposite keyword must be used on both sides of the bidirectional relation.

Both ends of a bidirectional relation cannot be required at the same time.

Example:

entity SalesPerson {
	relation Lead[] leads opposite salesperson;
}

entity Lead {
	relation required SalesPerson salesperson opposite leads;
}

Another form of bidirectional relationship declaration is as follows.

Syntax:

relation [required] <entity>[[]] <name> opposite-add <opposite>[[]] ;

The opposite-add keyword with a simple <opposite> is used to create a bidirectional relation and inject the opposite relation to an already defined entity without changing the original definition of the entity.

The opposite-add keyword with an <opposite> and [] injects a collection in the target entity.

The most useful feature of opposite-add is that it can be used with models that should not be modified. If you import a model, you can still create bidirectional relationships between your entities and the entities you imported.

The example below defines two bidirectional relations. Defining the salesperson relation in the Lead entity also adds a collection to the SalesPerson entity named leads. In addition, the relation defined in the Customer entity adds a single customer relation to the Lead entity.

Example:

entity SalesPerson {
}

entity Lead {
	relation required SalesPerson salesperson opposite-add leads[];
}

entity Customer {
	relation Lead[] leads opposite-add customer;
}

Composition

A composition or composite field is a member whose type is an entity. A composition can be considered as a specific type of reference where the owner of the composition contains the referenced entity instance(s).

An important feature of composition is that after deleting the owner, all entity instances in its composite fields are also deleted. Compositions can also be thought of as entity instances that are not created within the shared space of the persistent storage, but within the owner entity instance.

Use the field keyword to specify a composite field within an entity.

Syntax:

field [required] <entity>[[]] <name> ;

where <entity> is the name of an entity and <name> is the referable name of the composition.

The optional [] indicates that the composite field stores a list of entity instances rather than a single <entity> instance. In other words, the cardinality of the field is a collection.

The keyword required means that the value of the field must be specified, so the value is not allowed to be UNDEFINED. Each newly created entity instance must set this field. The keyword required is not allowed for collections.

Example:

entity Address {
    field required String line1;
    field String line2;
    field String city;
    field String zipCode;
}

entity Person {
    field required Address address;
}

The example defines the Address entity with its primitive fields, and each Person instance must have exactly one composite Address.

Derived members

Derived members provide a flexible mechanism to read their value. The value of a derived member cannot be set, in other words derived members are read only. However, derived members can be read as if they are stored fields, but they have a special expression called getter expression. The getter expression is used to return the value of the derived member.

Use the derived keyword to specify a derived member within an entity.

Syntax:

derived <primitive> <name> => <getter> ;

where <primitive> is the name of a domain model primitive, and <name> is the referable name of the derived field, or

Syntax:

derived <entity>[[]] <name> => <getter> ;

where the <entity> is the name of an entity, and <name> is the referable name of the derived member. The optional [] indicates that the derived member returns a collection rather than a single reference to one <entity>.

The <getter> expression returns the value of the derived member whenever it is requested. See Expression later.

Note that the derived members use the arrow operator (=>) instead of the assignment operator (=) to define the getter expression.

Example:

entity Person {
    field required String firstName;
    field required String lastName;

	derived	String fullName => self.firstName + " " + self.lastName;
}

The example above defines two stored fields and a derived member. The name of the derived member is fullName and its value is calculated by concatenating the firstName and lastName with a space in the middle. Note the self keyword in the getter expression which refers to the current Person instance.

Example:

entity SalesPerson {
	relation Lead[] leads;

	derived Lead topLead => self.leads!head(l | l.value DESC)!any();
}

entity Lead {
    field Integer value;
}

In the second example the topLead derived member returns the highest value lead.

Query

A query is a request that retrieves a primitive or entities. Each query has a return type, a name and a query expression, which is used to calculate the return value.

There are two main differences between queries and derived members.

  • queries can have arguments, and

  • queries are never evaluated automatically

The moment of evaluation needs explanation. Queries are never evaluated automatically, and their return values will only be calculated when the query is invoked.

The moment of derived fields evaluation is a bit more complicated. Derived fields that return a primitive value are evaluated immediately when the entity is referenced. However, if a derived returns an entity reference or collection, its derived expression is only evaluated during navigation.

There are two types of queries

  • instance query and

  • static query

Instance query

Instance queries are declared within an entity. The entity that contains the query can be referred to as self in the query expression.

Use the query keyword to specify a query within an entity.

Syntax:

query <primitive> <name> [(<primitive> <argument> = <default>, ...)] => <expression> ;

where <primitive> is the name of a domain model primitive that will be returned, and <name> is the referable name of the query, or

Syntax:

query <entity>[[]] <name> [(<primitive> <argument> [= <default>], ...)] => <expression> ;

where the <entity> is the name of an entity that will be returned, and <name> is the referable name of the query. The optional [] indicates that the query returns a list of <entity> instances rather than a single reference to one <entity>.

The <expression> calculates the return value of the query whenever it is requested. See Expression later.

Note that queries do not use the assignment operator (=) but the arrow operator (=>) to set the value.

The optional comma separated list is the arguments of the <expression>. The arguments can be used in the <expression>. <primitive> is the type of argument which can be a domain model primitive only. <argument> is the name of the argument. Arguments can be declared with optional <default> values. The default values are used when the query is executed by one or more missing arguments.

Example:

entity SalesPerson {
	relation Lead[] leads;

	query Lead[] leadsBetween(Integer min = 0, Integer max = 100) =>
	    self.leads!filter(l | l.value > min and  l.value < max);
}

entity Lead {
    field Integer value;
}

The example above shows the query leadsBetween, which has two arguments (min and max). It returns the leads of a salesperson with a value between min and max. If no arguments are specified when executing leadsBetween, the query returns leads between 0 and 100. For more details about expressions, see the chapter Expression.

Static query

Static queries are very similar to instance queries, but static queries are within the scope of the model, not the scope of the entity. Thus, static queries cannot use self variable.

The syntax of the static query declaration is the same as the syntax of the instance query declaration.

Example:

entity SalesPerson {
	relation Lead[] leads;
}

entity Lead {
    field Integer value;
}

query Lead[] leadsBetween(Integer min = 0, Integer max = 100) =>
    Lead!filter(l | l.value > min and  l.value < max);

The example above shows the static query leadsBetween, which has two arguments (min and max). It returns the leads of of all salespersons with a value between min and max.

Note the difference between the instance and the static query examples. The instance query uses the self variable as the starting point for navigation. It collects all the leads belonging to the given salesperson and then runs the filter condition. In the second example, the starting point of the static query is all the leads stored in the application. Then the static query also runs the filter condition to select the leads between the two limits.

Inheritance

Inheritance is a mechanism by which more specific entities incorporate structure of the more general entities (called parent entities).

Entities may inherit identifiers, stored fields, derived fields, relations and entity constraints from their parent entities. An entity and its parent entity are in IS-A relation, the entity can replace its parent entity anywhere.

Inherited members of an entity which were defined in the parent behave as if they were defined in the entity itself.

An entity may be the parent of any number of other entities and may have any number of parents. An entity should not be inherited from itself, either directly or indirectly.

An entity cannot have an inherited and a non-inherited member with the same name. Thus, overriding members is not allowed. In addition, an entity cannot inherit two members with the same name from two different parents.

The extends keyword in the entity declaration indicates that an entity is inherited from another entities.

Example:

entity Person {
    identifier required String email;
    field required String firstName;
    field required String lastName;
    field String midName = "";
}

entity SalesPerson extends Person {
    relation Person manager;
}

In the above example the SalesPerson entity inherits the four fields of the Person entity and defines a relation to its manager.

Example:

entity Person {
    identifier required String email;
    field required String firstName;
    field required String lastName;
    field String midName = "";
}

entity abstract Employee {
    relation Employee manager;
}

entity SalesPerson extends Person, Employee {
}

In the second example above the SalesPerson entity inherits the four fields of the Person entity and inherits its relationship with its manager from the Employee entity. This is an example of multiple inheritance.

Error

In the current release, error definitions are not used in JSL. However, the Java API can use error definitions.

An error is an event that occurs during the execution of an application. If an error occurs within the application, it interrupts the normal process of program instructions, and the application must create an error description object to pass it to the external caller.

Error descriptors can only contain fields of domain primitive type. Errors are usually kept simple, often only offering a few number of fields that allow information about the error to be extracted by handlers for the error.

To define an error, use the error keyword.

Syntax:

error <name> [extends <error>] {
    [field] ...
}

where the <name> is the name of the error, and the error fields are defined between { and }.

Example:

error GenericError {
}

The example above defines an error named GenericError. This error has no fields.

An error may contain data descriptions called fields. A field has an associated domain type that must be a domain model primitive. Fields cannot store multiple primitive values (that is, lists, sets), but only a single primitive value.

Use the field keyword to specify a field within an error.

Syntax:

field <primitive> <name> [= <default>] ;

where <primitive> is the name of a domain model primitive, and <name> is the referable name of the field.

Optionally, a <default> value can be specified as an expression. The <default> value expression is evaluated when a new instance of the error is created, and the field is set according to the default value expression. See Expression later.

The self variable cannot be used in default expressions.

Example:

error PersonNotFound {
    field String email;
}

The example above defines an error named PersonNotFound. This error has only one field. email is a string that contains an email address that doesn’t match any person’s email address.

Errors can be inherited from each other using the keyword extends. Inheritance of errors is the same mechanism as inheritance of entities. However, errors can only have one parent. To learn more about inheritance, read the chapter on inheriting entities.

Example:

error GenericError {
    field Integer code;
}

error PersonNotFound extends GenericError {
    field String email;
}

The example above shows an error inheritance. The PersonNotFound error inherits the numeric value code from the parent named GenericError.

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.myAttribute, self.relation.otherAttribute)

  • an expression containing other data expressions and operators, such as 2 + 4 * self.myIntegerAttribute

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 sums the values of the right and left operands.

There are two exceptions that have different numbers of operands. The logical negation operator has only one operand, 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

not

8

*, /, div, mod

7

+, -

6

>, <, <=, >=

5

==, !=

4

and

3

or

2

? :

1

=

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 model::entities::MyEntity, which means all instances of the entity type model::entities::MyEntity, or

  • a static query execution using the syntax model::entities::MyQuery(), which means all entity instances returned by the query model::entities::MyQuery

  • the predefined variable called self. The self keyword refers to the current instance in an expression.

Examples:

// a navigation starting from static collection
derived Person[] allOwners => cars::Car.owner;

// a navigation starting from self variable
derived String ownerName => self.owner.name;

Navigation can be chained, which means that you can navigate further from a navigation result.

derived Address ownerAddress => self.owner.address;

In the example above, we first navigate from self to a Person entity who is the owner of the car. We then proceed to the address of the Person.

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 an UNDEFINED value.

  • 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 ownerName => self.owner.name;

In the example above, the expression returns the owner’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[] havingLeadsBetween0and10 => self.leadsBetween(min = 0, max = 10).customer;

In the example above, the leadsBetween 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.courses!size();
derived Integer nameLength => self.owner.name!size();

The self.courses!size() evaluates to an integer, the number of the courses in the collection and the expression self.owner.name!size() will return the number of characters in the owner’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(order | order.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 return the maximum discount from these orders.

self.orders!filter(order | order.price > 100)!max(order | order.discount)

The list of available functions can be found in section 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 self and the iterators of embedding functions cannot be used in functions, proper modeling can work around this limitation. When we need the value of the self or embedding iterator, use a bidirectional relation. The bidirectional relation can be used to navigate back to self or to any embedding iterator value.

The following example selects a salesperson’s leads that are less valuable than average lead values.

entity Lead {
    field Integer value;
}

entity SalesPerson {
    relation Lead[] leads opposite-add salesPerson;
    derived Integer avgValue => self.leads!avg(l | l.value);
    derived Lead[] belowAvgLeads = > self.leads!filter(l | l.value < l.salesPerson.avgValue);
}

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

SYSTEM

current_date

Returns the current date.

Date!getVariable(category = "SYSTEM", key = "current_date")

Shortcut: Date!now()

SYSTEM

current_timestamp

Returns the current time.

Timestamp!getVariable(category = "SYSTEM", key = "current_timestamp")

Shortcut: Timestamp!now()

SYSTEM

current_time

Returns the current time.

Time!getVariable(category = "SYSTEM", key = "current_time")

Shortcut: Time!now()

ENVIRONMENT

any

Returns an OS environment variable.

Path for the current working directory: String!getVariable(category = "ENVIRONMENT", key = "PWD")

UID of running OS user: Integer!getVariable(category = "ENVIRONMENT", key = "UID")

SEQUENCE

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: Long!getVariable(category = "SEQUENCE", key = "Order")

Undefined

Field selectors and navigations 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.

Any expression containing undefined value will also evaluate to undefined, e.g. self.optionalAttribute + 2 will result undefined if self.optionalAttribute is not set.

In the case of logical values comparisons with undefined can never result in either true or false, but always in 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.instances.other will evaluate to undefined, when self.instances is undefined.

Functions applied on undefined references will also result in undefined. For example, self.instances!size() will evaluate to undefined instead of 0, when self.instances is undefined. The only exceptions are the isDefined() and isUndefined() functions. self.name!isDefined() will evaluate to false if self.name is undefined, and self.name!isUndefined() will evaluate to false if self.name is undefined.

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.

To check if a field or relation is undefined use the isDefined() or isUndefined() functions. See Any type functions.

Base types

String

String represents a text. String literals are delimited by double quotes. For example "apple".

Escape sequences allow for the representation of some nongraphic characters as well as the double quote, and backslash characters in string literals.

Escape sequences are the followings:

Escape sequence Character

\t

Horizontal tab

\n

Linefeed

\f

Form feed

\r

Carriage return

\"

Double quote

\\

Backslash

In accordance with the above, multi-line string literals are allowed, inside the string literals "\n" escape sequence can be used for line breaks. The new line character is not allowed in string literals.

In some situations, it may be inconvenient that escape sequences are interpreted differently than the rest of the string literals. To overcome these situations, use raw string literals with the "r" character before the opening double quote. For example, the string literal r"apple\nbanana" is a single line string. A typical use of raw strings is to specify regular expressions.

To define string primitive, use the type keyword with string.

Syntax:

type string <name> (min-size = <min>, max-size = <max>[, regex = <regular expression>]) ;

where the <name> is the name of the domain model string, and the mandatory <min> and <max> specify the minimum and the maximum size of the text that can be stored. Both <min> and <max> must be between 1 and 4000, and <min> cannot be greater than <max>.

The optional <regular expression> is a sequence of characters that specifies a pattern, that is for validation. The following tables summarize the regular expression constructs.

Characters Matches

x

The character x

\uhhhh

The character with hexadecimal value 0xhhhh

\t

The tab character

\n

The newline character

\f

The form-feed character

\r

The carriage-return character

\\

The backslash character

Character classes Matches

[abc]

a, b, or c

[^abc]

Any character except a, b, or c

[a-z]

a through z

[^a-z]

Any character except a through z

[a-zA-Z]

a through z or A through Z

Predefined classes Matches

.

Any character

\d

Digit: [0-9]

\D

Non-digit

\s

Whitespace character: [ \t\n\r]

\S

Non-whitespace character

\w

Word character: [a-zA-Z_0-9]

\W

Non-word character

Quantifiers Matches

x?

x, once or not at all

x*

x, zero or more times

x+

x, one or more times

x{n}

x, exactly n times

x{n,m}

x, at least n but not more than m times

x{n,}

x, at least n times

Logical operators Matches

xy

x followed by y

x|y

Either x or y

(x)

x as a group

Boundaries Matches

^

Start of line

$

End of line

Example:

type string String(min-size = 0, max-size = 128);
type string LongString(min-size = 0, max-size = 1024);
type string PhoneNumber(min-size = 0, max-size = 32, regex =
r"^(\+\d{1,2}\s)?\(?\d{2,5}\)?[\s-]\d{2,4}[\s-]\d{4}$");

This creates three domain model primitives. The first can store a maximum of 128 length text, the second may store texts up to 1024 characters.

The third string may accept only phone numbers. Please note that the regular expression is specified in a raw string using the r prefix. In the raw string the backslash (\) characters of the regular expression are not escaped. If you use the regular (non-raw) string to specify regex, you must escape the backslash characters as follows.

Example:

type string PhoneNumber (min-size = 0, max-size = 32, regex = "^(\\+\\d{1,2}\\s)?\\(?\\d{2,5}\\)?[\\s-]\\d{2,4}[\\s-]\\d{4}$");

You can find a detailed explanation of the operators and their precedence in the Operators chapter. The supported operators of string are the following:

Operator Result Meaning Example

==

Boolean

Case-sensitive comparison.

  • "apple" == "apple" is true

  • "Apple" == "apple" is false

<, >

Boolean

Case-insensitive ordering.

  • "apple" < "pear" is true

  • "Apple" > "plum" is false

+

String

Concatenates two strings into a single string result.

  • "apple" + "tree" is "appletree"

Supported functions are listed in chapter String functions.

Numeric

Numeric represents a numeric value. Numeric constants are represented by digits and at most one dot (.) symbol that cannot be the first nor the last character. For example 10 or 3.14.

To define numeric primitive, use the type keyword with numeric.

Syntax:

type numeric <name> (precision = <precision>, scale = <scale>) ;

where the <name> is the name of the domain model numeric, the <precision> is the maximum total number of decimal digits that will be stored, both to the left and to the right of the decimal point. The precision must be greater than 0. <scale> is the number of decimal digits that will be stored to the right of the decimal point (fraction). This number is subtracted from precision to determine the maximum number of digits to the left of the decimal point. Scale must be a value from 0 through precision.

The number 3.14 has a precision of 3 and a scale of 2.

Example:

type numeric Integer(precision = 9, scale = 0);
type numeric Currency(precision = 7, scale 2);

You can find a detailed explanation of the operators and their precedence in the Operators chapter. The supported operators of numeric are the following:

Operator Result Meaning Example

<, >, ==, !=, <=, >=

Boolean

Comparison.

  • -1 < 10 is true

  • -1 > 0 is false

  • 1.00 == 1 is true

  • 0.9999 != 1 is true

  • 10 >= 10 is true

  • 9 <= 8 is false

+, -, *, /

Numeric

Arithmetic operations.

  • 1 + 2 is 3

  • 2 - 3 is -1

  • 2 * 2 * 3.14 is 12.56

  • 9.0 / 2 is 4.5

mod, div

Numeric

Integer arithmetic operations, scale of both arguments must be 0.

  • 9 mod 2 is 1

  • 9 div 2 is 4

Supported functions are listed in chapter Numeric functions.

Boolean

Boolean represents a logical value: true or false.

To define boolean primitive, use the type keyword with boolean.

Syntax:

type boolean <name> ;

where the <name> is the name of the domain model boolean.

Example:

type boolean Boolean

You can find a detailed explanation of the operators and their precedence in the Operators chapter. The supported operators of boolean are the following:

Operator Result Meaning Example

not

boolean

logical negation

not true is false

and

boolean

logical AND

true and false is false

or

boolean

inclusive logical OR

true and false is true

xor

boolean

exclusive logical OR

true and true is false

implies

boolean

logical material implication

true implies false is false

? :

<any>

conditional branching

true ? "A" : "B" is "A"

false ? "A" : "B" is "B"

Besides true and false, the result of logical expressions can also be undefined. This three-valued logic is a consequence of supporting undefined to mark absent data. If a logical expression contains an undefined value, the result is calculated according to the Kleene three-valued logic. The truth table of the three-valued logic is as follows:

p q p or q p and q p xor q p implies q

true

true

true

true

false

true

true

false

true

false

true

false

true

undefined

true

undefined

undefined

undefined

false

true

true

false

true

true

false

false

false

false

false

true

false

undefined

undefined

false

undefined

true

undefined

true

true

undefined

undefined

true

undefined

false

undefined

false

undefined

undefined

undefined

undefined

undefined

undefined

undefined

undefined

p not p

true

false

false

true

undefined

undefined

Supported functions are listed in chapter Boolean functions.

Date

Date is a calendar date with no time nor time zone information. Date is delimited by backtick, for example `2020-02-18`.

To define date primitive, use the type keyword with date.

Syntax:

type date <name> ;

where the <name> is the name of the domain model date.

Example:

type date Date;

You can find a detailed explanation of the operators and their precedence in the Operators chapter. The supported operators of date are the following:

Operator Result Meaning Example

<, >, ==, !=, <=, >=

Boolean

Comparison.

`2020-02-18` > `2020-01-01` is true

Arithmetic operators are not available for the date type. If you want to perform calculations with a date value, first convert it to a timestamp. Then use the timestamp functions and convert it back to a date type. The following example adds a week to a date.

Timestamp!of(date = `2021-02-28`)!plusDays(7)!date()

Supported functions are listed in chapter Date functions.

Time

Time represents the time of day, independent of any particular day and with no time zone information.

Time literals can be represented as literals using the following syntax.

Syntax:

`<hh>:<mm>[:<ss>]`

where

  • <hh> refers to a zero-padded hour between 00 and 23,

  • <mm> refers to a zero-padded minute between 00 and 59,

  • <ss> refers to a zero-padded second between 00 and 59,

  • the surrounding backticks are required.

The valid values of time are between `00:00:00` and `23:59:59`.

The following examples are valid time literals.

Example:

`23:59:59`
`23:59`

To define time primitive, use the type keyword with time.

Syntax:

type time <name> ;

where the <name> is the name of the domain model time.

Example:

type time Time;

You can find a detailed explanation of the operators and their precedence in the Operators chapter. The supported operators of date are the following:

Operator Result Meaning Example

<, >, ==, !=, <=, >=

Boolean

Comparison.

`11:30` > `10:29` is true

Arithmetic operators are not available for the time type. If you want to perform calculations with a time value, first convert it to a timestamp. Then use the timestamp functions and convert it back to a date type. The following example adds 6 hours to a time.

Timestamp!of(date = `2021-02-28`, time = `18:59:00`)!plusHours(6)!time()

Supported functions are listed in chapter Time functions.

Timestamp

Timestamp is a value identifying when a certain event occurred or when a certain event will occur. The accuracy of the timestamp is in milliseconds, which is used for comparison.

Timestamp is surrounded by backticks ( ` ) and formatted using ISO-8601 standard, for example

`2020-02-18T10:11:12Z`

`2019-07-18T11:11:12+02:00`

`2019-07-18T11:11:12.003+02:00`

To define timestamp primitive, use the type keyword with timestamp.

Syntax:

type timestamp <name> ;

where the <name> is the name of the domain model timestamp.

Example:

type timestamp Timestamp;

Some timestamp functions may expect time zone information as input. Use the following format to specify the time zone:

Syntax:

`[+|-]<hh>:<mm>`

where

  • <hh> refers to a zero-padded hour between 00 and 23,

  • <mm> refers to a zero-padded minute between 00 and 59,

  • the surrounding backticks are required.

Example:

`+02:00`

You can find a detailed explanation of the operators and their precedence in the Operators chapter. The supported operators of timestamp are the following:

Operator Result Meaning Example

<, >, ==, !=, <=, >=

Boolean

Comparison

  • `2020-02-18T10:11:12Z` != `2020-02-18T00:00:00Z` is true

  • `2020-02-18T09:11:12Z` == `2020-02-18T10:11:12+01:00` is `true

Arithmetic operators are not available for the timestamp type. If you want to perform calculations with a timestamp value, use Timestamp functions. An easy way to use arithmetic is to convert timestamp values to milliseconds.

Supported functions are listed in chapter Timestamp functions.

Binary

The binary data type contains an unlimited number of bytes.

To define binary primitive, use the type keyword with binary.

Syntax:

type binary <name> (mime-types = <mime-types>, max-file-size = <max-file-size>) ;

where the <name> is the name of the domain model binary, the <mime-types> is a list of mime types indicating the accepted formats, and the <max-file-size> is the maximum size of the file.

The <max-file-size> can be expressed in bytes, kilobytes, megabytes or gigabytes using an integer and an optional unit. The unit can be based on powers of 10 (kB, MB, GB) or on powers of 2 (KiB, MiB, GiB). If there is no unit specified, the value is interpreted in bytes. The table below gives the value in bytes for each unit.

Unit Value in bytes

kB

1000

MB

10002

GB

10003

KiB

1024

MiB

10242

GiB

10243

The <mime-types> is a comma-separated list of strings representing mime types. A mime type string must consist of a type and a subtype separated by / in the following format:

"<type>/<subtype>"

or

"<type>/*"

Valid mime types and subtypes can only contain alphabetic characters, digits and ., _, -, +.

Examples:

type binary Document (mime-types = ["application/pdf", "application/msword"], max-file-size = 10MB);

The example above defines a Document type that accepts pdf or ms-word documents up to 10 megabytes in size.

type binary Image (mime-types = ["image/*"], max-file-size = 500kB);

The second example defines an Image type that accepts any image format up to 500 kbytes in size.

Supported functions are listed in chapter Binary functions.

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 isDefined() cannot be used for collections.

Example: "apple"!isDefined() is true

isUndefined() : boolean

Evaluates to true if the value does not exist, e.g. it is undefined. Note, that isUndefined() cannot be used for collections.

Example: "apple"!isUndefined() is false

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:

"apple"!orElse("grape") is "apple"

2!orElse(3) is 2

self.name!orElse("unknown") is "unknown" if self.name is UNDEFINED, otherwise it returns the value of self.name.

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 in chapter External variables.

Examples:

"apple"!orElse("grape") is "apple"

2!orElse(3) is 2

self.name!orElse("unknown") is "unknown" if self.name is UNDEFINED, otherwise it returns the value of self.name.

String functions

size() : numeric

Returns the number of characters in the string.

Example: "apple"!size() is 5

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: "apple"!first(count = 2) is "ap"

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: "apple"!last(count = 1) is "e"

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: "apple"!position(substring = "p") is 2

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: "apple"!substring(offset = 2, count = 3) is "ppl"

lower() : string

Returns the original string with all alphabetic characters in lowercase.

Example: "ApPlE"!lower() is "apple"

upper() : string

Returns the original string with all alphabetic characters in uppercase.

Example: "ApPlE"!upper() is "APPLE"

capitalize() : string

Returns a string with the first letter capitalized and all other characters lowercased.

Example: "apPlE"!capitalize() is "Apple"

matches(pattern = <pattern>) : boolean

Returns true if the string matches a given regular expression <pattern>, false otherwise. A detailed description of the regular expression can be found in the String section.

Example: "apple"!matches(pattern = r".*pl.") is true

like(pattern = <pattern> [, exact = <exact>]) : boolean

Returns true if the string matches a given <pattern>, false otherwise. The optional <exact> is a boolean parameter. If <exact> is true the pattern matching is case-sensitive. If <exact> is false the pattern matching is case-insensitive. If no <exact> argument is passed, the pattern matching is case-sensitive.

There are two wildcards used in the <pattern>:

  • percent sign (%)

  • underscore (_)

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:

"apple"!like(pattern = "%pl_") is true

"apPLe"!like(pattern = "_pple", exact = false) is true

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: "apple"!replace(oldstring = "le", newstring = "endix") is "appendix"

trim() : string

Returns the original string with all leading and trailing whitespace characters removed.

Example: " apple "!trim() is "apple"

ltrim() : string

Returns the original string with all leading whitespace characters removed.

Example: " apple "!ltrim() is "apple "

rtrim() : string

Returns the original string with all trailing whitespace characters removed.

Example: " apple "!rtrim() is " apple"

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 (" ") is used by default.

Examples:

"apple"!lpad(size = 6) is `" apple"

"ple"!lpad(size = 5, padstring = "ap") is "apple"

"ple"!lpad(size = 6, padstring = "ap") is "apaple"

"ple"!lpad(size = 7, padstring = "ap") is "apapple"

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 (" ") is used by default.

Examples:

"apple"!rpad(size = 6) is "apple "

"app"!rpad(size = 5, padstring = "le") is "apple"

"app"!rpad(size = 6, padstring = "le") is "applel"

"app"!rpad(size = 7, padstring = "le") is "applele"

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:

1!round() is 1

7.89!round() is 8

7.89!round(scale = 1) is 7.9

2.50!round() is 3

-2.5!round() is -3

-7.89!round() is -8

floor() : numeric

Returns the largest integer that is less than or equal to the argument.

Example:

1!floor() is 1

2.9!floor() is 2

-2.9!floor() is -3

ceil() : numeric

Returns the smallest integer that is greater than or equal to the argument.

Example:

1!ceil() is 1

2.9!ceil() is 3

-2.9!ceil() is -2

abs() : numeric

Returns the absolute value of the argument.

Example:

1!abs() is 1

2.9!abs() is 2.9

-3!abs() is 3

asString() : string

Numeric value as string.

Example: 123456.789!asString() is "123456.789"

Boolean functions

asString() : string

Boolean value as string.

Example: true!asString() is "true"

Date functions

year() : numeric

Returns the year part of date.

Example: `2021-03-02`!year() is 2021

month() : numeric

Returns the month part of date.

Example: `2021-03-02`!month() is 3

day() : numeric

Returns the day part of date.

Example: `2021-03-02`!day() is 2

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: Date!of(year = 2011, month = 1, = day = 28) is `2011-01-28`

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: `1970-01-01`!dayOfWeek() is 4, i.e. it was Thursday.

dayOfYear() : numeric

Returns the day of the year for a given date (a number from 1 to 366).

Example: `2020-02-01`!dayOfYear() is 32

asString() : string

Date value as string according to the ISO-8601 standard.

Example: `2021-03-02`!asString() is "2021-03-02"

Time functions

hour() : numeric

Returns the hour part of time.

Example: `23:15:59`!hour() is 23

minute() : numeric

Returns the minute part of time.

Example: `23:15:59`!minute() is 15

second() : numeric

Returns the second part of time.

Example: `23:15:59`!second() is 59

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: Time!of(hour = 13, minute = 45, second = 00) is `13:45:00`

asString() : string

Time value as string according to the ISO-8601 standard.

Example: `23:15:59`!asString() is "23:15:59"

Timestamp functions

date() : date

Returns the date part of the timestamp in UTC+0 timezone.

Example:

`2019-07-18T01:11:12Z`!date() is `2019-07-18`

`2019-07-18T01:11:12+02:00`!date() is `2019-07-17`

time() : time

Returns the time part of the timestamp in UTC+0 timezone.

Example:

`2019-07-18T01:11:12Z`!time() is `01:11:12`

`2019-07-18T01:11:12+02:00`!time() is `23:11:12`

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 `00:00:00`.

Example:

Timestamp!of(date = `2021-02-28`, time = `10:30:01`) is `2021-02-28T10:30:01Z`

Timestamp!of(date = `2021-02-28`) is `2021-02-28T00:00:00Z`

asMilliseconds() : numeric

Returns the timestamp in milliseconds from 1970-01-01T00:00:00Z.

Example: `1970-01-01T00:01:00Z`!asMilliseconds() is 60000

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: Timestamp!fromMilliseconds(milliseconds = 60000) is `1970-01-01T00:01:00Z`

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:

`2019-07-18T01:11:12Z`!plus(days = 1) is "2019-07-19T01:11:12Z"

`2019-07-18T01:11:12Z`!plus(days = 1, hours = 2) is "2019-07-19T03:11:12Z"

`2019-07-18T01:11:12Z`!plus(days = 1, hours = -24) is "2019-07-18T01:11:12Z"

asString() : string

Timestamp value as string according to the ISO-8601 standard.

Example: `2019-07-18T01:11:12Z`!asString() is "2019-07-18T01:11:12Z"

Binary functions

There are no binary functions.

Enumeration functions

asString() : string

Enumeration literal as string.

Example: Title#MRS!asString() is "MRS"

Instance functions

typeOf(entityType = <entityType>) : boolean

Evaluates to true if the given instance conforms to the <entityType> but not any subtype of the <entityType>. That is, an instance of <entityType> and not an instance of any subtype of <entityType>.

Example: self.field!typeOf(entityType = Lib::MyType)

kindOf(entityType = <entityType>) : boolean

Evaluates to true if the given instance conforms to the <entityType>. That is, an instance of <entityType> or an instance of any subtype of <entityType>.

Example: self.field!kindOf(entityType = Lib::MyType)

container(entityType = <entityType>) : instance

Returns the container of the instance if that matches the given <entityType>. Type matching works according to the typeOf() function.

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 kindOf() function.

Example: self.field!asType(entityType = MyApplication::MyEntity)

memberOf(instances = <instances>) : boolean

Indicates if the <instances> collection contains the given instance.

Collection functions

head(<variable name> | <selector> [ASC|DESC], …​) : collection

First, it sorts the given collection by the list of selectors, 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.

tail(<variable name> | <selector> [ASC|DESC], …​) : collection

First, it sorts the given collection by the list of selectors, 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.

any() : instance

Returns an arbitrary element of a collection.

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.

contains(instance = <instance>) : boolean

Indicates if the given collection contains the given <instance>.

filter(<variable name> | <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: self.orderDetails!filter(od | od.price < 10)

anyTrue(<variable name> | <logical expression>) : boolean

Returns true if the logical expression is true for any element in the collection.

Example: self.orderDetails!anyTrue(od | od.price < 10)

allTrue(<variable name> | <logical expression>) : boolean

Returns true if the logical expression is true for all the collection elements.

Example: self.orderDetails!allTrue(od | od.price < 10)

anyFalse(<variable name> | <logical expression>) : boolean

Returns true if the logical expression is false for any element in the collection.

Example: self.orderDetails!anyFalse(od | od.price < 10)

allFalse(<variable name> | <logical expression>) : boolean

Returns true if the logical expression is false for all the collection elements.

Example: self.orderDetails!allFalse(od | od.price < 10)

min(<variable name> | <selector>) : numeric

Returns the minimum value of the set of values selected by the <selector>.

Example: self.orderDetails!min(od | od.price)

max(<variable name> | <selector>) : numeric

Returns the maximum value of the set of values selected by the <selector>.

Example: self.orderDetails!max(od | od.price)

avg(<variable name> | <selector>) : numeric

Returns the average of the set of values selected by the <selector>.

Example: self.orderDetails!avg(od | od.price)

sum(<variable name> | <selector>) : numeric

Returns the sum of the set of values selected by the <selector>.

Example: self.orderDetails!sum(od | od.price)

Built-in models

  • Primitive types

  • User management

  • Audit log