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

 

10. SBQL Updatable Views

SBQL database updatable views are the main programmer’s facility for implementing virtual repositories. They present the most distinguishable part of the VRMS ODRA. Below we present a motivation for them, technical assumptions and detailed programmer’s specification. This description does not include implementation mechanism that we have used for views.

A database view is a virtual image of the data stored in a database. The most important property of views is transparency, which means that the user formulating a query needs not to distinguish between stored and virtual data. Transparency is relatively easy to achieve for read-only views (no updates). In such a case a view is nothing more than a function (returning a collection) that is known from a lot of programming languages, including SBQL.

A really challenging problem concerns the transparency of view updates. Updates of virtual objects have to be automatically mapped as updates of stored objects with no anomalies and no warping a user intention. This problem is known for about 35 years and considered very challenging. It has some partial solutions for relational databases (see e.g. the Oracle RDBMS), but concerning object–oriented databases the problem was till now not well understood. The SBQL updatable views present the first in the IT history universal, consistent and implemented solution of the view updating problem for object databases (actually, for any kind of databases). The method reminds the instead_of trigger views known from relational database system, but it is much more general concerning the data model and the algorithmic power of a view definition language. Moreover our method is much more promising concerning performance, as (in contrast to instead_of trigger views) it can avoid materialization of view results. Our solution for updatable views is supported by sophisticated query optimization methods, which are not applicable to other proposals concerning updatable views.

 

10.1 General Idea of SBQL Updatable Views

Classical database views, as known from SQL, are able to map stored data into virtual data. Such views can be used only for retrieval. The idea and implementation of them are rather obvious, with no conceptual or implementation challenges. In contrast, if virtual data are to be updatable, the view must somehow map updates of virtual data into updates of stored data. This is very challenging issue, which during last 35 years has raised a lot of interest (several hundreds of papers). Unfortunately, all this effort has very little practical impact.

SBQL updatable views present a revolutionary change concerning this issue. The idea of SBQL updateable object views relies in augmenting the definition of a view with the information on users’ intents with respect to updating operations. An SBQL updatable view definition is subdivided into two parts. The first part is the functional procedure, which maps stored objects into virtual objects (similarly to SQL). The second part contains redefinitions of generic operations on virtual objects. These procedures express the users’ intents with respect to update, delete, insert and retrieve operations performed on virtual objects. A view definition usually contains definitions of sub-views, which are defined on the same rule, according to the relativity principle. Because a view definition is a regular complex object, it may also contain other elements, such as procedures, functions, state objects, etc.

A view definition deals with two names. The first one is a managerial name that can be used to perform administration operations on the view definition, for instance, delete it, insert an object into it, etc. The second name is the name of virtual objects that are delivered by the view. The managerial name is optional. If it is not specified it is assumed by default that the managerial name will be the name of virtual objects suffixed with the string “Def”.

The general syntax for a view definition is following:

view_def ::= view [ manag_name ] { view_body }

where manag_name is the managerial view name.

 

10.2 Seeds of Virtual Objects

In contrast to all existing approaches to views, SBQL views do to return complete virtual objects as the result of view invocation. This decision is motivated both by the new concept of views and by performance. Invocation of an SBQL view returns only seeds of its virtual ojects. A seed is a small piece of a virtual object that uniquely identifies it. The rest of an virtual object is delivered according to the need of an application that uses it. For instance, if a virtual object has a virtual attribute address, but an application does not use it, then address is not delivered. Seeds are also the conceptual basis for updating virtual objects: they parameterize updating operations that are specified by the view designer.

The first part of a view definitition is a declaration of a virtual object. The declaration is similar to a variable declaration preceded with keyword virtual.

virtual_variable_decl ::= virtual name:type[cardinality]

where:

  • name is a name of virtual objects used in queries.
  • type is a type of virtual objects. The type will be (implicitly) used for the view operators and sub-views type check (as described later in the chapter).
  • cardinality is a cardinality of virtual objects.

The second part of a view definition body has the form of a functional procedure named seed. The name of the virtual objects procedure is the name of virtual objects that the view returns. The seed procedure returns bag of seeds. Seeds are then (implicitly) passed as parameters of procedures that overload operations on virtual objects (see: operators on virtual objects). Usually, seeds have to be named (i.e. they are binders), to identify them in the body of procedures. This is not obligatory if another identification method is possible. This name is then used in procedures that overload operators on virtual objects and within sub-views definitions.

The general syntax for the seed procedure definition is the following:

seed_def ::= seed:type[cardinality] { statements }

where:

  • type is the return type of a seed procedure.
  • cardinality is a cardinality of a seed return value. This value has to be the same as the cardinality of a virtual objects.

 

Sample View

Lets assume the following declaration of EmpType and DeptType types and Emp and Dept objects collections:

 type EmpType is record {

     name: string;

     deptName: string;

     salary: integer;

     opinion: string [0..1];

 }    

 type DeptType is record {

  name: string;

  location: string;

  }

 Emp: EmpType [0..*];

 Dept: DeptType [0..*];

The example below defines the view returning only those employees that earn more than 2000. The name of virtual objects is RichEmp and the managerial view name is RichEmpDef.

view RichEmpDef {

   virtual RichEmp : record {

      name: string;

 salary: integer;            

 worksIn: ref Dept;

   }[0..*];  

   seed: record {e: ref Emp;}[0..*] {

      return (Emp where salary > 2000) as e;

   }

//the rest of the view definition

}

First RichEmpDef view declares the virtual variable named RichEmp (our virtual objects). As we can see virtual objects are structurally different from the Emp objects. RichEmp contains a name and a salary amount that are similar to those in Emp object.  Instead of department name (deptName attribute) virtual object defines a (virtual) pointer to Dept object. The opinion attribute is not visible through virtual object.

The second part is a definition of virtual object seed. The seed procedure returns seeds of the declared type. In this case it returns named values  (binders) that are represented as structures with one element named e. A binder value is an id of the Emp object. The cardinality is same as the cardinality of virtual variable.

From the programmer point of view (in his/her imagination) the presence of this view definition can be perceived as the database contains virtual objects named RichEmp. A simple query:

RichEmp

returns identifiers of virtual objects (so-called virtual identifiers), having seeds as main components. Currently, however, no operation on them is possible, because they have to be expliciltly defined in the further part of the definition.

 

10.3 Operators on Virtual Objects

The operations that can be performed on virtual objects are defined in the second part of a view definition. They allow the programmer to create the behaviour of virtual objects in the context of the following generic operations:

·      retrieve: (dereference) returns the value of a given virtual object;

·      update: modifies the value of the given virtual object;

·      create/insert : create a new virtual object, insert an object into a virtual object;

·      delete: removes the given virtual object;

·      navigate: navigates according to a virtual pointer.

If there is no definition of a particular operator inside a view definition it is assumed that the operation is forbidden for the virtual objects generated by the view.

 

10.3.1 Defining operators

The definitions of the operators have procedural semantics. Each operator has a predefined name.

·      on_retrieve – retrieving value of virtual object

·      on_update – update virtual object

·      on_delete – delete virtual object

·      on_new – create virtual object

·      on_navigate – navigates according to a virtual pointer

The execution of given operator is implicit. If the system detects that the parameter of the operation is a virtual object, instead of taking system default action the appropriate view operator procedure is invoked. A seed describing a virtual object is implicitly passed as a default parameter to the procedure through the environment stack. After the execution the control is passed back to the user program.

The above description is similar for all operators except the operator for creating virtual objects (on_new). This operator is not executed in the context of a virtual object. The system passes the control to the on_new procedure if in the environment where the virtual objects with the given name are defined a new object with the same name appears (e.g. it was created by the create operator or inserted by the insert operator). The value of the object is passed as an argument to the on_new procedure. After the on_new procedure ends, the object is automatically deleted (i.e. a material object is substituted by a virtual object).

The procedure on_new performs (determined by the view definer) actions on stored objects that result in the effect that the new virtual object appears in the database environment. To this end, new stored objects can be created in the database, but the questions which objects and how they are created depend on the current need that is recognized by the view definer.

 

on_retrieve - retrieve operator

 

To describe the result of the dereference operation on the virtual object the on_retrieve procedure definition must be introduced into a view definition. The syntax is as follows:

onretrieve_oper ::= on_retrieve {statements}

The operator does not define the return type and the cardinality of a return value because it is implicitly assumed that the type is same as the type of a virtual object defined inside the view.

 

on_update – update operator

 

To describe the behaviour of updating a virtual object, the on_update procedure definition must be introduced into a view definition. The syntax is as follows:

onupdate_oper ::= on_update [parameter_name] {statements}

where parameter_name is optional name for the on_update argument (update operator right hand operand) that can be used inside the operator procedure body. If the name is omitted the default name: ’value’ is assumed. The operator does not define the type of a parameter because it is implicitly assumed that the type is same as the type of a virtual object defined inside the view. The cardinality of a parmeter is [1..1] (because update operator in SBQL is not macroscopic).

When an assignment operation is performed on a virtual object, the right side of the assignment is passed as the parameter of the on_update procedure (as usual, in the strict-call-by value mode). Passing the parameter is implicit, the programmer processing a virtual object need not to deal with that.

 

on_delete – delete operator

 

To describe the behaviour of deleting virtual objects the on_delete procedure definition must be introduced into a view definition. The syntax is as follows:

ondelete_oper ::= on_delete {statements}

The on_delete procedure has only a seed of a virtual object as an implicit parameter.

 

on_new – create operator

 

To describe the behaviour of creating virtual objects the on_new procedure definition must be introduced into a view definition. The syntax is as follows:

onnew_oper ::= on_new [parameter_name] {statements}

where parameter_name is optional name for the on_new argument (create operator operand) that can be used used inside the operator procedure body. If the name is omitted the default name: ’value’ is assumed. The operator does not define the type of a parameter because it is implicitly assumed that the type is same as the type of a virtual object defined inside the view. As for the on_update operator, this parameter is passed implicitly and the programmer that uses the view need not to deal with that.

 

Examples (…continued)

Now we can extend the RichEmpDef view with the operators. Assume that we want to allow to perform all the operation on the virtual object except the deletion that will be forbidden:

First we define the dereference operator.

view RichEmpDef {

   virtual RichEmp : record {

      name: string;

      salary: integer;            

      worksIn: ref Dept;

   }[0..*];  

   seed: record {e: ref Emp;}[0..*] {

      return (Emp where salary > 2000) as e;

   }

   on_retrieve {

      return e.(name as name,

                salary as salary,

                ref (Dept where name = deptName)as worksIn);

   }

        //…

}

The on_retrieve procedure used the implicit parameter e (the seed of a virtual object) to construct the required structure of dereferenced virtual object (accordint to its typw defined insode the view). Because binding to name of the seed returns reference to Emp object (see the seed procedure type) the programmer has full control on the transformation of regular object (Emp) to virtual object (RichEmp) (note that explicit deref operator can be omitted here because the system can add it implicitly basing on the return type). In more complex examples the operator can perform additional tasks (as it is a regular procedure).

Next we extend the virtual objects with update operator:

view RichEmpDef {

 virtual RichEmp : record {

      name: string;

      salary: integer;            

      worksIn: ref Dept;

   }[0..*];  

   seed: record {e: ref Emp;}[0..*] {

      return (Emp where salary > 2000) as e;

   }

   on_retrieve {

      return e.(name as name,

                salary as salary,

                ref (Dept where name = deptName)as worksIn);

   }

   on_update {

      e.name := value.name;

      e.deptName := value.worksIn.name;

      if e.salary < value.salary then {    

                 e.salary := value.salary;

      }

   }

//…

}

As in described above for on_retrieve the on_update operator has also implicit parameter that is the virtual object seed. The sample update operator does not define the name of a parameter (but it is not a rule). It means that the default ‘value’ name has to be used inside the operator procedure body. Note that the type of a parameter is (implicilty) the type of the virtual object (RichEmp) but we update the regular object (Emp). In other words, on_update operator maps virtual data onto regular (in contrary to on_retrieve operator that maps regular data onto the virtual one).

The semantics of sample update operation assumes that the name of a virtual RichEmp object is directly maped to the name of a regular Emp object. Because the virtual object defines a worksIn pointer object, the value of name subobject of the target Dept object (the pointer points to) is mapped onto the deptName value. The definition assumes that the salary field value in Emp is updated only if is less that the salary value inside virtual object.

Finally we define on_new operator to allow creating new RichEmp virtual objects. But assume the semantic that he object will be created only when the attribute salary will be greater than 2000.

view RichEmpDef {

   virtual RichEmp : record {

      name: string;

      salary: integer;            

      worksIn: ref Dept;

   }[0..*];  

   seed: record {e: ref Emp;}[0..*] {

      return (Emp where salary > 2000) as e;

   }

   on_retrieve {

      return e.(name as name,

                salary as salary,

                ref (Dept where name = deptName)as worksIn);

   }

   on_update {

      e.name := value.name;

      e.deptName := value.worksIn.name;

      if e.salary < value.salary then {    

                 e.salary := value.salary;

      }

   }

   on_new newEmp {

      if newEmp.salary > 2000 then

      create permanent Emp(

        newEmp.name as name,

        newEmp.salary as salary,

        newEmp.worksIn.name as deptName

      );

   }

//…

}

In this sample on_new definition the name of the parameter was given explicitly and can be used inside of the operator procedure body. As in case of on_update operator the on_new operator maps virtual data onto corresponding regular one.

 

10.4 Nested Views (sub-views)

Each view can contains sub-views. The number of view nesting levels is unlimited. Syntax and semantics of nested sub-view definitions is the same as for enclosing views. If a virtual object has attributes they can be defined by sub-views. In principle, there is no way to declare attributes and sub-attributes of virtual objects in another way, but this is only temporary decision that can be changed.

From the point of type control the type, name and cardinality of a nested vitual object have to conform to one of the field defined for the virtual object in eclosing view.

Sub-views define their own seeds and on_xxx operators procedures. Additionally, the procedures have access to seeds generated by enclosing views.

Example (…continued)

The RichEmp virtual objects defined in the previous sections currently does not have any attribute (sub-object), even though it seed is based ob the complex Emp object. To define virtual attributes we need to define sub-views. In our example we can decide which Emp attributes become a virtual attributes of RichEmp. For each attribute we can separately decide which operators will be available for it. Below a definition of an attribute name is shown:

view RichEmpDef {

   //…

   view nameDef {

      virtual name:string;

      seed : record {n:string;} {

         return e.name as n;

   }

   on_retrieve { return n; }

 

   //…

}

The view nameDef introduces a virtual attribute name of RichEmp virtual objects. The RichEmp virtual object in eclosing view declares name of tyle string and default cardinality [1..1] as one of its fields. The declaration of our nested virtual object  conforms to this specification. The seed procedure for the name virtual object access the eclosing virtual object seed and returns a binder n with string representing the RichEmp name. Notice that the virtual object definition has access to the seed defined in the RichEmpDef view.

The view definition contains only one operator – dereference. Other operations are forbidden for the name virtual attribute.

Similar approach has to be taken for the salary attribute (ofcourse the programmer can decide on the range of available operators).

 

10.5 Virtual Pointers

Up to now virtual objects can be perceived by the user as simple objects (defined by the view without nested sub-views) or complex objects (defined by the view with nested sub-views describing attributes). For completeness of the transparency we need also to define virtual entities that can be perceived as pointer objects.

A pointer object allows to navigate in an object graph. The unique property of SBQL is that the environment of a pointer object is represented by the binder named with the name of pointed object; thus, navigation through the pointer object requires typing the name of the target object. This property allows us to separate the reference to pointer itself and the reference to pointed object. For example if we assume that friend is a pointer sub-object of Person object the query:

(Person where name = “Kim”).friend

Returns the reference (bag of references) of pointer object named friend. Such reference can be the subject of imperative operations (e.g. updated, deleted). To return the references to objects pointed by the friend objects one must write:

 (Person where name = “Kim”).friend.Person

To define a virtual pointer with analogous semantics we need to introduce into the view definition a new operator.

 

Defining virtual pointer – on_navigate

A virtual object acts as a virtual pointer if its definition is augmented by the operator on_navigate:

onnavigate_oper ::= on_navigate {statements}

As usual, the operator is defined as a functional procedure. It must return a reference (or a virtual reference) of a “virtually” pointed object. As for the other operators its return type implicitly corresponds to the declared type of a virtual object. These two assumptions enforce that only those virtual objects that return refrence to the other objects can possess on_navigate operator and be perceived as (virtual) pointers.

The operator procedure is implicitly executed during the process of calculating a nested environment in the context of a non-algebraic operator (see: non-algebraic operators). The result reference of a on_navigate call is then available within the virtual object environment (the semantics is the same as for regular pointer objects).

 

Example (…continued)

At this stage we’ll introduce to the RichEmp virtual object the virtual pointer worksIn that will point at the department the given rich employee works in. The definition of the virtual pointer attribute requires addition of a suitable sub-view. To transform virtual object into virtual pointer we’ll define the on_navigate operator. We also assume that the virtual pointer is a subject of dereference and update operation and define on_retrieve and on_update operators.

view RichEmpDef {

   //…

 

   view worksInDef {

      virtual worksIn:ref Dept;

      seed :record{ dn:Emp.deptName; } {

         return e.deptName as dn;

      }

     

      on_navigate { return Dept where name = dn; }

 

      on_retrieve { return Dept where name = dn; }

 

      on_update { dn := value.name; }

   }

//…

}

 

The seed procedure returns reference to deptName attribute inside the Emp object. The on_navigate operator returns a reference to a Dept object that name is equal to the employee deptName attribute value. The result of on_navigate is a reference of a virtual pointer target object.

The sample code inside the on_retrieve operator procedure is the same as for the on_navigate. Both operators have to return the value of the type declared for the virtual object. But the code can performe some additional tasks different for navigating and updating[1].

The update semantics is straightforward. An argument is a reference of a Dept object. To change an employee’s department we simply update the deptName attribute value with the value of the argument department name object.

 

10.6 Local Objects Within Views

A view definition can include local objects. This is necessary for stateful views, which have a lot of applications, for example, to store security data or the state of network connections. The situation can be compared to instance and class invariants known from popular object-oriented programming languages. The sub-views define non-static (virtual objects) attributes and the local view objects are like static (class) attributes.

The definition of a view local objects is similar to declaring global or local variables (see: variable declaration) but the declaration is placed inside the view definition.

 

Example (…continued)

We extend the RichEmpDef view with a state. Assume that the employee’s richness level is parameterized by the database administrator. RichEmp virtual objects procedure will use this parameter to select those employees that salary is greater than a parameter value. To do this we must introduce the state to the view and modify RichEmp virtual objects procedure (and all operators that depend on the earning threshold).

 

view RichEmpDef {

   virtual RichEmp : record {

      name:string;

      salary:integer;            

      worksIn: ref Dept;

   }[0..*];  

   seed: record {e: ref Emp;}[0..*] {

      return (Emp where salary > threshold) as e;

   }

   on_retrieve {

      return e.(

         name as name,

         salary as salary,

         ref (Dept where name = deptName)as worksIn

      );

   }

 

   on_update {

      e.name := value.name;

      e.deptName := value.worksIn.name;

      if e.salary < value.salary then {     

         e.salary := value.salary;

      }

   }

 

   on_new newEmp {

      if newEmp.salary > threshold then

         create permanent Emp(

            newEmp.name as name,

            newEmp.salary as salary,

            newEmp.worksIn.name as deptName

      );

   }

 

   //the rest of the definition

   //declaration of the view local object

   threshold: integer;

}

The threshold object is local to the view definition and accessible through the view managerial name. Now the entitled user can change the threshold level with use of the following query:

RichEmpDef.threshold := 2500;

 

More complex statefull view example

Assume that a boss periodically performs inspection of his employees. A view named EmpInspectionDef delivers him the convenient interface to do this task. A virtual object EmpInspection defined by the view has four attributes (defined by a sub-views):

·         name and opinion that comes from an Emp object.

·         annotation and assessed that are local to the EmpInspection virtual object.

The boss can read the attributes name, opinion and annotation and is able to set the annotation value to any string. Setting and changing an annotation value does not have any impact on the data in the database because the value is stored locally inside the view definition. Each new employee will be automatically accessible through the EmpInspection view. The last attribute named assessed cannot be read. The boss is able only to set it value to boolean true. Setting this value means that the corresponding employee inspection process has been ended. The employee won’t appear it the view any more. If the annotation value for the employee was set before it was set as inspected the value of virtual attribute annotation will be concatenated with the value stored in the employee opinion attribute (if the attribute was present) or the new opinion attribute will be inserted into the employee with a value equals to the annotation value (if employee did not have the attribute).

view EmpInspectionDef {

 

   virtual EmpInspection : record {

      name:string;

      opinion:string[0..1];

      annotation:string[0..1];

      assessed:boolean;

   }[0..*];

 

   seed : record {e: Emp;  }[0..*] {

      return (Emp as e) where not

    (ref e in ref EmpInspectionDef.inspected.Emp);    

   }

 

   view nameDef { 

      virtual name:string;

      seed : record{n:string;} {

         return e.name as n;

      }

 on_retrieve { return n; }

   }

 

   view opinionDef { 

      virtual opinion : string[0..1];

  seed :record { eo:string; }[0..1] {

     return e.opinion as eo;

  }

  on_retrieve { return eo; }

   }

 

   view annotationDef {

      virtual annotation: string[0..1];

      seed :record {a:EmpInspectionDef.note;}[0..1] {

         return (EmpInspectionDef.note where

       ref e = ref(concern.Emp)) groupas a;

 }

   }

 

   on_retrieve {

      if exists a then

         return a.text;

      else

         return "no annotation";

   }

 

   on_update newText {

      if exists a then {

         a.text := newText;

      }

      else {

         EmpInspectionDef :<<

         note(ref e as concerns, newText as text);

      }

   }

 

   view assessedDef {

      virtual  assessed:boolean;

      seed:boolean {return false;}

      on_update done {

         if done then {

            EmpInspectionDef :<< inspected(ref e);

            if exists(EmpInspectionDef.note where

               (ref concerns.Emp = ref e))) then {

               if exists e.opinion then {

                  e.opinion := (e.opinion +

                     (EmpInspectionDef.note where

                     ((ref concerns.Emp) = (ref e))).text);

               }

               else {

                  e :<< opinion((EmpInspectionDef.note where

                         ((ref concerns.Emp) = (ref e))).text);

               }

            }

         }

      }

   }

   inspected: ref Emp[0..*];

   note: record {

      concerns:Emp;

      text:string;

   }[0..*];

}

 

10.7 Syntax of SBQL Views

view_def ::= view [ manag_name ] { view_body }

manag_name ::= name;

view_body ::= view_body_sections

view_body_sections :: = view_body_section

| view_body_section view_body_sections

view_body_sections ::= virtual_variable_decl

   | seed_def

   | view_operator_def

   | variable_declaration

virtual_variable_decl ::= virtual name:type[cardinality]

seed_def ::= seed:type[cardinality] { statements }

view_operator_def ::= onretrieve_oper | onupdate_oper | ondelete_oper

| onnew_oper | onnavigate_oper

onretrieve_oper ::= on_retrieve {statements}

onupdate_oper ::= on_update [parameter_name] {statements}

ondelete_oper ::= on_delete {statements}

onnew_oper ::= on_new [parameter_name] {statements}

onnavigate_oper ::= on_navigate {statements}

 

 

Last modified: June 20, 2008

 

 



[1] It is possible that in the future versions these two operators will be merged.