OQL
Features
- Select queries with navigation over arbitrarily many class relationships
- Inheritance and polymorphism always assumed
- Type specialization (downcasting) in navigation
- Ordering and grouping
- Unions of select queries
- Objects, values, or aggregate functions in results
- Windowing of the result set (offset and limit)
- Parameterized queries
Syntax
This is an informal and not strictly precise definition of the syntax, yet it is practically sufficient as a quick reminder. The syntax is generally very close to that of SQL, and the following description illustrates the supported features.
SELECT [DISTINCT] expression [[AS] alias], expression [[AS] alias]...
FROM term [[AS] alias], term [[AS] alias]...
[WHERE condition]
[GROUP BY expression [[AS] alias], expression [[AS] alias]...]
[HAVING condition]
[UNION [DISTINCT | ALL] query]
[ORDER BY expression [ASC | DESC], expression [ASC | DESC]...]
[LIMIT size | LIMIT ALL]
[OFFSET offset]
Navigation terms
- Class name (identifiers may be quoted with "", e.g. "from")
- term.property
- term:DowncastClass.property
Expressions
- * (in SELECT only)
- term.* (in SELECT only)
- term
- term.property
- constant
- #parameter#
- arithmetic operators: +, -, *, /, MOD
- aggregate functions: MAX, MIN, SUM, AVG, COUNT, COUNT(*), COUNT(DISTINCT)
Conditions
- equality and inequality: =, ==, !=, <>
- comparison: <, <=, >, >=, BETWEEN ... AND ...
- LIKE 'pattern', LIKE #param#
- IN (constant, constant...)
- IS NULL
- Boolean operators: AND, OR, NOT
Constants
(type qualifier may be omitted if type can be inferred from the context):
- Text: 'text'
- Boolean: true, false
- Integer:
- decimal: 42
- hexadecimal: 0x2A, x'2A'
- octal: 052
- binary: 0b101010, b'101010'
- Decimal: 42.0, decimal'42.0', currency'42.0'
- Real: 42.0, 6.022E+23, real'42.0'
- Date: date'yyyy-MM-dd'
- Time: time'HH:mm:ss.SSS'
- DateTime: datetime'yyyy-MM-dd HH:mm:ss.SSS', timestamp'yyyy-MM-dd HH:mm:ss.SSS'
- Enumerations: enumClassName'value' (e.g. Status'APPROVED')
Semantics
This is just a brief description of the semantics of OQL queries, especially of those parts that are specific to OQL or not completely analogue to the counterparts in SQL. These explanations should be sufficient to those already familiar with SQL or HQL, while in-depth and precise explanations of the semantics of OQL queries can be found in the book: Milicev D., Model-Driven Development with Executable UML, Wiley/Wrox, July 2009, ISBN 9780470481639
The basic form of an OQL SELECT query is the same as in SQL:
SELECT projectionClause FROM navigationClause WHERE selectionClause
For example:
SELECT p, p.name, p.email
FROM Course c, c.students p
WHERE c.name = 'Introduction to OQL'
Therefore, an OQL query has three main parts: the navigation clause (navigationClause
following FROM
), the selection clause (selectionClause
following WHERE
), and the projection clause (projectionClause
following SELECT
).
The navigation clause specifies navigation over the object structure. The navigation designates a type of tuples of values, each value representing a placeholder for an instance of a certain type. The navigations over properties also introduce implicit constraints over the returned tuples. The constraints enforce that the objects referred to by the placeholders are linked by links of the corresponding relationships. In the previous example, each tuple designated by the navigation clause has two values named c
and p
, where c
is a placeholder for an object of Course
, while p
is a placeholder for an object of Person
(the type of the navigated property students
). The implicit restriction introduced by the navigation clause is that the objects referred to by p
and c
are linked, so that p
is an element of the collection designated by c.students
.
In short, the navigation clause defines a collection of tuples of values, one value for each term in the clause (the terms are separated by commas). The value in the tuple is named by the optional alias provided in the term, and can be referred to by that name in the clauses of the same query. Therefore, the navigation clause also introduces the names of the values in the tuple, the scope of the names being the same query.
As it can be concluded, the collection of tuples is obtained by the so-called inner join. (All OQL joins are inner joins. We will support outer joins in future versions.) This means that the collection of the tuples designated by the navigation clause consists of a sub-collection of a Cartesian product of all existing instances of the types, such that the values (referring to instances of classifiers) implied by the terms satisfy the following constraint: If a term in the clause specifies navigation over a property, the value implied from it must be an element of the collection designated by the referred property.
For the previous example, the resulting collection of tuples defined by the navigation clause is the collection of tuples (c, p)
, where c
is an object of Course
, while p
is an object of Person
(the type of the navigated property students
), and p
is an element of c.students
. On the other hand, consider the tuple defined by the following FROM
navigation clause, assuming that students of a course are objects of class Person
, and the class Teacher
is a subclass of the class Person
:
Course c, c.students:Teacher t, c.name n1, t.taughtCourses tc, tc.name n2
This consists of the named elements as shown in the following table:
Name | Type | Additional Implied Constraint |
---|---|---|
c | Course | None |
t | Teacher | t is in c.students |
n1 | String | n1 is in c.name |
tc | Course | tc is in t.taughtCourses |
n2 | String | n2 is in tc.name |
As a result, the navigational clause defines a collection of all tuples retrieved from the object space satisfying the described criteria. In the general case, the collection is a bag, because the collections designated by the navigation terms can be bags, and the navigations can be combined over several properties (for example, t.taughtCourses.teachers.taughtCourses
). Each value in the tuple is of the type defined in the term, and is always of cardinality exactly one (that is, it is never a null
).
There are two kinds of terms allowed in the navigation clause.
The first kind of term is a simple class reference. The term simply specifies a class and an optional alias. An example is the term Course c
. The type of the value designated by such a term is that class.
The second kind is a term that defines navigation over properties of objects. The navigation must start with a name defined by another term before this term. For example, the following terms define navigations:
c.students:Teacher t, c.name n1, t.taughtCourses tc, tc.name n2
A term can include a navigation chain over several properties, as shown in the following example:
t.taughtCourses.students
t.taughtCourses.students.attendedCourses
t.taughtCourses.students.attendedCourses.name
t.taughtCourses.students.attendedCourses.students
t.taughtCourses.students.attendedCourses.students.name
The number of hops over properties in a navigation chain can be arbitrary, but finite.
The type of the term is that of the last property in the navigation chain. Optionally, the type can be specialized to a subtype of the property’s type by the downcasting colon operator:
t.taughtCourses.students:Teacher
It specifies that the designated collection is restricted to objects of that subclass only (and all its subclasses; substitution/polymorphism is always assumed). The term above specifies the collection of all students of all courses taught by the given teacher t
, but only those students that are also teachers (recall that the class Teacher
is derived from the class Person
).
The downcasting operator can appear anywhere in the navigation chain. In that case, the further navigation may refer to properties of the specializing type. For example, if t
is of type Teacher
:
t.taughtCourses.students:Teacher.taughtCourses.students:Teacher.title
The collection of values designated by a navigation term is obtained as an inner join of all elements in the chain. For example, the collection designated by this term is obtained by the following set of nested iterations:
t.taughtCourses.students.attendedCourses
For each instance t
of the type specified by the term that defines t
(Teacher
in this case) — for each element e1
in t.taughtCourses
, for each element e2
in e1.students
, for each element e3
in e2.attendedCourses
, return e3
. It is obvious that if the chain has more than one navigation hop, the resulting collection can be non-unique (that is, a bag) in a general case, since the resulting values can be multiplied.
The selection clause after WHERE
constrains further the resulting collection of tuples by specifying an expression. The expression must return a Boolean result. The expression can refer to all the names of the values in the tuple defined by the navigation clause, or to a parameter of the query. Parameters are arbitrary identifiers enclosed in # signs (for example, #nameParam#
). Actual values of the parameters will be provided by the invocation of the query through an API call, when the query is executed. The WHERE
expression is evaluated for every tuple in the collection designated by the navigation FROM
clause. The resulting collection of tuples is reduced by the selection WHERE
clause to all these and only these tuples for which the expression evaluates to true.
Finally, the projection clause following SELECT
simply projects the resulting tuple to another tuple by keeping some of the values in the tuple and removing the others, or even adding new ones. Think of the collection of the tuples resulting from the rest of the query (the navigation and selection clauses) as a table with columns identifying values and rows representing the selected tuples. Then, the projection clause simply removes some columns, while keeping the others or adding new ones. The values can be the names of the terms from the navigation FROM
clause or their properties. All rows are preserved (no duplicates are removed, unless the DISTINCT
specifier is used). Optionally, the projection clause renames some columns by providing different names (aliases) for some values in the tuples.
The resulting collection of tuples can be grouped or ordered, as in SQL. The resulting set can be limited to a subcollection (a window) by providing the optional OFFSET
(0-based, offset 0 is default) and LIMIT
(the size of the window). For example, the following query returns the third group of 10 students (as objects) of the given course, ordered by their names:
SELECT p, p.*
FROM Course c, c.students p
WHERE c.name = 'Introduction to OQL'
ORDER BY p.name
LIMIT 10 OFFSET 20
A union can be made of queries that have homogeneous result sets. This means that the tuples of the results of the queries in the union must have the same number of columns, and the corresponding columns must be of the same type or of a type with a common supertype; the resulting union will then have that common supertype as each column type. The resulting union is a true set union with distinct values (DISTINCT
is default), unless the ALL
specifier is used.