Developed at the
ODRA – Object Database for Rapid Application development
Description and Programmer Manual
SBQL supports popular imperative programming language’s constructs and abstractions, including control structures (if, loop, etc.), procedures, classes, methods and others. All are fully orthogonal with SBQL queries and use SBQL queries as their components. The constructs and abstractions do not use any other expression language: all the expressions, in all contexts, are SBQL queries.
In ODRA any variable must be declared. Collections, including persistent collections, are ODRA variables too. To use the name of a variable in a query the declaration environment must be visible to the environment against which the query is executed.
The variable declaration has the following syntax:
name: type [cardinality]
where name declares the variable name, type establishes the variable type (see variable types), and cardinality optionally declares the variable minimal and maximal cardinality constraints. The following syntax is assumed for cardinalities:
[minCard .. maxCard]
If the cardinality specification is not present in a variable declaration, the system implicitly assumes the default cardinality [1..1].
Because ODRA is a database system and SBQL is a query language it is a common situation where a variable declaration concerns a collection of objects. Example cardinalities can look as follows:
[0..*] – a collection with unlimited size, including an empty collection.
[1..*] – a collection having at least one element
[0..1] – a collection having zero or one element (optional element); in SBQL this is the only way to say that “null is allowed”.
If the cardinality specification is not present in the variable declaration, the system implicitly assumes the default cardinality [1..1].
ODRA has five built-it simple types described with the following keywords:
Apart from simple types ODRA support structural types. The complex type declaration syntax consist of keyword record, followed by the list of the structure field:
ODRA supports named types that can be introduced with use of keyword type. They are macros that allow the programmer to shorten the source code. The syntax for a named type declaration is the following:
type typename is type
declares named type PersonT which is a structure type with two fields.
Declared named types can be used in variable declaration, e.g.:
Note: because ODRA supports structural type equivalence only, the above variable declarations are equivalent to:
In ODRA it is possible to declare a recursive type if one of the fields that cause the recursion is optional. Consider the following example:
The EmpType type possesses an optional field worksIn that is of the FirmType type. The FirmType type possesses a non-optional field employs that is a collection of EmpType objects. This kind of a recursive type definition is allowed because of the optionality of the worksIn field.
Notice that in the above example we deal with true recursion: fields worksIn and employs declare structures rather than pointers. Pointers are declared with the use of ref (see next).
A pointer type allows for declaring SBQL pointer objects. The value of a pointer object is a reference to an object. Unlike typical object-oriented programming languages we have decided that a pointer type declaration is represented by the variable (object) name that the pointer points to. The variable must be available the environment visible to the context of the pointer declaration. For example if the following variable declaration is available:
we can declare a reference variable to the declared Person object as:
This decision concerning the methods of typing pointers has motivation in the way how database schemata are defined. In database schemata associations (e.g. written in UML) connect objects of given names, disregarding their types. Moreover, the programmer that navigates along a pointer uses (e.g. in a path expression) pointer names and object names rather than their types. For instance (c.f. the schema presented at Fig. 2-4), the programmer can write the following query (get the surname of the Doe’s boss):
If in the schema the pointers worksIn and boss would be typed by the type of their objects, in many cases it would be impossible to see to which objects the pointers lead to. Typing pointers by object names (hence by their types, but indirectly) is much more precise concerning schema specification and much more understandable for the programmers during writing SBQL queries.
7.1.2 Variable declaration environment
The variables in ODRA can be declared as:
- permanent – if the declaration is placed at the module level. These variables are kept in the persistent store.
- temporal – if the declaration at the module level is preceded by the keyword session.
- local – if the declaration is placed inside a procedure/method body.
The declaration place does not force the persistence status (except objects that are created automatically). The status can be modified with the use of create operation parameters (see: object creation). The general principle says that an object can be created in an environment defined by the declaration or in “less” persistent one. For example, if a variable has been declared at the module level as persistent, it can be created as temporal as well as local (inside the procedure body).
Declaration of an integer variable named x with the default cardinality:
Declaration of a complex variable named emp with three fields (string name, integer salary and optional pointer object worksIn pointing to a Firm object).
Objects are created by the create operator. The system automatically checks if an object creation conforms to the declared type and cardinality. The syntax is the following:
create [where] name(query);
The create operation is orthogonal to persistency - no difference between creating persistent and transient objects. The operator is macroscopic which means that the parameter query can return a bag and the number of created objects will be equal to the result bag cardinality. The parameter query is automatically dereferenced. The operator is optionally parameterized with the place indicator (where). It can be one of the keywords permanent, temporal and local that have been explained previously.
If no place indicator is specified, the system creates an object in a default environment. It depends on the create execution environment. If the operation is executed dynamically (ad hoc queries), the default environment is the persistent environment (created object will be persistent). If the operation is executed in the context of procedure the default environment is the local procedure environment (the created object will be automatically removed while the local environment disappears).
The operator can be used to create each kind of SBQL objects (simple, complex, pointer).
To create simple objects the parameter query must return a simple value (or a bag of simple values) or a reference to a simple object (or a bag of such references). In the latter case the result of the query will be automatically dereferenced before passing it to create operator. If the type checker is enabled, the statements requires appropriate declarations.
Create a simple object of the integer type named amount that value is a result of atomic query; the persistency status depends on the context:
Create two persistent simple objects of type date named possibleMeetingDate.
Create (possibly many) local simple string objects named fullName that value is a result of more complex query:
Notes that local place indicator is available only inside the procedure/method body.
To create a pointer object (or pointer objects) the query must return a reference to an object (or references to objects). If the type checker is enabled, the statements requires appropriate declarations.
Create (possibly many) persistent reference objects named highPayed that store references to high payed employees
Create (possibly many) reference objects named johnWorkPlace that store identifiers of all the departments employing employees with the first name ‘John’.
It is assumed that worksIn is a reference object. The persistency status depends on the context.
To create a complex object the result query must return structures with named fields or a reference to a complex object. In the latter case the result will be automatically dereferenced before passing it to create operator. As previously, if the argument query returns a bag, many objects with the same name are created. If the type checker is enabled, the statements requires appropriate declarations.
Create a single persistent complex object named Emp.
Create (possibly many) temporal complex objects named Car being copies of an existing one:
If an object declaration has the cardinality with the minimal bound greater than 0, the minimal required number of objects must be created during environment (persistent module, session module or local) initialization. For example consider declaration:
The default cardinality ([1..1]) requires presence of one object named x. Thus the process of initialization the declaration environment creates an object with a default value.
The assignment operator allows for changing an object value. The syntax is the following:
lQuery := rQuery;
where lQuery and rQuery are the left and right hand operand expressions.
The operand queries must return single values (the operator is not macroscopic). The result of left hand operand query is a reference to an object. The result of right hand operand query is automatically dereferenced. The operator can be used to assign a value to an each kind of SBQL object (simple, complex, pointer). The assignment expression returns the reference of the updated object.
Simple object assignment requires a reference to simple object as the left hand operand and a simple value as the right hand operand.
The type of the right hand value must be compatible with updated variable object type. If the types are not the same, the system is trying to perform automatic coercion. If no automatic coercion is available, the type error is reported (see: errors).
A complex object assignment requires reference to a complex object as the left hand operand and a structure with named elements (binders) as the right hand operand. In this context the assignment operation reassembles create operation except that the updated object does not change its identifier. All its sub-objects are removed and new sub-objects are created on the basis of right hand assignment operand. Thus the structure must include elements with names that determine sub-object names.
The right hand structure fields have to be named. The types of structure fields have to be compatible with type of corresponding sub-objects declaration (see: automatic coercion). If declared cardinality of a particular sub-object is greater than zero the corresponding structure field must be available in the structure.
The example below updates an employee object with new values:
A pointer assignment is similar to an assignment to a simple object but requires a reference to object as the right hand operand. Because the assignment operator performs automatic dereference on the result returned by rQuery it is usually needed to use ref keyword to avoid the dereference.
The right hand reference have to represent an object having a type declared as a pointer target type.
The example below changes the Doe employee work place by changing the value of the worksIn pointer object.
Insertion allows to insert an object into another object. The syntax is as follows:
lQuery :< rQuery;
where lQuery and rQuery are the left and right hand operands.
The result of left hand operand query is a reference to a complex object. The result of the right hand operand query is a bag of references to objects being inserted. If the insertion operation concerns objects placed in the same store, the identifier of the inserted object will not be changed (it can be perceived as moving an object from one environment to another one). If the insertion concerns objects that are placed in different stores (e.g. inserting a local object into a persistent one), the identifier of the inserted object may change.
To make types compatible, the name of an inserted object must be declared as the name of one of sub-objects of the left hand operand. The cardinality of the declaration has to be different from the default ( [1..1] ).
The example below inserts new prevJobPlace pointer object into Jones employee:
The create and insert operator is a combination of the create operator and the insert operator. It allows the programmer to create a new object directly inside the environment of the target object. The syntax is following:
lQuery :<< name(rQuery);
where lQuery and rQuery are the left and right hand operands.
The result of left hand operand query is a reference to a complex object. The right side operand (name plus rQuery) have the same meaning as for the create operator (see Object creation).
The target object must possess a declared sub-object with a name and the type of the declared object must be compatible with the result of the right hand query. In contrast to the create operator, the creating object declaration is not required in the environment where the query is executed.
Example: Insert new prevJobPlace pointer object into Jones employee (compare it to 6.4.1):
The delete operator makes it possible to remove objects from the store. The operation is fully orthogonal to the persistence status. The syntax is following:
The result of the operand query have to be a reference or a bag of references (the operator is macroscopic). The operator can be used to delete each kind of SBQL objects (simple, complex, pointer).
The declared cardinality of deleted object must be different from the default ([1..1]). If the declared minimal cardinality is greater that zero, the runtime check is performed to assure that after deletion the number of objects will not be lower than the minimal cardinality constraint.
Delete the Marketing department.
Delete the location
ODRA SBQL implements typical program control flow instructions. In most cases the syntax is similar to Java.
7.7.1 Conditional operator
ifstatement ::= if query then statement else statement1
ifstatement ::= if query then statement
The query must return a boolean value. If the query returns true then statement is executed; otherwise statement1 is executed. In the second case if the query returns false, no action is performed.
Example: If the number of employees hired in year 2006 is greater than in 2005, insert to the report a note “assumed employment increase achieved” otherwise insert note “assumed employment increase unachieved”,
7.7.2 While, do-while loops statements
whilestatement ::= while query do statement
dowhilestatement ::= do statement while(query)
The query must return a single boolean value. In the first case statement is executed repeatedly, where each next iteration is started if query returns true. The second case is similar, but for the first time statement is executed without testing query; all next iterations depend on whether query returns true.
The final result: i = 50 (no loop is performed)
The final result: i = 40 (one loop is performed).
forstatement ::= for(initstmnt; cquery; incrstmnt) do statement
The semantics is similar to C/C++. The statement is executed till cquery returns false. The initstmnt determinies the statement that initiates the loop. The incrstmnt determines the incremental statement.
A foreach statement allows to iterate through elements of a collection. The collection is determined by a query and the element of the collection parameterizes the statement executed in each iteration loop. The semantics is similar to the semantics of non-algebraic operators described before.
foreachstatement ::= foreach query do statement
The query is evaluated first. It should return a bag; an individual element is coerced to a bag with one element. For each bag element r its environment is calculated (see: SBQL non-algebraic operators) and the statement is executed against this environment. After statement execution the environment is destroyed.
The statement below increases the salary by 100 to all the employees working in the marketing department if the employee previous salary was below the average.
Without “iteration variable”:
With the “iteration variable” over Emp objects:
The SBQL implementation for ODRA includes the mechanism known as exceptions handling. The syntax and semantics of the mechanism is similar to the one known from Java. The mechanism has been introduced to provide separation of the main program logic from some exceptional situations, mostly program errors.
Throwing an exception. An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions. When an error occurs within the procedure/method, the system or a programmer creates an exception object that is passed during runtime in the process called throwing an exception. The exception throwing is performed with use of the throw statement:
The above example shows implementation of the divide procedure that throws an exception if the second argument is equal to zero. In line 3 the exception object is defined with use of a variable declaration of type Exception. Because ODRA works on collections the declaration is equivalent to e:Exception[1..1] (currently it is only possible to throw single exceptions). Because this is a single element collection, the object is automatically created in the local procedure store. The exception type name represents the name of built-in system class that posses only setMessage(message:string) and getMessage():string operations. Exception objects should be instances of this class or its user-defined sub-classes. The equivalent SBQL definition of the built-in Exception class is presented below:
Catching an exception. To catch an exception thrown within the code execution an exception handler is to be used. The exception handler consists of three blocks: try, catch and finally.
If an exception occurs within the try block, that exception is handled by an exception handler associated with it (that is: placed directly after the try block).
The argument of a catch block specifies an exception type handled by the given block. The overall rule requires that exceptions are to be handled in the order determined by the exception types inheritance hierarchy. A more specific exception type is to be caught before more generic ones. The number of catch blocks is limited only to the number of exception types that appear in the code placed inside the try block.
The code placed inside an optional finally block will be executed no matter if exceptions occur or not. Thus this is the place for a code that is always executed.
The example assumes that if the division by zero has appeared (that is, the divide procedure throws an exception) the result variable is set to zero. Additionally no matter the division succeed or ends with an error, the const value will be always added to result.
Comments follow the Java convention:
// precedes the comment to the end of a line
/* and */ are comment parantheses for comments that span several lines. Nested comments are not supported.
Last modified: February 21, 2009
 The type conformity based on type names (like e.g. in Pascal) is currently unsupported, but considered in next releases.
 Currently ODRA makes it possibile to switch off type checking, however, this is not recommended.
 Note that “iteration variable” is not an SBQL term. It is used as an informal notion in a lot of other proposals. In SBQL the “as” operator has formal and very simple semantics.