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


9. SBQL Classes, Methods and Bidirectional Pointers

The ODRA object model deals with classes and static inheritance[1]. Classes are pieces of a source code, as usually in object oriented programming languages. After compilation classes in ODRA are database objects similar to regular objects described before. Compiled classes are stored in an object store on usual principles. Operationally, however, classes behave differently than objects. The difference concerns the assumption that classes are created in a special manner and are used to introduce and store invariants of objects - class instances. In the current implementation classes are not directly accessible through a query language. The use of classes is indirect, through the use of objects being class instances.

ODRA classes follow static inheritance known from UML and many programming languages such as C++ and Java. The ODRA object model allows for multiple inheritance, i.e. a class can inherit from more than one classes. No special means for resolving name conflicts concerning properties inherited from different classes is provided (just like e.g. in UML and CORBA). We have concluded that name conflicts can be easily avoided by some naming convention, hence the problem is not sufficiently significant to be treated by special syntactic, semantic and pragmatic options. In the further ODRA releases we plan to implement dynamic object roles and dynamic inheritance that are perhaps the only radical solution for all the problems with inheritance.


9.1 Class Invariants

Invariants introduced by the class belong to the following kinds:

  • Type – the data structure describing a class instance
  • Methods – the behavior of class instances
  • Name – optional name of class instances

Below we describe them in detail.

Type. A class instance type is described by structural (complex) declaration, with optional set of fields declaration in curly brackets, preceded with the keyword instance and the optional instances_name:


instance [instance_name] : {



Methods. A method is an ODRA procedure stored inside a class. The current implementation of ODRA introduces only instance methods (called in the context of class instance). Class methods are not supported.

Inside a method body the current instance against which the metod is accessible with the keyword self.


getName():integer {




Instance name. The class can optionally introduce an invariant name for its instances. Such a name is optional. However, it the name is not present, a class instance cannot be the subject of the substitutability principle (decribed later in this chapter).



9.2 Syntax of Class Declaration

class name [extends superclass_names_list] {

instance [instance_name] : {







Sample class declarations

class PersonClass {

instance Person : {

   f_name : string;

   s_name : string;

   age   : integer;

   address : record {

      city : string;

      street : string;

      number : integer;




   return f_name + “ “ + l_name;





class EmployeeClass extends PersonClass {

instance Employee : {

   salary : integer;

   works_in : ref Department;

   prev_job : record {

      comp_name : string;

      from : integer;

      till : integer;



changeSalary(amount : integer){

 salary := amount;


moveEmployee(newDept : ref Department){





class StudentClass extends PersonClass {

      instance Student : {





        scholarship : = amount;


     setSholarship(newScholar:integer) {

   scholarship := newScholar;




class EmpStudentClass extends EmpClass, StudentClass {

      instance EmpStudent : {    }


     getTotalIncomes():integer {

   return self.(salary + scholarship);





9.3 Declaration of Class Instances

Although a class may determine an instance name, by itself it does not declare the default collection (an extent) of class instances. Declaration of of class instances is to be done separately. The syntax for a declaration is the same as for ordinary variables. The only difference is that a class name is to be used instead of type. For example, declarations presented below introduce four variables that identify collections of class instances:

Person: PersonClass[0..*];

Emp: EmpClass[1..*];

Student: StudentClass[0..*];

EmpStudent:   EmpStudentClass[0..*];


9.4 Substitutability

If a given class defines an instance name then instances of the class can be a subject of the substitutability principle. It allows for passing instances of a sub-class everywhere a super-class instance is expected. Because SBQL addresses data structures rather that data models, the substitutability is available on the level of object collections and their names rather than class names. For sample classes and data introduced in the previous sections it means that instances of the most specific class named EmpStudent are also available through the instance names defined in its super-classes (Student, Emp and Person). Thus the simple query


returns all the persons, also those that are instances of the PersonClass sub-classes. We didn’t find a consistent definition of the substitutability when names of class instances are not determined by their classes. The main problem with this case concerns typing and binding rules in connection with collections and the open-close principle.


9.5 SBQL Operators Specific to the Object Model with Classes

9.5.1 Operator instanceof

query ::= query1 instanceof class_name

The query1 returns a single reference to a class instance. The instanceof operator tests if the referenced object is an direct or indirect instance of the class identified with name class_name. Returns boolean TRUE if the test succeeds, FALSE otherwise.

The type checker rises an error if it discovers that the reference cannot be an instance of any class or if the class_name is not the name of any class.



(Emp where f_name = “Poe”) instanceof EmpClass // returns TRUE

(Emp where f_name = “Poe”) instanceof PersonClass // returns TRUE

(Emp where f_name = “Poe”) instanceof StudentClass //returns FALSE

(Emp where f_name = “Poe”) instanceof EmpStudentClass // the result depends

// on the database state, because EmpStudentClass instances

// are also accessible through name Emp.


9.5.2 Operator cast to a class

query ::= (class_name)query1


query1 returns a bag of references to class instances. The result of the class cast is a sub bag of references that contains only those instances references from the source bag that belongs to a class we cast to (identified with name class_name). If none such reference exists, the result is the empty bag.

We can distinguish three types of class cast:

  • Downcast – cast to a sub-class
  • Upcast – cast to a super-class
  • Crosscast – cast to a class that is neither sub- nor super-class.


(StudentClass) (Person where age > 23);

The result of this downcast is a bag of references to person objects that value of age sub-objects is greater that 23 and are instances of StudentClass (i.e. that are students older that 23). This kind of cast is equivalent to the selection:

Student where age > 23;

The result of the query presented below (including down- and up-cast) is a bag of references to objects having the value of age greater that 23 and are instances of both StudentClass and EmpClass. First, the query selects all persons that are older that 23, then the persons that are students. In the next step the result is casted up to the PersonClass. Final ly, the downcast selects those students that are also employees.

((EmpClass)((PersonClass)((StudentClass) (Person where age > 23))))

The same result can be obtained with use of crosscast that allows us to omit the upcast to the PersonClass.

((EmpClass) ((StudentClass) (Person where age > 23)))


9.6 Bidirectional Pointers

The goal of bidirectional pointers is to support automatic update of “twin” pointer objects that are instances of a modeling construct commonly referred to as relationship (in the Entity-Relationship model and in the ODMG standard) or association (in UML). Bidirectional pointers support only binary relationships (associations) with no attributes (or association classes). Before the definition of their semantics we outline the problem that was the motivation for the construct.

9.6.1 The problem of binary associations

If the object model of an application introduces a binary association, the update of one of its side implies an appropriate update of its opposite side. Consider the following example (from “Your First Tiny Object Base”; for the full example see section 2.3).



class EmpClass extends PersonClass {

    instance Emp : {


     worksIn: ref Dept;


 //the rest of the definition



class DeptClass {

   instance Dept : {

      dNbr: integer;

      dName: string;

      loc: string [1..*];

      employs: ref Emp [0..*];




//declaration of class instances collections


Emp: EmpClass [0..*];

Dept: DeptClass [0..*];

The classes define a binary association implemented by two kinds of pointer objects: worksIn in EmpClass and employs in DeptClass. The definition implicitly assumes that for a worksIn pointer there is a Dept object having a pointer sub-object employs that points to the Emp object having this worksIn pointer. Without automatic support this implicit constraint must be managed by application programmers. Such management implies the following requirements:

a. Creation

If a new Emp object is created the programmer is forced to create a “reverse” pointer in the appropriate Dept object, as shown below:

//create Emp object and insert employs pointer into Dept //object


(Dept where dName = “Ads”) :<<


 create permanent Emp(

                   //… ,

               (Dept where dName = “Ads”) as worksIn))        );

(Note: create permanent returns a reference to the newly created object.)

b. Updating

If an employee has to be moved to another department, the worksIn sub-object of the corresponding Emp object must be updated. Together, the associated employs object has to be moved to the new Dept object. The example below illustrates the situation:

//move employee to from Ads to Toys department

//first update worksIn pointer

(Emp where lName=“Doe”).worksIn := (Dept where dName=“Toys”);

//then move corresponding employs pointer

(Dept where dName = “Toys”) :<

   (Dept where dName = “Ads”).employs where Emp.lName = “Doe”;

c. Deletion

If an Emp object is to be deleted, the associated employs pointer must be removed, to avoid dangling pointers. In ODRA this problem does not exist because an object store automatically deletes all the pointer objects while the object pointed by them is being deleted. The problem that is not solved is connected with the direct deletion of an employs pointer. Semantically this operation should be associated with the deletion of associated worksIn pointer. Because the declared cardinality of the employs pointer variable is [0..*] the type control system permits for the deletion of the employs objects. The information about the associated worksIn object is not accessible in this context to the type-checker and the mechanism is unable to discover that the cardinality of worksIn ([1..1]) does not allow for such deletion.

9.6.2 The solution – bidirectional pointers

A bidirectional pointer object is similar to an ordinary pointer object (with some exceptions). There is no distinction in the SBQL syntax and semantics concerning navigation through ordinary and bidirectional pointers. The syntax for update operation is also the same but there are significant changes in the update semantics. The difference is based on the assumption that bidirectional pointer always has an associated “twin” pointer (that also must be bidirectional). When a bidirectional pointer is created, an associated bidirectional pointer is created too. If a bidirectional pointer is updated, then the appropriate action is automatically taken on its counterpart.

Bidirectional pointer declaration

Bidirectional pointers can be declared only within classes. A declaration is similar to a declaration of an ordinary pointer object with addition of the keyword reverse and the name of an associated pointer. For instance, a declaration of a worksIn pointer in the EmpClass as the bidirectional pointer can be as follows:

class EmpClass extends PersonClass {

   instance Emp : {


      worksIn: ref Dept reverse employs;


   //the rest of the definition


The above declaration requires the existence of employs pointer in the DeptClass that also has to be declared as bidirectional:

class DeptClass {

   instance Dept : {

      dNbr: integer;

      dName: string;

      loc: string [1..*];

      employs: ref Emp [0..*] reverse worksIn;



The combination of those two declaration allows to simplify the process of creating and updating bidirectional pointers.

a. Creating a bidirectional pointer

//create Emp object (insertion of an employs pointer into a Dept object is automatic)


 create permanent Emp(

                   //… ,

 (Dept where dName = “Ads”) as worksIn));

b. Updating a bidirectional pointer

//move an employee from Ads to Toys department through updating worksIn

(Emp where lName = “Doe”).worksIn := (Dept where dName = “Toys”);

//the corresponding employs pointer is moved automatically

e. Deleting a bidirectional pointer

In our example the deletion of worksIn pointer is not allowed because of it is declared as required (cardinality [1..1]). Thus if one tries to delete associated employs pointer the compiler raises a type error.



Last modified: June 20, 2008



[1] Interfaces, i.e. specifications of public properties of classes, are planned in the next ODRA release.