|
|
Developed
at the Polish-Japanese Institute of Information
Technology © 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 ViewsSBQL 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:
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:
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 |