Developed at the

Polish-Japanese Institute of Information Technology

Chair of Software Engineering

SBA and SBQL home pages

© Copyright by ODRA team, © Copyright by PJIIT

 

 

 

 

ODRA – Object Database for Rapid Application development

Description and Programmer Manual

 

 

 

by Radosław Adamus and the ODRA team

 

7. SBQL Imperative Statements

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.

 

7.1 Variable Declarations

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].

7.1.1 Variable types

Simple types

ODRA has five built-it simple types described with the following keywords:

  1. integer
  2. real,
  3. string,
  4. boolean
  5. date.

Complex types

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:

record {

 field1:type[cardinality];

 [field2:type[cardinality];

 

 fieldh:type[cardinality];]

}

 

Named types

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

Example:

type PersonT is record { name:string; age:integer; }

declares named type PersonT which is a structure type with two fields.

Declared named types can be used in variable declaration, e.g.:

georg:PersonT;

michael:PersonT;

Note: because ODRA supports structural type equivalence[1] only, the above variable declarations are equivalent to:

georg: record { name:string; age:integer; }

michael: record { name:string; age:integer; }

 

Recursive types

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:

     type EmpType is record {

                        fName:string;

                        lName:string;

                        age:integer;

                        married:boolean;

                        worksIn:FirmType[0..1]; }

                       

     type FirmType is record {

                        name:string;

                        employs:EmpType[1..*];

     }

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).

 

Pointer types

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:

Person: record { name:string; age:integer; };

we can declare a reference variable to the declared Person object as:

refperson: ref Person;

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):

(Emp where lName = “Doe”).worksIn.Dept.boss.Emp.lName

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).

7.1.3 Variable declaration examples

Declaration of an integer variable named x with the default cardinality:

x:integer;

Declaration of a complex variable named emp with three fields (string name, integer salary and optional pointer object worksIn pointing to a Firm object).

emp: record {

     name:string;

     salary:integer;

     worksIn: ref Firm[0..1];

 }

 

7.2 Object Creation

7.2.1 Operator create

Objects are created by the create operator. The system automatically checks if an object creation conforms to the declared type and cardinality[2]. The syntax is the following:

create [where] name(query);

Semantics

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).

 

7.2.2 Simple object creation

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.

Examples

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 amount(2500);

Create two persistent simple objects of type date named possibleMeetingDate.

create permanent possibleMeetingDate(2007-06-04 union 2007-09-12);

Create (possibly many) local simple string objects named fullName that value is a result of more complex query:

create local fullName(

   (Emp where worksIn.Dept.dName = “adv”).(fName + “ “ + lName));

Notes that local place indicator is available only inside the procedure/method body.

 

7.2.3 Pointer object creation

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.

Examples

Create (possibly many) persistent reference objects named highPayed that store references to high payed employees

create permanent highPayed(

   ref (Emp where sal > 3000)

 );

Create (possibly many) reference objects named johnWorkPlace that store identifiers of all the departments employing employees with the first name ‘John’.

 create johnWorkPlace(

    unique ref (Emp where fName = “John”).worksIn.Dept

 );

Or equivalently:

create johnWorkPlace(

   unique (Emp where fName = “John”).worksIn

 );

It is assumed that worksIn is a reference object. The persistency status depends on the context.

 

7.2.4 Complex object creation

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.

Examples

Create a single persistent complex object named Emp.

create permanent Emp(

    “Tom” as fName,

    “Jones” as lName,

    2500 as sal,

    ref (Dept where dName = “adv”) as worksIn,

    (

       ref (Dept where dName = “pr”) union

       ref (Dept where dName = “retail”)

    )groupas prevJobPlace

);

Create (possibly many) temporal complex objects named Car being copies of an existing one:

create temporal Car(

   Car where (prodYear > 2005 and manufacturer = “Fiat” )

);

 

7.2.5 Default object creation

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:

x:integer;

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.

 

7.3 Assignment

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.

Semantics

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.

7.3.1 Assignment to simple 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).

7.3.2 Assignment to a complex object

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:

(Emp where lName = “Jones”) := (

   “Tom” as fName,

   “Jones” as lName,

   2500 as sal,

   ref (Dept where dName = “adv”) as worksIn,

   (

      ref (Dept where dName = “pr”) union

       ref (Dept where dName = “retail”)

   ) groupas prevJobPlace

);

 

7.3.3 Assignment to a pointer

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.

(Emp where lName = “Doe”).worksIn :=

                           ref (Dept where dName = “pr”);

 

7.4 Insertion

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.

Semantics

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:

(Emp where lName=“Doe”):<

          create prevJobPlace( ref(Dept where dName=“pr”));

 

7.5 Create and Insert

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):

(Emp where lName=“Doe”):<<

                 prevJobPlace( ref(Dept where dName=“pr”));

 

7.6 Deletion

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:

delete query;

Semantics

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.

Examples

Delete the Marketing department.

delete Dept where dName = ”Marketing”;

Delete the location London from the Marketing department.

delete (Dept where dName = ”Marketing”).(loc as x where x =London”).x;

 

7.7 Program Control Statements

ODRA SBQL implements typical program control flow instructions. In most cases the syntax is similar to Java.

 

7.7.1 Conditional operator

Syntax

ifstatement ::= if query then statement else statement1

ifstatement ::= if query then statement

Semantics

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”,

 

if count(Emp where (hiredate > 2005-12-31 and hiredate < 2007-01-01)) >

 count(Emp where (hiredate > 2004-12-31 and hiredate < 2006-01-01)) + 100 )

then {

     report :<< note(“employment increase achieved”);

}

else {

      report :<< note(“employment increase not achieved”);

}

 

7.7.2 While, do-while loops statements

Syntax

whilestatement ::= while query do statement

dowhilestatement ::= do statement while(query)

Semantics

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.

Examples

i:integer;

i := 50;

while i > 50 do {

     i := i – 10;

}

The final result: i = 50 (no loop is performed)

i:integer;

i := 50;

do{i := i – 10;}while(i > 50);

The final result: i = 40 (one loop is performed).

 

7.7.3 For loop

Syntax

forstatement ::= for(initstmnt; cquery; incrstmnt) do statement

Semantics

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.

Examples

for(x:=0; x <= 20; x:= x+1) do {

 y := 1000 * x;

 print(y, count(Emp where sal >= y and sal < y+1000));

}

 

 

7.8 For Each 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.

Syntax

foreachstatement ::= foreach query do statement

Semantics

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.

Examples

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”:

foreach (avg(Emp.sal) as a.(Emp where sal < a and worksIn.Dept.dName = “Marketing”)) do

     sal := sal + 100;

With the “iteration variable”[3] over Emp objects:

foreach (avg(Emp.sal) as a.

  (Emp where sal < a and worksIn.Dept.dName = “Marketing”) as e) do

     e.sal := e.sal + 100;

 

7.9. Exceptions

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:

throw expression;

For example:

1 divide(arg1:integer; arg2:integer):real {

2   if(arg2 = 0){

3      e:Exception; //definition of an exception object

4      e.setMessage(“division by zero”);

5      throw e;

6   }

7   return arg1 / arg2;

8 }

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:

class Exception {

  instance : {message:string;}

  getMessage():string {return message; }

  setMessage(msg:string) {message := msg; }

}

 

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.

try {

    //code

}

catch and finally blocks

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).

try {

    //code

}

catch(name:ExceptionType1){

}

catch(name:ExceptionType2){

}

catch(name:ExceptionTypeN){

}

finally {

}

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.

Example:

performeCalculation(arg1:integer; arg2:integer; const:integer):real{

  result:real;

  try {

     result := divide(arg1; arg2);

  }

  catch(exc:Exception){

    result := 0;

  }

  finally {

     result := result + const;

  }

}

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.

 

7.10. Comments

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

 

 



[1] The type conformity based on type names (like e.g. in Pascal) is currently unsupported, but considered in next releases.

[2] Currently ODRA makes it possibile to switch off type checking, however, this is not recommended.

[3] 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.