|
|
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 |
||
7. SBQL Imperative StatementsSBQL 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:
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. 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 delete (Dept where dName = ”Marketing”).(loc
as x where 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. ExceptionsThe 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.