ODB for WARP User's Guide: Difference between revisions

From OS2World.Com Wiki
Jump to navigation Jump to search
No edit summary
(12 intermediate revisions by 2 users not shown)
Line 1: Line 1:
By [[Staffan Flodin]]
[[Image:odb.jpg|right]]
   
''By [[Staffan Flodin]]''
[[Image:odb.jpg|Nice Pic]]  


ODB for WARP  
ODB for WARP


An Object-Oriented Database V. 0.1
An Object-Oriented Database V. 0.1


== 1.0 Introduction ==
= 1.0   Introduction =
The purpose of this system is to provide system developers an easy to use database facility with powerful query capabilities. '''ODB''' (''ObjectDataBase'') consists of a graphical maintenance interface where databases may be defined and updated and a database engine.
 
 
The purpose of this system is to provide system developers an easy to use
database facility with powerful query capabilities. '''ODB'''
(''ObjectDataBase'') consists of a graphical maintenance interface where
databases may be defined and updated and a database engine.
<br>
The database engine is accessed by applicaions through a simple and consistent
''application programming interface'', API. '''ODB''' is a light-weight
single- user main-memory object-oriented database.


The database engine is accessed by applicaions through a simple and consistent ''application programming interface'', API. '''ODB''' is a light-weight single- user main-memory object-oriented database.


*'''ODB''' supports, subtyping, inheritance, complex objects and all objects have a unique identity hence '''ODB''' is object-oriented.
*It always keeps the data in main memory, thus it is primarily intended for applications with moderately sized data requirements. It uses secondary storage only for storing the database between sessions.
*It is light-weight because some of the traditional database facilities such as locking and logging does not exist in '''ODB'''. The reason is that it is intended to be used as a fast and easy to use storage and search module in any application where traditionally the application developer programmed the data storage, e.g. as sequential file scans etc.
*It differs from other C++ based object-oriented databases in that it is dynamic in nature. Hence your application is linked and compiled once and the database schema is created or changed dynamically without any recompilation of the application.


* '''ODB''' supports, subtyping, inheritance, complex objects and all objects have a unique identity hence '''ODB''' is object-oriented.
== 2.0 ODB system ==
* It always keeps the data in main memory, thus it is primarily intended for applications with moderately sized data requirements. It uses secondary storage only for storing the database between sessions.
The '''ODB'''-system consists of two parts: A graphical user interface for creation and maintenance of the database and a C++ API for accessing the database engine from another application.
* It is light-weight because some of the traditional database facilities such as locking and logging does not exist in '''ODB'''. The reason is that it is intended to be used as a fast and easy to use storage and search module in any application where traditionally the application developer programmed the data storage, e.g. as sequential file scans etc.
* It differs from other C++ based object-oriented databases in that it is dynamic in nature. Hence your application is linked and compiled once and the database schema is created or changed dynamically without any recompilation of the application.
= 2.0  ODB system =


[[Image:ODB_fig1.gif|System Overview]]
''Figure 1. System Overview''


The '''ODB'''-system consists of two parts: A graphical user interface for
The graphical user interface faciliates creation, update and deletion of types, properties and objects in an easy manner. The interface also supports loading, saving and creation of '''ODB'''-databases.
creation and maintenance of the database and a C++ API for accessing the
database engine from another application.
<br>  [[Image:ODB_fig1.gif|System Overview]] <br>
Figure 1. System Overview
<br>


The graphical user interface faciliates creation, update and deletion of types,
The C++ API gives any application access to a set of high-level easy to use interface methods to the database engine. The API gives the application access to the same functionality which is available in the user interface. In addition, the API also provides a powerful ''stream based query interface'' to the database.
properties and objects in an easy manner. The interface also supports loading,
saving and creation of '''ODB'''-databases.
<br>
The C++ API gives any application access to a set of high-level easy to use
interface methods to the database engine. The API gives the application access to the same functionality which is available in the user interface. In addition, the API also provides a powerful ''stream based query interface'' to the database.


=== 2.1 Using ODB for Warp ===
The on-line version of '''ODB''' contains all functionality of the system, i.e. it is not a special shareware version. If you use '''ODB''' and find it useful the cost is US$99. Send an e-mail to sflodin@ozemail.com.au for further details.


== 2.1  Using ODB for Warp ==
=== 2.2 Files ===
 
 
The on-line version of '''ODB''' contains all functionality of the system,
i.e. it is not a special shareware version. If you use '''ODB''' and find it
useful the cost is US$99. Send an e-mail to [[sflodin@ozemail.com.au]] for further
details.
 
 
== 2.2   Files ==
'''ODB''' consists of the following files:
'''ODB''' consists of the following files:
* pmstore.exeThe '''ODB''' creation and maintenance interface.
*pmstore.exe The '''ODB''' creation and maintenance interface.
* ODB.h '''ODB''' C++ header file.
*ODB.h '''ODB''' C++ header file.
* ODB.libThe '''ODB''' library.
*ODB.libThe '''ODB''' library.
* Compdb.odb Sample '''ODB''' database files.
*Compdb.odb Sample '''ODB''' database files.
* Demo.cpp Sample '''ODB''' application source code.
*Demo.cpp Sample '''ODB''' application source code.
 
== 2.3  System Requirements ==
 
 
The system is intended to run under OS/2 Warp Version 3 or higher. Any other
requirements does not exist.
 
 
= 3.0  System overview of ODB =


=== 2.3 System Requirements ===
The system is intended to run under OS/2 Warp Version 3 or higher. Any other requirements does not exist.


An '''ODB'''-database is a collection of ''types'' with ''properties'' and ''objects'' which are instances of types. '''ODB'''-types are instances of the system class ''type'' and '''ODB'''-objects are instances of the ''object'' system class [[]] <br>
== 3.0 System overview of ODB ==
An '''ODB'''-database is a collection of ''types'' with ''properties'' and ''objects'' which are instances of types. '''ODB'''-types are instances of the system class ''type'' and '''ODB'''-objects are instances of the ''object'' system class [[]]


The types are organized in a ''subtype-supertype'' hierarchy where subtypes ''inherit'' all properties defined or inherited to its supertype. In addition to the inherited properties new properties can be created.
The types are organized in a ''subtype-supertype'' hierarchy where subtypes ''inherit'' all properties defined or inherited to its supertype. In addition to the inherited properties new properties can be created.
A property is a name which ''denotes values of objects''. Properties can denote values of the following '''ODB'''-database types:
A property is a name which ''denotes values of objects''. Properties can denote values of the following '''ODB'''-database types:
* Integer
* Integer
* Real
* Real
Line 86: Line 54:
* Collection of object reference.
* Collection of object reference.


'''ODB'''-objects are instances of '''ODB'''-types where all properties
'''ODB'''-objects are instances of '''ODB'''-types where all properties defined or inherited to the type can be assigned values for the object.
defined or inherited to the type can be assigned values for the object.
 
<br>
Objects can be retreived by their property values. And the ''extent'' of a type, i.e. the objects that belongs to a particular type and all subtypes to that type, can be queried through a stream based query interface. To facilitate fast look-up of objects ''indexes'' can be built over some or all of the properties of a type.
Objects can be retreived by their property values. And the ''extent'' of a type, i.e. the objects that belongs to a particular type and all subtypes to that type, can be queried through a stream based query interface. To facilitate fast look-up of objects ''indexes'' can be built over some or all of the properties of a type.
<br>
 
The database can easily be updated or altered in the following ways:
The database can easily be updated or altered in the following ways:
* Type creation
* Type creation
Line 102: Line 69:
* Object modification.
* Object modification.


''Integrity constraints'' will be maintained by the system when types or objects are deleted or modified. All properties and indexes are inherited
''Integrity constraints'' will be maintained by the system when types or objects are deleted or modified. All properties and indexes are inherited automatically to any newly created subtype of a particular type.
automatically to any newly created subtype of a particular type.


= 4.0   Database operations =
== 4.0 Database operations ==
In this section the '''ODB'''-database engine methods in the API are described and exemplified as C++ examples. Throughout the examples a database object named ''My_DB'' is used to exemplify the '''ODB'''-API.
In this section the '''ODB'''-database engine methods in the API are described
 
and exemplified as C++ examples. Throughout the examples a database object
=== 4.1 Database creation ===
named ''My_DB'' is used to exemplify the '''ODB'''-API.
[[Image:ODB_fig3.gif|The ODB Database Object]]


The '''ODB'''-API contains a C++ class named ''database'' see Figure 3. The database object in your application is a
== 4.1  Database creation ==
particular important object. It is through the database object which all accesses to the '''ODB'''-database and all calls to the database engine go.
[[Image:ODB_fig3.gif|The ODB Database Object]] <br>  <br>


The '''ODB'''-API contains a C++ class named ''database'' see
The database object contains the ''database name,'' an '''ODB'''-type named ''Usertypes'', an object identifier (''OID'') generator and a ''string table''.
Figure 3. The database object in your application is a
particular important object. It is through the database object which all
accesses to the '''ODB'''-database and all calls to the database engine go.
The database object contains the ''database name,'' an '''ODB'''-type named
''Usertypes'', an object identifier (''OID'') generator and a ''string
table''.


The name of the database object is assigned to it at creation or when the database is loaded from file.
The name of the database object is assigned to it at creation or when the
The type ''Usertypes'' is the root of the user defined type tree. This type can not be given user defined properties and objects can not be created from it.
database is loaded from file.
Thus, if a type is created without a specified supertype the type named Usertypes will become its supertype by default.
The type ''Usertypes'' is the root of the user defined type tree. This type
can not be given user defined properties and objects can not be created from it.
Thus, if a type is created without a specified supertype the type named
Usertypes will become its supertype by default.


The ''string table'' is a character string repository. All strings used in the database are stored in this table. This facilitates fast look-up of string valued properties. There are som cases where the application must access the string table but in most cases '''ODB''' handles this for the application.
The ''string table'' is a character string repository. All strings used in
the database are stored in this table. This facilitates fast look-up of string
valued properties. There are som cases where the application must access the
string table but in most cases '''ODB''' handles this for the application.


The OID generator generates OIDs for all new objects or types in the database.
The OID generator generates OIDs for all new objects or types in the database.
The OID can not be changed by the application and the application must never
The OID can not be changed by the application and the application must never reference object by their OID's as the OIDs of the objects may change when the database is loaded again.
reference object by their OID's as the OIDs of the objects may change when the
database is loaded again.
 
If your application only needs one database this database is declared in your
application as:
 
 
<pre>
database My_DB("DBName");
 
</pre>
 
 
This statement declares the object ''My_DB'' to be an '''ODB'''-database
object with name ''DBname''. All applications using '''ODB''' must have a
database object. There is no limit set by '''ODB'''to the number of databases
a single application can use.
If several databases are required in the application these can be created
dynamically by using the C++ new-operator as:
 
 
<pre>
new database("OneMoreDB");
 
</pre>


If your application only needs one database this database is declared in your application as:
database My_DB("DBName");
This statement declares the object ''My_DB'' to be an '''ODB'''-database object with name ''DBname''. All applications using '''ODB''' must have a database object. There is no limit set by '''ODB'''to the number of databases a single application can use.


If several databases are required in the application these can be created dynamically by using the C++ new-operator as:
new database("OneMoreDB");
The database can be saved and restored from file as:
The database can be saved and restored from file as:
database My_DB;
My_DB.load_database("Db1.odb");
My.DB.save_database("Db1.odb");
In above statements the database object ''My_DB'' is first loaded and then saved with file name ''Db1.odb''.


<pre>
=== 4.2 Type creation ===
database My_DB;
An '''ODB'''-type serve as a template for object creation. Properties and indexes are defined on types. A type can be declared as a subtype to any other type previously defined. If no supertype is declared then the new type will become subtype to the type named ''Usertypes'' in the database object.
My_DB.load_database("Db1.odb");
'''ODB'''-types are instances of the system type named type, see Figure 2.
My.DB.save_database("Db1.odb");
 
</pre>
 
In above statements the database object ''My_DB'' is first loaded and then
saved with file name ''Db1.odb''.
 
== 4.2   Type creation ==
An '''ODB'''-type serve as a template for object creation. Properties and
indexes are defined on types. A type can be declared as a subtype to any other
type previously defined. If no supertype is declared then the new type will
become subtype to the type named ''Usertypes'' in the database object.
'''ODB'''-types are instances of the system type named type,
see Figure 2.


[[Image:ODB_FIG2.GIF|Figure 2]]
[[Image:ODB_FIG2.GIF|Figure 2]]
   
   
An '''ODB'''-type inherits all properties defined to its supertype. Any indexes
An '''ODB'''-type inherits all properties defined to its supertype. Any indexes defined to the supertype will also be defined to the new type. Types are created by the '''ODB'''-database object as:
defined to the supertype will also be defined to the new type. Types are
My_DB.create_type("Person");
created by the '''ODB'''-database object as:
My_DB.create_type("Employee","Person");
Here a type named ''Person'' is first created. A supertype has not been specified thus the type named Person will be a subtype of the type ''Usertypes'' in the database object ''My_DB''. In the next example a type named ''Employee'' is created as a subtype to the type named Person.


If several databases are active in some application there may be types eith identical names in the different databases. Since references to types go through the database object there is never any ambiguity when referencing types by their names.


<pre>
The return value from the ''create_type''-statement is a reference to the new type or the NULL-reference if the type creation failed. The following example declares a type reference that can be bound to the return value from the create_type statement:
My_DB.create_type("Person");
ODB_TYPE My_NewType;
My_DB.create_type("Employee","Person");
My_NewType=My_DB.create_type("Person");
 
The return value need to be assigned a reference. The type is inserted in the type hierarchy of the database and can be referenced by its name whenever required.
</pre>
 
Here a type named ''Person'' is first created. A supertype has not been
specified thus the type named Person will be a subtype of the type
''Usertypes'' in the database object ''My_DB''. In the next example a type
named ''Employee'' is created as a subtype to the type named Person.
If several databases are active in some application there may be types eith
identical names in the different databases. Since references to types go through
the database object there is never any ambiguity when referencing types by
their names.


=== 4.3 Property Creation ===
The return value from the ''create_type''-statement is a reference to the new
Properties are defined to types and for the objects of that type the properties can be given values. Properties are typed where the following types together with their identifiers are supported:
type or the NULL-reference if the type creation failed. The following example
declares a type reference that can be bound to the return value from the
create_type statement:
 
 
<pre>
ODB_TYPE My_NewType;
My_NewType=My_DB.create_type("Person");
 
</pre>
 
The return value need to be assigned a reference. The type is inserted in the
type hierarchy of the database and can be referenced by its name whenever
required.
 
== 4.3   Property Creation ==
Properties are defined to types and for the objects of that type the properties
can be given values. Properties are typed where the following types together
with their identifiers are supported:
 
 
{| border="1"
{| border="1"
|-
!Type  
!Type  
!Type tag
!Type tag
|-
|-
|Integer
|Integer
|_INT_
|_INT_
|-
|-
|Real
|Real
|_REAL_
|_REAL_
|-
|-
|Charstring
|Charstring
|_CHAR_
|_CHAR_
|-
|-
|Object reference
|Object reference
|_OBJECT_
|_OBJECT_
|-
|-
|Collection of Integer
|Collection of Integer
|_INT_COLLECTION_
|_INT_COLLECTION_
|-
|-
|Collection of Real
|Collection of Real
| _REAL_COLLECTION_
|_REAL_COLLECTION_
 
|-
|-
|Collection of Charstring  
|Collection of Charstring  
|_CHAR_COLLECTION_
|_CHAR_COLLECTION_
|-
|-
|Collection of object reference.
|Collection of object reference.
| _OBJECT_COLLECTION_
| _OBJECT_COLLECTION_
 
|}
|}  
To create an integer valued property named salary to the type named employee in the database ''My_DB'' we write:
To create an integer valued property named salary to the type named employee in
My_DB.add_property("Employee","Salary",_INT_);
the database ''My_DB'' we write:
 
 
<pre>
My_DB.add_property("Employee","Salary",_INT_);
 
</pre>
 
Analogously, an object valued property named Boss is created as:
Analogously, an object valued property named Boss is created as:
My_DB.add_property("Employee","Boss",_OBJECT_);
The return value is either less than or greater than zero if the operation failed or succeeded, respectively.


<pre>
The ''add_property''-statement can be executed any time and all subtypes to the target type that may exist will also be given the property. All instances to the types which has been given a new property will also be given the property.
My_DB.add_property("Employee","Boss",_OBJECT_);
The value however will be undefined and it is up to the application that the value is not referenced prior to being assigned a value.


</pre>
=== 4.4 Object creation ===
Objects are created as instances to a particular type. When objects are created these are given all the properties defined to the type. The values of the properties are undefined and must be assigned values prior to being referenced.


The return value is either less than or greater than zero if the operation
To create an instances of the type named Person in the database ''My_DB'' we write:
failed or succeeded, respectively.
My_DB.create_instance("Person");
The return value is a reference to the newly created object or the NULL reference. To declare a variable which can be assigned the result of the create_instance operation we write:
The ''add_property''-statement can be executed any time and all subtypes to
ODB_REF My_NewObj;
the target type that may exist will also be given the property. All instances to
My_NewObj=My_DB.create_instance("Person");
the types which has been given a new property will also be given the property.
The return value may be discarded but then there is a problem in referencing the object at a later stage. Perhaps the best procedure is to create an object and keep a reference to it. This reference can then be used when the properties of
The value however will be undefined and it is up to the application that the
the objects are assigned values. When values are assigned to its properties the reference may be discarded and the object can at a later stage be referenced through its attribute values. This is easily done through the query interface described later in this manual.
value is not referenced prior to being assigned a value.
 
 
== 4.4  Object creation ==
Objects are created as instances to a particular type. When objects are created
these are given all the properties defined to the type. The values of the
properties are undefined and must be assigned values prior to being referenced.
 
To create an instances of the type named Person in the database ''My_DB''
we write:
 
 
<pre>
My_DB.create_instance("Person");
 
</pre>
 
The return value is a reference to the newly created object or the NULL
reference. To declare a variable which can be assigned the result of the
create_instance operation we write:
 
<pre>
ODB_REF My_NewObj;
My_NewObj=My_DB.create_instance("Person");
 
</pre>
 
The return value may be discarded but then there is a problem in referencing the
object at a later stage. Perhaps the best procedure is to create an object and
keep a reference to it. This reference can then be used when the properties of
the objects are assigned values. When values are assigned to its properties the
reference may be discarded and the object can at a later stage be referenced
through its attribute values. This is easily done through the [[query interface]]described later in this manual.
== 4.5  Object modification ==
Object modification means changing the values of its properties. This is done by
using sending the message ''set_propertyvalue'' to the database object as:
 
<pre>
My_DB.setproperty_value(My_NewObj,"Salary",(ODB_INT)53200);
</pre>


=== 4.5 Object modification ===
Object modification means changing the values of its properties. This is done by using sending the message ''set_propertyvalue'' to the database object as:
My_DB.setproperty_value(My_NewObj,"Salary",(ODB_INT)53200);
Where ''My_NewObj'' is an '''ODB'''-object declared and created e.g as:
Where ''My_NewObj'' is an '''ODB'''-object declared and created e.g as:
 
ODB_REF My_NewObj;
<pre>
My_NewObj=My_DB.create_instance("Employee");
ODB_REF My_NewObj;
My_NewObj=My_DB.create_instance("Employee");
</pre>
 
The object is given the value 53200 for the property named Salary. Note that constants may require type casting for disambiguation purposes. In this example the constant 53200 is casted to the type ODB_INT.
The object is given the value 53200 for the property named Salary. Note that constants may require type casting for disambiguation purposes. In this example the constant 53200 is casted to the type ODB_INT.
The result from the database method ''setproperty_value'' is greater than or less than zero if the operation was successful or failed, respectively.
The result from the database method ''setproperty_value'' is greater than or less than zero if the operation was successful or failed, respectively.
Line 356: Line 182:
When property values are modified all indexes are automatically updated to consider the new value for the property.
When property values are modified all indexes are automatically updated to consider the new value for the property.


== 4.6   Index creation ==
=== 4.6 Index creation ===
To make retrievals through some property fast, indexes can be created. For example if a frequent search in the database is on a particular attribute e.g. Salary then an index should be created on that attribute to facilitate fast access to the objects with a particular value for the attribute named Salary.
To make retrievals through some property fast, indexes can be created. For
example if a frequent search in the database is on a particular attribute e.g.
Salary then an index should be created on that attribute to facilitate fast
access to the objects with a particular value for the attribute named Salary.


Indexes are created from the database object through the ''create_index'' method. The following example creates an index on the Salary property of the type Employee.
Indexes are created from the database object through the ''create_index''
My_DB.create_index("Employee","Salary");
method. The following example creates an index on the Salary property of the
type Employee .
 
 
<pre>
My_DB.create_index("Employee","Salary");


</pre>
Note that if an index is created to some attribute of a particular type the index will also be created to that attribute in all subtypes. In above example any subtype to Employee will get an index on the salary property. It does not matter if the index is created before or after the subtypes are created. It does not matter if instances to the types the index is created over has been created. All existing instances will be entered into the index structures automatically by '''ODB'''.


Note that if an index is created to some attribute of a particular type the
=== 4.7 Type deletion ===
index will also be created to that attribute in all subtypes. In above example
Types can be deleted from the database dynamically. This is a particularly costly operation. When a type is deleted so are the subtypes to the type as well as all objects that are instances to the type or to any subtype of the type. Hence, A type deletion is a recursively applied down the type tree rooted at the specified type to delete.
any subtype to Employee will get an index on the salary property. It does not
matter if the index is created before or after the subtypes are created. It does
not matter if instances to the types the index is created over has been
created. All existing instances will be entered into the index structures
automatically by '''ODB'''.
== 4.7   Type deletion ==
Types can be deleted from the database dynamically. This is a particularly
costly operation. When a type is deleted so are the subtypes to the type as
well as all objects that are instances to the type or to any subtype of the
type. Hence, A type deletion is a recursively applied down the type tree rooted
at the specified type to delete.


Types are deleted by the database object by sending the ''delete_type'' message. The result of the operation is either greater than or less than zero if the operation was successful or failed, respectively.
Types are deleted by the database object by sending the ''delete_type''
message. The result of the operation is either greater than or less than zero if
the operation was successful or failed, respectively.
To delete the type Person from the database ''My_DB'' we write:
To delete the type Person from the database ''My_DB'' we write:
 
My_DB.delete_type("Person");
<pre>
My_DB.delete_type("Person");
</pre>


This will delete the type named Person, all instances of that type and then recursively delete all subtypes to the Person type. This operation can be costly to perform when there are a lot of objects and types that has to be deleted and their memory freed.
This will delete the type named Person, all instances of that type and then recursively delete all subtypes to the Person type. This operation can be costly to perform when there are a lot of objects and types that has to be deleted and their memory freed.
Line 404: Line 201:
Note especially that any references maintained by the application to types or objects which have been deleted must be reset by the application. Dereferncing such a reference is an undefined operation.
Note especially that any references maintained by the application to types or objects which have been deleted must be reset by the application. Dereferncing such a reference is an undefined operation.


== 4.8   Property deletion ==
=== 4.8 Property deletion ===
Single properties can be deleted from types. When a property is deleted from some type it is deleted from all subtypes as well.
Single properties can be deleted from types. When a property is deleted from
some type it is deleted from all subtypes as well.


When a property is deleted from a type the property is also deleted from all object that are instances to that type, i.e. the extent of the type.
When a property is deleted from a type the property is also deleted from all object that are instances to that type, i.e. the extent of the type.
Line 413: Line 208:
Properties are deleted from types by sending the ''delete_property'' message to the database object.
Properties are deleted from types by sending the ''delete_property'' message to the database object.
To delete the property named Salary from the type named Employee in the database ''My_DB'' we write:
To delete the property named Salary from the type named Employee in the database ''My_DB'' we write:
 
My_DB.delete_propery("Employee","Salary");
<pre>
My_DB.delete_propery("Employee","Salary");
</pre>


Note that if the property that is to be deleted is inherited to the specified type that property can not be deleted as that would leave the database schema in an inconsistent state. To properly delete a property it must be deleted from the most general type it is defined for. (''A supertype is more general than its subtypes'')
Note that if the property that is to be deleted is inherited to the specified type that property can not be deleted as that would leave the database schema in an inconsistent state. To properly delete a property it must be deleted from the most general type it is defined for. (''A supertype is more general than its subtypes'')
Line 422: Line 214:
This is because the object-oriented model states that any operation ''op'' applicable to a type ''t'' is also applicable to all subtypes of the type ''t''. Hence, deleting an inherited property would violate this constraint.
This is because the object-oriented model states that any operation ''op'' applicable to a type ''t'' is also applicable to all subtypes of the type ''t''. Hence, deleting an inherited property would violate this constraint.


== 4.9   Delete Object ==
=== 4.9 Delete Object ===
Objects may be deleted. When an object is deleted the system automatically deletes all references to that object from other objects in the database. Any references kept by the application to the deleted object must be reset by the application itself.
Objects may be deleted. When an object is deleted the system automatically
deletes all references to that object from other objects in the database. Any
references kept by the application to the deleted object must be reset by the
application itself.


Objects are deleted by sending the ''delete_object'' message to the database.
Objects are deleted by sending the ''delete_object'' message to the database.
When deleting an object the database object must be provided a handle to the
When deleting an object the database object must be provided a handle to the object that are subject of deletion.
object that are subject of deletion.
 
A handle can be obtained through the [[query interface]] or
[[other methods]] that returns object handles.
 
To exemplify, consider the following example where a handle ''obj'' is
declared. This handle is then assigned an object through the
''getobjectwithoid'' method. This object is then deleted by the database
object:


A handle can be obtained through the query interface or [[#8.4_Getting_an_object_handle|other methods]] that returns object handles.


To exemplify, consider the following example where a handle ''obj'' is declared. This handle is then assigned an object through the ''getobjectwithoid'' method. This object is then deleted by the database object:
<pre>
<pre>
//Declare object handle,
//Declare object handle,
Line 467: Line 245:
//by ODB
//by ODB
My_DB.delete_object(obj);
My_DB.delete_object(obj);
</pre>
</pre>
Any other object that reference the deleted object through some attribute will now have that attribute value set to NULL. Note that in this example the object handle ''obj'' is outside the control of '''ODB''' and must not be used by the application after the deletion. This is up to the application programmer to see to.


Any other object that reference the deleted object through some attribute will
=== 4.10 Delete index ===
now have that attribute value set to NULL. Note that in this example the object
Indexes may be deleted by sending the ''drop_index'' message to the database object.
handle ''obj'' is outside the control of '''ODB''' and must not be used by
In the following example the index over Salary for type Employee in database ''My_DB'' is deleted:
the application after the deletion. This is up to the application programmer
My_DB.drop_index("Employee","Salary");
to see to.
The result is greater or less than zero if the operation was successful or failed respectively.
   
   
== 4.10  Delete index ==
== 5.0 The query interface ==
The query interface is a ''stream based'' query interface. In a stream based query interface the application opens a stream from which objects can be retrieved one by one until no more objects are returned. Thus, no materialization of the entire set of object that may constitute the result of some query occurs.
Indexes may be deleted by sending the ''drop_index'' message to the database
object.
In the following example the index over Salary for type Employee in database
''My_DB'' is deleted:
 
 
<pre>
My_DB.drop_index("Employee","Salary");
 
</pre>
 
The result is greater or less than zero if the operation was successful or
failed respectively.
== 5.0   The query interface ==
The query interface is a ''stream based'' query interface. In a stream based
query interface the application opens a stream from which objects can be
retrieved one by one until no more objects are returned. Thus, no materialization
of the entire set of object that may constitute the result of some query occurs.
The basic operations on streams are:
The basic operations on streams are:
* Open
* Open
* Get
* Get
Line 506: Line 262:
* Close
* Close


Streams may be opened over any type or over another existing stream. Streams
Streams may be opened over any type or over another existing stream. Streams have a state which means that it depends on the previous operations what the result of the ''get'' operation will be. As a consequence of this streams may be opened and objects retrieved one by one at arbitrary occasions, the stream will hold its state and return the next object upon request.
have a state which means that it depends on the previous operations what the
 
result of the ''get'' operation will be. As a consequence of this streams
Open streams may be ''reset'' which means that their state is changed to some initial state. In order to redo a query this is a useful feature that saves the application from having to open and combine some new streams to redo the same query.
may be opened and objects retrieved one by one at arbitrary occasions, the
stream will hold its state and return the next object upon request.
Open streams may be ''reset'' which means that their state is changed to some
initial state. In order to redo a query this is a useful feature that saves the
application from having to open and combine some new streams to redo the same
query.


Streams which will not be used any more may be ''closed''. Closing a streams means it cannot be reused and any reference to any stream which is closed must not be reused.
Streams which will not be used any more may be ''closed''. Closing a streams
means it cannot be reused and any reference to any stream which is closed must
not be reused.


There are three different types of streams:
There are three different types of streams:
* Query stream
* Query stream
* Select stream
* Select stream
* Semi-join stream
* Semi-join stream


Query streams are opened over types to select all or those object which comply
Query streams are opened over types to select all or those object which comply with some condition that can be defined when opening the stream.
with some condition that can be defined when opening the stream.
Select streams are opened over another stream to select only those objects that
satisfy a condition that is defined with the select stream.
 
Semi-join streams are opened over two other streams. The semi join stream
returns those object from the left operand stream for which there exists an
object in the right operand stream that satisfy some condition defined over
the two streams.
 
== 5.1  Opening streams ==
Streams are opened by sending a message to the database object. The return value
is either a reference to the new stream or NULL if the operation was successful
or failed, respectively. In this section ''stream handles'' and ''open
stream statements'' are explained.
 
=== 5.1.1  Stream handles ===
   
   
There are three C++ types that may be used to declare stream handles. A handle,
Select streams are opened over another stream to select only those objects that satisfy a condition that is defined with the select stream.
''q_strm'', to a query_stream is declared as:


Semi-join streams are opened over two other streams. The semi join stream returns those object from the left operand stream for which there exists an object in the right operand stream that satisfy some condition defined over the two streams.


<pre>
=== 5.1 Opening streams ===
ODB_QSTREAM q_strm;
Streams are opened by sending a message to the database object. The return value is either a reference to the new stream or NULL if the operation was successful or failed, respectively. In this section ''stream handles'' and ''open stream statements'' are explained.
 
</pre>


==== 5.1.1 Stream handles ====
There are three C++ types that may be used to declare stream handles. A handle, ''q_strm'', to a query_stream is declared as:
ODB_QSTREAM q_strm;
A declaration of a select stream, ''s_strm'', handle is:
A declaration of a select stream, ''s_strm'', handle is:
 
ODB_SSTREAM s_strm;
<pre>
ODB_SSTREAM s_strm;
 
</pre>
 
And for a semi-join stream the handle, ''sj_strm'', is declared as:
And for a semi-join stream the handle, ''sj_strm'', is declared as:
ODB_SJSTREAM sj_strm;
==== 5.1.2 Opening a query stream ====
Query streams are opened over some type. This means that the stream accesses the objects that are instances of the type, the type extent, and the instances of its subtypes.


<pre>
For example, in this figure, if a query stream is opened over the type named Person then all objects of type person and of the type named Employee will be considered as possible objects to output.
ODB_SJSTREAM sj_strm;
 
</pre>
 
=== 5.1.2  Opening a query stream ===
Query streams are opened over some type. This means that the stream accesses the
objects that are instances of the type, the type extent, and the instances of
its subtypes.
   
For example, in this figure, if a query stream is opened over the type named
Person then all objects of type person and of the type named Employee will be
considered as possible objects to output.


A query stream can be opened over a type named Employee as:
A query stream can be opened over a type named Employee as:
 
My_DB.open_stream("Employee");
 
<pre>
My_DB.open_stream("Employee");
</pre>
 
This stream will retrieve every object in the extent of type Employee and in the extent of all subtypes to the type named Employee, called the ''deep extent'' of type Employee.
This stream will retrieve every object in the extent of type Employee and in the extent of all subtypes to the type named Employee, called the ''deep extent'' of type Employee.
   
   
A stream can be opened which only retrieves object that satisfy some condition defined with the stream. Assume we want to retrieve all persons aged 18, i.e. all objects in the deep extent of the type named Person that have the value 18 for its age property. This is done as:
A stream can be opened which only retrieves object that satisfy some condition defined with the stream. Assume we want to retrieve all persons aged 18, i.e. all objects in the deep extent of the type named Person that have the value 18 for its age property. This is done as:
 
My_DB.open_stream("Person","Age", "=",(ODB_INT)18);
<pre>
My_DB.open_stream("Person","Age", "=",(ODB_INT)18);
</pre>


In this statement we used the =-operator to compare the values of the objects with the value defined with the stream. For integer and real there are additional comparison operators that can be used. These are:
In this statement we used the =-operator to compare the values of the objects with the value defined with the stream. For integer and real there are additional comparison operators that can be used. These are:
* =
* =
* &lt
* <
* &lt =
* <=
* >
* >
* >=
* >=
Line 611: Line 311:


For object reference, charstring and collection valued properties only equality is meaningful thus when opening streams with condition on such properties the operator can be excluded.
For object reference, charstring and collection valued properties only equality is meaningful thus when opening streams with condition on such properties the operator can be excluded.
 
For collections the equality operator is ''overloaded''. If one operand is a collection and the other is a non collection type then the equality means the ''member'' operator. If on the other hand both operands are sets then the equality operator means that the same members should be found in both sets, i.e. A=B means that if an object, o, is member of set A then o must also be a member of the set B and vice verca.
For collections the equality operator is ''overloaded''. If one operand is a collection and the other is a non collection type then the equality means the ''member'' operator. If on the other hand both operands are sets then the equality operator means that the same members should be found in both sets, i.e. A=B means that if an object, o, is member of set A then o must also be a member of the set B and vice verca.


Line 617: Line 317:


A stream is to be opened over the Employee type where all Employees which have the name "Bob Smith" should be returned from the stream. This is written as:
A stream is to be opened over the Employee type where all Employees which have the name "Bob Smith" should be returned from the stream. This is written as:
 
My_DB.open_stream("Employee","Name","Bob Smith");
<pre>
My_DB.open_stream("Employee","Name","Bob Smith");
</pre>


Streams with restrictions over collections and object reference valued properties are opened analogously.
Streams with restrictions over collections and object reference valued properties are opened analogously.


=== 5.1.3   Opening a select stream ===
==== 5.1.3 Opening a select stream ====
Select streams are opened over existing streams to add additional conditions on the objects that are sought.
Select streams are opened over existing streams to add additional conditions
If for example, all Person objects with age greater than 18 and whose name is "John Doe" are interesting we write:
on the objects that are sought.
If for example, all Person objects with age greater than 18 and whose name is
"John Doe" are interesting we write:
 
 
<pre>
<pre>
ODB_QSTREAM s1;
ODB_QSTREAM s1;
Line 637: Line 329:
//Open s1
//Open s1
s1=My_DB.open_stream("Person","Age","&gt =",(ODB_INT)18);
s1=My_DB.open_stream("Person","Age","&gt =",(ODB_INT)18);
</pre>
</pre>
Now, ''s1'' retrieves all objects of type Person with age greater than or equal to 18. The additional name condition can be added by opening a select_stream over this stream as:
s2=My_DB.select(s1,"Name","John Doe");
Here, a stream is opened over the existing stream, s1, and a condition on the name attribute is defined and the result is bound to the stream variable s2.
The objects are retrieved from s2 by the get-method.


==== 5.1.4 Open a semi join stream ====
The semi-join operation is a variant of the traditional join operator in the relational algebra.
The semi-join stream is opened over two streams where attribute values from objects in one stream are compared with attribute values of the objects from the other stream. Only objects from one stream is in the result of the stream. The latter is the difference from a traditional join operator where the result is a tuple of values, (''See any textbook that covers the relational algebra and relational databases'').


Now, ''s1'' retrieves all objects of type Person with age greater than or
To exemplify, assume we have two types, Pet and Person where both have a character string valued ''name''-attribute. If we would like to retrieve those persons that has the same name as a pet has we write:
equal to 18. The additional name condition can be added by opening a
select_stream over this stream as:
 
<pre>
s2=My_DB.select(s1,"Name","John Doe");
 
</pre>
 
 
Here, a stream is opened over the existing stream, s1, and a condition on the
name attribute is defined and the result is bound to the stream variable s2.
The objects are retrieved from s2 by the [[get-method]].
=== 5.1.4  Open a semi join stream ===
The semi-join operation is a variant of the traditional join operator in the
relational algebra.
The semi-join stream is opened over two streams where attribute values from
objects in one stream are compared with attribute values of the objects from the other stream. Only objects from one stream is in the result of the stream. The latter is the difference from a traditional join operator where the result is a tuple of values, (''See any textbook that covers the relational algebra and relational databases'').
 
To exemplify, assume we have two types, Pet and Person where both have a
character string valued ''name''-attribute. If we would like to retrieve
those persons that has the same name as a pet has we write:
 
 
<pre>
<pre>
ODB_QSTREAM qst1,qst2;
ODB_QSTREAM qst1,qst2;
Line 679: Line 351:
//Open the semi join stream
//Open the semi join stream
sjs=My_DB.semi_join(qst1,"Name",qst2,"Name");
sjs=My_DB.semi_join(qst1,"Name",qst2,"Name");
</pre>
</pre>
 
The objects returned will be from the stream qst1 that has the same ''name''-value as any object of type pet.
The objects returned will be from the stream qst1 that has the same
''name''-value as any object of type pet.
   
   
== 5.2   Retrieving objects from streams ==
=== 5.2 Retrieving objects from streams ===
Given an open stream, objects can be retrieved through that stream by sending the ''get'' message to the database object with the stream as additional argument with the message. The result is either an object or NULL depending on the success of the operation.
Given an open stream, objects can be retrieved through that stream by sending
the ''get'' message to the database object with the stream as additional
argument with the message. The result is either an object or NULL depending
on the success of the operation.
 
The following example opens a stream and retrieves all objects that satisfies
the condition that their value for the age property must be greater then 65.
 


The following example opens a stream and retrieves all objects that satisfies the condition that their value for the age property must be greater then 65.
<pre>
<pre>
ODB_QSTREAM qst;
ODB_QSTREAM qst;
Line 717: Line 378:
}
}
} //end else
} //end else
</pre>
</pre>
 
This example shows the standard way of iterating through a stream. When all objects are retrieved (a NULL reference is returned) the stream can either be closed or reset to its initial state.
 
This example shows the standard way of iterating through a stream. When all
objects are retrieved (a NULL reference is returned) the stream can either be
closed or reset to its initial state.
   
   
== 5.3   Stream reset ==
=== 5.3 Stream reset ===
When an opened stream has reached its end or if we by some other reason do not want to retrieve any more objects from it the stream can be reset. When a stream is reset it is set to the same state as if it was just opened.
When an opened stream has reached its end or if we by some other reason do not
want to retrieve any more objects from it the stream can be reset. When a
stream is reset it is set to the same state as if it was just opened.
 
Streams that will be used again to query the data should be reset instead of
closed and recreated.
A stream is reset by sending the ''reset_stream'' message to the database
object as:
 
 
<pre>
My_DB.reset_stream(qst);
 
</pre>


Streams that will be used again to query the data should be reset instead of closed and recreated.
A stream is reset by sending the ''reset_stream'' message to the database object as:
My_DB.reset_stream(qst);
Where qst is an object of type query, select or semi join stream.
Where qst is an object of type query, select or semi join stream.
   
   
== 5.4   Stream close ==
=== 5.4 Stream close ===
Streams are closed when it is no longer to be reused again. For composite streams, closing the outer most stream will cause all streams it uses to be closed as well. In Figure4 this means that closing the stream ''s2'' will also close the stream ''s1''.  
Streams are closed when it is no longer to be reused again. For composite streams, closing the outer most stream will cause all streams it uses to be closed as well. In Figure4 this means that closing the stream ''s2'' will also close the stream ''s1''.  


Line 752: Line 396:
In the same figure, closing ''s1'' only will leave ''s2'' in an undefined state.
In the same figure, closing ''s1'' only will leave ''s2'' in an undefined state.
Streams are closed by sending the ''close_stream'' message to the database object as:
Streams are closed by sending the ''close_stream'' message to the database object as:
<pre>
<pre>
ODB_QSTREAM qst;
ODB_QSTREAM qst;
Line 758: Line 401:
My_DB.close_stream(qst);
My_DB.close_stream(qst);
</pre>
</pre>
After a stream has been closed th application must not try to use the reference unless the reference is assigned another stream.
After a stream has been closed th application must not try to use the reference unless the reference is assigned another stream.


= 6.0   Collections =
== 6.0 Collections ==
The datatype ''collection'' is somewhat more complex than the other datatypes supported by '''ODB'''. A collection is similar to a set with the difference that a collection can contain several equal objects or values.
The datatype ''collection'' is somewhat more complex than the other datatypes
supported by '''ODB'''. A collection is similar to a set with the difference
that a collection can contain several equal objects or values.
 
== 6.1  Creating collections ==
Collections are created by sending the ''create_collection ''message to the
database object. On successful creation a handle to an empty collection is
returned. This handle must '''ALWAYS''' be assigned a reference owned by the
application since the collection otherwise will be unaccessible to the
application. The application owned reference can be released when the collection
is referenced from elswhere, e.g. through an attribute of some object.
 
A collection which may contain references to other objects is created and
assigned a collection handle ''ch'' as:


=== 6.1 Creating collections ===
Collections are created by sending the ''create_collection ''message to the database object. On successful creation a handle to an empty collection is returned. This handle must '''ALWAYS''' be assigned a reference owned by the application since the collection otherwise will be unaccessible to the application. The application owned reference can be released when the collection is referenced from elswhere, e.g. through an attribute of some object.


A collection which may contain references to other objects is created and assigned a collection handle ''ch'' as:
<pre>
<pre>
ODB_SET ch;
ODB_SET ch;
ch=My_DB.create_collection(_OBJECT_);
ch=My_DB.create_collection(_OBJECT_);
</pre>
</pre>
Valid type identifiers are to the create_collection method are:
Valid type identifiers are to the create_collection method are:
* _INT_
* _INT_
* _REAL_
* _REAL_
* _CHARSTRING_
* _CHARSTRING_
* _OBJECT_
* _OBJECT_
Thus, it is not possible to declare a collection to contain other collections.  
Thus, it is not possible to declare a collection to contain other collections.  
   
   
== 6.2   Adding elements to a collection ==
=== 6.2 Adding elements to a collection ===
When a collection has been created elements may be inserted into the collection.
When a collection has been created elements may be inserted into the collection.
'''ODB''' supports two way of doing this; by sending the ''collection_insert''
'''ODB''' supports two way of doing this; by sending the ''collection_insert'' message to the database object or by sending the ''insert'' message directly to the collection object.
message to the database object or by sending the ''insert'' message directly
to the collection object.
 
In the following example a collection of character strings are created and bound
to the handle ''coll''. Then two elements are inserted into the set by
''collection_insert'' and by ''insert''.
 


In the following example a collection of character strings are created and bound to the handle ''coll''. Then two elements are inserted into the set by ''collection_insert'' and by ''insert''.
<pre>
<pre>
ODB_SET coll;
ODB_SET coll;
Line 818: Line 433:
My_DB.collection_insert(coll,"Object");
My_DB.collection_insert(coll,"Object");
coll->insert("Orientation");
coll->insert("Orientation");
</pre>
</pre>
The difference with these two ways is that by sending the collection_insert message to the database objects all necessary checks are performed before insertion. When the insert message is sent to a collection the application must ensure that the collection is of the correct type and that the collection handle is initialized properly.


The difference with these two ways is that by sending the collection_insert
message to the database objects all necessary checks are performed before
insertion. When the insert message is sent to a collection the application
must ensure that the collection is of the correct type and that the collection
handle is initialized properly.
The return value from collection_insert is:
The return value from collection_insert is:
 
* > 0 upon succesful insert
 
* &gt 0 upon succesful insert
* == -1 for improper handle
* == -1 for improper handle
* ==-2 if the collection was of another type than the element to insert
* ==-2 if the collection was of another type than the element to insert
    
    
== 6.3   Delete element from collection ==
=== 6.3 Delete element from collection ===
Elements may be deleted from a collection by either sending the ''delete_element'' message to the set object or by sending the
Elements may be deleted from a collection by either sending the
''delete_from_collection'' message to the database object. In the following example objects are deleted from the set coll.
''delete_element'' message to the set object or by sending the
''delete_from_collection'' message to the database object. In the following
example objects are deleted from the set coll.
 
 
<pre>
<pre>
ODB_SET coll;
ODB_SET coll;
Line 857: Line 458:
My_DB.delete_from_collection(coll,3);
My_DB.delete_from_collection(coll,3);
coll->delete(2);
coll->delete(2);
</pre>
</pre>
 
Using the ''delete_from_collection'' message to the database object is a safer way since all necessary checks are performed before deketion of any element. However, sending the ''delete'' message directly to the collection object is slightly faster but then the application must ensure that the handle is initialized etc.
 
Using the ''delete_from_collection'' message to the database object is a
safer way since all necessary checks are performed before deketion of any
element. However, sending the ''delete'' message directly to the collection
object is slightly faster but then the application must ensure that the handle
is initialized etc.
   
   
Note especially that before using the ''delete'' message directly to the
Note especially that before using the ''delete'' message directly to the collection object to delete an ODB_CHAR the characterstring must be substituted with the characterstring stored in the string table. The same is true for inserting characterstrings directly into a collection The following example illustrates the correct procedure:
collection object to delete an ODB_CHAR the characterstring must be substituted
with the characterstring stored in the string table. The same is true for
inserting characterstrings directly into a collection The following example
illustrates the correct procedure:
 
 
<pre>
<pre>
ODB_REF cstr, cstr2;
ODB_REF cstr, cstr2;
Line 895: Line 483:
cstr2=My_DB.getstring("ODB");
cstr2=My_DB.getstring("ODB");
coll.delete(cstr2);
coll.delete(cstr2);
</pre>
</pre>
    
    
== 6.4   Delete collection ==
=== 6.4 Delete collection ===
Collections are deleted by either sending the ''delete_collection'' message to the database object or the ''delete_set'' message to the collection object.
Collections are deleted by either sending the ''delete_collection'' message
to the database object or the ''delete_set'' message to the collection object.


== 7.0 Datatypes ==
= 7.0   Datatypes =
There are a number of '''ODB''' specific datatypes and type identifiers that are used to interface '''ODB''' in the API.
There are a number of '''ODB''' specific datatypes and type identifiers that
are used to interface '''ODB''' in the API.


=== 7.1 C++ Datatypes ===
== 7.1   C++ Datatypes ==
There are a number of C++ datatypes and classes defined in the API these are:
There are a number of C++ datatypes and classes defined in the API these are:
 
{| border="1"
{| border="1"
|-
! C++ datatype  
! C++ datatype  
! Type of data
! Type of data
|-
|-
|ODB_INT
|ODB_INT
|integer
|integer
|-
|-
|ODB_REAL
|ODB_REAL
|real
|real
|-
|-
|ODB_OID
|ODB_OID
|oid
|oid
|-
|-
|ODB_CHAR
|ODB_CHAR
|charstring
|charstring
|-
|-
|ODB_REF
|ODB_REF
|object handle
|object handle
|-
|-
|ODB_SET
|ODB_SET
|collection handle
|collection handle
|-
|-
|ODB_QSTREAM
|ODB_QSTREAM
|query stream handle
|query stream handle
|-
|-
|ODB_SSTREAM
|ODB_SSTREAM
|select stream handle
|select stream handle
|-
|-
|ODB_SJSTREAM
|ODB_SJSTREAM
|semi-join stream handle
|semi-join stream handle
|-
|-
|ODB_DB
|ODB_DB
|database object
|database object
|-
|-
|ODB_DB_HANDLE
|ODB_DB_HANDLE
|odb database handle
|odb database handle
|-
|-
|ODB_TYPE
|ODB_TYPE
|odb type handle
|odb type handle
|}   
|}   
These types are used by the application using '''ODB''' to declare interface
These types are used by the application using '''ODB''' to declare interface variables to '''ODB'''.
variables to '''ODB'''.


The C++ classes the API provides are:
The C++ classes the API provides are:
 
{| border="1"
{| border="1"
|-
!C++ Class
!C++ Class
!Ref. decl
!Ref. decl
!Description
!Description
|-
|-
|Database
|Database
|ODB_DB
|ODB_DB
|The ODB database class
|The ODB database class
|-
|-
|Type
|Type
|ODB_TYPE(ref.)
|ODB_TYPE(ref.)
|The ODB type class
|The ODB type class
|-
|-
|Object
|Object
|ODB_REF (ref.)
|ODB_REF (ref.)
|The ODB object class
|The ODB object class
|-
|-
|Outputbuffer
|Outputbuffer
|N/A
|N/A
|The ODB outputbuffer class
|The ODB outputbuffer class
|}


|} 
=== 7.2 Type identifiers ===
== 7.2   Type identifiers ==
There are a number of type identifiers defined in the API which are used to specify to '''ODB''' which type to consider. These are:
There are a number of type identifiers defined in the API which are used to
specify to '''ODB''' which type to consider. These are:
 
 
* _INT_
* _INT_
* _REAL_
* _REAL_
Line 1,016: Line 568:
* _REAL_COLLECTION_
* _REAL_COLLECTION_
* _CHAR_COLLECTION_
* _CHAR_COLLECTION_
 
These identifiers are used to identify objects of C++ datatypes as:
These identifiers are used to identify objects of C++ datatypes as :
 
{| border="1"
{| border="1"
|-
! Type Id
! Type Id
!C++ Type
!C++ Type
|-
|-
|_INT_
|_INT_
|ODB_INT
|ODB_INT
|-
|-
|_REAL_
|_REAL_
|ODB_REAL
|ODB_REAL
|-
|-
|_CHAR_
|_CHAR_
|ODB_CHAR
|ODB_CHAR
|-
|-
|_OBJECT_
|_OBJECT_
|ODB_REF
|ODB_REF
|-
|-
|_OBJECT_COLLECTION_
|_OBJECT_COLLECTION_
|ODB_SET
|ODB_SET
|-
|-
|_INT_COLLECTION_
|_INT_COLLECTION_
|ODB_SET
|ODB_SET
|-
|-
|_REAL_COLLECTION_
|_REAL_COLLECTION_
|ODB_SET
|ODB_SET
|-
|-
|_CHAR_COLLECTION_
|_CHAR_COLLECTION_
|ODB_SET
|ODB_SET
|}
|}
Table1. Type and Id mapping
Table1. Type and Id mapping
 
Note that the C++ datatype ODB_SET is used to denote all collection types.
Note that the C++ datatype ODB_SET is used to denote all collection types.
The type identifiers are used to denote the result type of properties when these
The type identifiers are used to denote the result type of properties when these are created and to denote which type of elements a collection can contain.
are created and to denote which type of elements a collection can contain.
 
= 8.0  More interface methods =
In addition to database creation, maintenance and querying there a methods to
print objects, retrieve object and types etc. these are described here.


== 8.0 More interface methods ==
== 8.1  The string table ==
In addition to database creation, maintenance and querying there a methods to print objects, retrieve object and types etc. these are described here.
Each database object contains a stringtable where all strings used by the datbase
are stored. Before some operatons involving strings can be executed the
application must get a handle to the string maintained by the database otherwise
equality tests etc. will not function properly. In most cases '''ODB''' does
this for you.


=== 8.1 The string table ===
Each database object contains a stringtable where all strings used by the datbase are stored. Before some operatons involving strings can be executed the application must get a handle to the string maintained by the database otherwise equality tests etc. will not function properly. In most cases '''ODB''' does this for you.
   
   
A string handle to a prticular string is obtained by sending the ''getstring''
A string handle to a prticular string is obtained by sending the ''getstring'' message to the database object. In the following example a handle is obtained to the string "ODB Rules".
message to the database object. In the following example a handle is obtained
ODB_CHAR str;
to the string "ODB Rules".
str=My_DB.getstring("ODB Rules");
 
 
<pre>
ODB_CHAR str;
str=My_DB.getstring("ODB Rules");
 
</pre>
 
The following example demonstrates the benefit if the string table:
The following example demonstrates the benefit if the string table:
<pre>
<pre>
ODB_CHAR s1, s2;
ODB_CHAR s1, s2;
Line 1,107: Line 618:
if (s1==s2) cout &lt&lt "\nODB Rules\n";
if (s1==s2) cout &lt&lt "\nODB Rules\n";
else cout &lt&lt "\nYou cheated\n");
else cout &lt&lt "\nYou cheated\n");
</pre>
</pre>
The output will of course be: ODB Rules  
The output will of course be: ODB Rules  
   
   
== 8.2   Printing ODB objects ==
=== 8.2 Printing ODB objects ===
All objects, types and databases may be printed by sending the ''display'' message to the object.
To support any print-method the application may want to utilize all display methods of all '''ODB''' objects print in a buffer which is provided by the '''ODB'''API.
   
   
All objects, types and databases may be printed by sending the ''display''
The ''display'' message can be sent to any '''OBD''' object with a ''display buffer'' as argument as:
message to the object.
To support any print-method the application may want to utilize all display
methods of all '''ODB''' objects print in a buffer which is provided by the
'''ODB'''API.
 
The ''display'' message can be sent to any '''OBD''' object with a
''display buffer'' as argument as:
 
 
<pre>
<pre>
displaybuffer buf;
displaybuffer buf;
Line 1,141: Line 642:
//reset buffer before used
//reset buffer before used
buf.reset_buffer();
buf.reset_buffer();
</pre>
</pre>
    
    
=== 8.2.1   Display buffer ===
==== 8.2.1 Display buffer ====
OBD provides a class named ''outputbuffer'' which all display methods use. A buffer, Disp_Buf is declared in the application as:
OBD provides a class named ''outputbuffer'' which all display methods use.
outputbuffer DispBuf;
A buffer, Disp_Buf is declared in the application as:
 
 
<pre>
outputbuffer DispBuf;
 
</pre>
 
The buffer can be retrieved by sending the get_buffer message as:
The buffer can be retrieved by sending the get_buffer message as:
 
Disp_Buf.getbuffer();
<pre>
Disp_Buf.getbuffer();
 
</pre>
 
The return value is a reference to a character buffer as:
The return value is a reference to a character buffer as:
<pre>
<pre>
ODB_CHAR BufPtr
ODB_CHAR BufPtr
BufPtr=Disp_Buf.getbuffer();
BufPtr=Disp_Buf.getbuffer();
</pre>
The reference to the buffer can then be used by the application to print the
content of the buffer on a widget in a GUI or on a tex display using cout etc.as
e.g.:
<pre>
ODB_CHAR BufPtr
BufPtr=Disp_Buf.getbuffer();
cout &lt&lt BufPtr;
</pre>
When the buffer has been printed or to prevent it from overflowing it can be
emptied by sending the ''reset_buffer'' message to the buffer object as:
<pre>
DispBuf.reset_buffer();
</pre>
The buffer size is 1k and if there is not enough free space in it when used by
some object, the display output from the object will be lost.
== 8.3  Getting a type handle ==
If a handle is required to a type for e.g. printing the gettypenamed message to
the database object as:
<pre>
ODB_TYPE tp;
tp=My_DB.gettypenemrd("Person");
</pre>
In this example a handle is returned to the type named Person. If no such type
exists the NULL handle is returned.
== 8.4  Getting an object handle ==
Object handles can retreived by using the query interface, see
[[Section 5.0]], or by sending the ''getobjectwithoid''
message to the database object as:
<pre>
ODB_REF o;
o=My_DB.getobjectwithoid("Usertypes",45);
</pre>
</pre>
The reference to the buffer can then be used by the application to print the content of the buffer on a widget in a GUI or on a tex display using cout etc.as e.g.:
ODB_CHAR BufPtr
BufPtr=Disp_Buf.getbuffer();
cout << BufPtr;
When the buffer has been printed or to prevent it from overflowing it can be emptied by sending the ''reset_buffer'' message to the buffer object as:
DispBuf.reset_buffer();
The buffer size is 1k and if there is not enough free space in it when used by some object, the display output from the object will be lost.


In this example the object with oid equal 45 is returned, if no such object exist
=== 8.3 Getting a type handle ===
a NULL handle is returned.
If a handle is required to a type for e.g. printing the gettypenamed message to the database object as:
Note especially that there is no guarantee that objects will get the same oid
ODB_TYPE tp;
when reloaded from file, in fact it is very likely that it will get another oid.
  tp=My_DB.gettypenemrd("Person");
   
In this example a handle is returned to the type named Person. If no such type exists the NULL handle is returned.
== 8.5  Traversing all subtypes of a type ==
   
   
If the application for some reason wants to traverse the type tree of the
=== 8.4 Getting an object handle ===
database the application then first sends the ''getallsubtypes'' message to
Object handles can retreived by using the query interface, see [[#5.0 The query interface|Section 5.0]], or by sending the ''getobjectwithoid'' message to the database object as:
the database object to get the first subtype. All subsequent subtypes are
ODB_REF o;
retreived one by one by sending the ''getnextsubtype'' message to the database
o=My_DB.getobjectwithoid("Usertypes",45);
object. The following exaple shows how all types in the database are printed
In this example the object with oid equal 45 is returned, if no such object exist a NULL handle is returned.
on stdout:
Note especially that there is no guarantee that objects will get the same oid when reloaded from file, in fact it is very likely that it will get another oid.
 


=== 8.5 Traversing all subtypes of a type ===
If the application for some reason wants to traverse the type tree of the database the application then first sends the ''getallsubtypes'' message to the database object to get the first subtype. All subsequent subtypes are retreived one by one by sending the ''getnextsubtype'' message to the database object. The following exaple shows how all types in the database are printed on stdout:
<pre>
<pre>
ODB_CHAR tp; //type names
ODB_CHAR tp; //type names
Line 1,244: Line 685:
tp=My_DB.getallsubtypes();
tp=My_DB.getallsubtypes();
while(tp!=NULL){
while(tp!=NULL){
cout &lt&lt tp &lt&lt `\n';
cout << tp << `\n';
tp=My_DB.getnextsubtype();
tp=My_DB.getnextsubtype();
//Type handles to the subtypes
//Type handles to the subtypes
Line 1,250: Line 691:
tph=My_DB.gettypenamed(tp);
tph=My_DB.gettypenamed(tp);
}
}
</pre>
</pre>
 
== 8.6  Traversing direct subtypes of a type ==
A ''direct subtype'' of a type, ''t'', is a type whose supertype is the
type ''t''. The direct subtypes are traversed by first sending the
''getsubtypes'' message to the database object and then sending the
''getsubtype'' message to get the other subtype names, one by one as:


=== 8.6  Traversing direct subtypes of a type ===
A ''direct subtype'' of a type, ''t'', is a type whose supertype is the type ''t''. The direct subtypes are traversed by first sending the ''getsubtypes'' message to the database object and then sending the ''getsubtype'' message to get the other subtype names, one by one as:
<pre>
<pre>
char *tp; //type names
char *tp; //type names
Line 1,274: Line 709:
tph=My_DB.gettypenamed(tp);
tph=My_DB.gettypenamed(tp);
}
}
</pre>
</pre>
In this example all direct subtypes of the type named Person are retreived.
In this example all direct subtypes of the type named Person are retreived.
   
   
== 8.7   Getting the supertype to a type ==
=== 8.7 Getting the supertype to a type ===
The supertype name of a particular type can be retreived by using the ''getsupertype'' message to the database object.
The supertype name of a particular type can be retreived by using the
''getsupertype'' message to the database object.
 
   
   
In this example the supertypename of the type named Employee is retreived and
In this example the supertypename of the type named Employee is retreived and then used to get a handle to the supertype to the type named Employee.
then used to get a handle to the supertype to the type named Employee.
 
 
<pre>
<pre>
ODB_CHAR stpnm;
ODB_CHAR stpnm;
Line 1,295: Line 721:
stpnm=My_DB.getsupertype("Employee");
stpnm=My_DB.getsupertype("Employee");
stp=My_DB.gettypenamed(stpnm);
stp=My_DB.gettypenamed(stpnm);
</pre>
</pre>
    
    
== 8.8   Getting all properties of a type ==
=== 8.8 Getting all properties of a type ===
The names of all properties of a type can be retreived b y first sending the ''getfirstprop'' messageto the database object and then get the rest of the property names by sending the ''getnextprop'' message to the database object.
The names of all properties of a type can be retreived b y first sending the
''getfirstprop'' messageto the database object and then get the rest of the
property names by sending the ''getnextprop'' message to the database object.


In this example all properties of the type named Employee are printed to stdout.
In this example all properties of the type named Employee are printed to stdout.
<pre>
<pre>
ODB_CHAR pn; //prop names
ODB_CHAR pn; //prop names
Line 1,318: Line 737:
pn=My_DB.getnextprop("Employee");
pn=My_DB.getnextprop("Employee");
}
}
</pre>
</pre>
 
== 8.9  Getting the type of a property ==
As described in [[Section 4.3]] all properties are associated
with a type where the type describes which objects the property can have as
values.
The type of a property can be retreived by sending the ''getpropertytype''
message to the database object. The returned value is a type identifier
[[see section 4.3]].
In the following example the type identifier is retreived from the name
property of the Person type.


=== 8.9 Getting the type of a property ===
As described in Section 4.3 all properties are associated with a type where the type describes which objects the property can have as values.


The type of a property can be retreived by sending the ''getpropertytype'' message to the database object. The returned value is a type identifier see [[#4.3 Property Creation|section 4.3]].
In the following example the type identifier is retreived from the name property of the Person type.
<pre>
<pre>
ODB_TYPE_TAG ttg;
ODB_TYPE_TAG ttg;
ttg=My_DB.getpropertytype("Person","Name");
ttg=My_DB.getpropertytype("Person","Name");
</pre>
</pre>
 
== 8.10  Getting the value of an object  property ==
Each property denotes a value or other object. To retreive the denoted value or
object the application sends the ''getproperty_value'' message to the database
object.
It is important to note that the return value is a code indicating the success
of the operation. The value is retreived by sending the reference to the variable
the result is intendet to be retreived into. In the next example the value of a
set valued property is retreived into the variable ''coll'':


=== 8.10 Getting the value of an object  property ===
Each property denotes a value or other object. To retreive the denoted value or object the application sends the ''getproperty_value'' message to the database object.


It is important to note that the return value is a code indicating the success of the operation. The value is retreived by sending the reference to the variable the result is intendet to be retreived into. In the next example the value of a set valued property is retreived into the variable ''coll'':
<pre>
<pre>
ODB_SET coll;
ODB_SET coll;
Line 1,364: Line 763:


ok=My_DB.getproperty_value(obj,"Parents",&coll);
ok=My_DB.getproperty_value(obj,"Parents",&coll);
</pre>
</pre>
The return value is:
The return value is:
 
* > 0 if the operation succeeded
* &gt 0 if the operation succeeded
* ==-1 if the object reference was NULL
* ==-1 if the object reference was NULL
* ==-2 if the property did not exist
* ==-2 if the property did not exist
* ==-3 if the expected result type did not match the resulttype of the property.
* ==-3 if the expected result type did not match the resulttype of the property.
 
= Conclusion =
This system is intended to support developers in managing moderately sized data
in an easy manner. The data can be
created and changed outside the application by using a graphical user interface
and later loaded into
the application easily without any recompilation or other cumbersome procedure.


== Conclusion ==
This system is intended to support developers in managing moderately sized data in an easy manner. The data can be created and changed outside the application by using a graphical user interface and later loaded into the application easily without any recompilation or other cumbersome procedure.
 
The system supports the object-oriented data modelling paradigm for the data stored.
The system supports the object-oriented data modelling paradigm for the data stored.
The data can easily be queried
The data can easily be queried through a high level stream based interface which the API provides.
through a high level stream based interface which the API provides.<br>
<br>
This is the firs released version of the system and any bug reports or comments
are welcomned to [[me]].<br>
<br>


This is the firs released version of the system and any bug reports or comments are welcomned to me.
= A Sample application =
 
To give an idea of how '''ODB''' is used the following example code illustrates
the interface. This application file is included in the ftp package ODB.ZIP.


== A Sample application ==
To give an idea of how '''ODB''' is used the following example code illustrates the interface. This application file is included in the ftp package ODB.ZIP.
----
----
<pre>
<pre>
 
#include <iostream.h>
#include &lt iostream.h&gt
#include <fstream.h>
#include &lt fstream.h&gt
#include <conio.h>
#include &lt conio.h&gt
#include <time.h>
#include &lt time.h&gt
#include <math.h>
#include &lt math.h&gt
#include <stdlib.h>
#include &lt stdlib.h&gt
#include <sys\timeb.h>
#include &lt sys\timeb.h&gt
#include "odb.h"
#include "odb.h"


outputbuffer obuf=outputbuffer();
outputbuffer obuf=outputbuffer();




Line 1,422: Line 801:
ODB_REF o;
ODB_REF o;


if (tp==NULL) return;   //quit if shitty reference
  if (tp==NULL) return;     //quit if shitty reference


tpname=tp-&gtgetname();   //this method is not described
  tpname=tp->getname();     //this method is not described
//in the manual, it is found
          //in the manual, it is found
//in odb.h
          //in odb.h


q_stream=db.open_stream(tpname);
  q_stream=db.open_stream(tpname);


if (q_stream==NULL) return; //should not happen
  if (q_stream==NULL) return;   //should not happen
//(they all say that)
          //(they all say that)


cout&lt &lt "Browsing the extent of type "&lt &lt tpname&lt &lt '\n';
  cout<<"Browsing the extent of type "<<tpname<<'\n';
obuf.reset_buffer(); //reset the buffer
  obuf.reset_buffer();   //reset the buffer


//follows is the standard structure of iterating through streams
  //follows is the standard structure of iterating through streams
//at ****** Any Code ***** application specific code are enterd
  //at ****** Any Code ***** application specific code are enterd
//in below example the object bound to o is just printed to stdout
  //in below example the object bound to o is just printed to stdout
//-------------------------------------------------
  //-------------------------------------------------
//----------- Std stream iteration-----------------
  //----------- Std stream iteration-----------------
//-------------------------------------------------
  //-------------------------------------------------
//--          o=db.get(q_stream);                --
  //--          o=db.get(q_stream);                --
//--          while (o!=NULL){                  --
  //--          while (o!=NULL){                  --
//--            ****** Any Code *****            --
  //--            ****** Any Code *****            --
//--            o=db.get(q_stream);              --
  //--            o=db.get(q_stream);              --
//--          }                                  --
  //--          }                                  --
//-------------------------------------------------
  //-------------------------------------------------
//----------- End Std stream iteration-------------
  //----------- End Std stream iteration-------------
//-------------------------------------------------
  //-------------------------------------------------
o=db.get(q_stream);
  o=db.get(q_stream);
while (o!=NULL){
  while (o!=NULL){
o-&gtdisplay(obuf);
    o->display(obuf);
cout&lt &lt obuf.get_buffer()&lt &lt '\n';
    cout<<obuf.get_buffer()<<'\n';
obuf.reset_buffer();
    obuf.reset_buffer();
o=db.get(q_stream);
    o=db.get(q_stream);
}
    }


//-------------------------------------------------
  //-------------------------------------------------
//--                I M P O R T A N T            --
  //--                I M P O R T A N T            --
//-------------------------------------------------
  //-------------------------------------------------
//-- The application MUST deallocate the stream  --
  //-- The application MUST deallocate the stream  --
//-- here, otherwise a memory leak would occur as--
  //-- here, otherwise a memory leak would occur as--
//-- the stream can not be referenced again      --
  //-- the stream can not be referenced again      --
//-- Alternatively, declare it as STATIC        --
  //-- Alternatively, declare it as STATIC        --
//-------------------------------------------------
  //-------------------------------------------------
db.close_stream(q_stream);
  db.close_stream(q_stream);
}
  }




Line 1,477: Line 856:
ODB_TYPE tphandle;
ODB_TYPE tphandle;


obuf.reset_buffer(); //empty any old crap in the buffer
  obuf.reset_buffer(); //empty any old crap in the buffer


tpname=db.getallsubtypes(); //get the first typename
  tpname=db.getallsubtypes();   //get the first typename


while (tpname!=NULL){ //loop over all typenames
  while (tpname!=NULL){     //loop over all typenames


tphandle=db.gettypenamed(tpname); //given a type name the handle
    tphandle=db.gettypenamed(tpname); //given a type name the handle
//can be fetched
              //can be fetched
if (tphandle!=NULL) {
    if (tphandle!=NULL) {
//display the type object and all its properties
      //display the type object and all its properties
tphandle-&gtdisplay(obuf);
      tphandle->display(obuf);
cout&lt &lt "\n--------------------------------------\n";
      cout<<"\n--------------------------------------\n";
cout&lt &lt obuf.get_buffer()&lt &lt '\n';
      cout<<obuf.get_buffer()<<'\n';
obuf.reset_buffer();
      obuf.reset_buffer();
cout&lt &lt "\n------- Press any key for more -------\n" ;
      cout<<"\n------- Press any key for more -------\n" ;
getch(); //let the user control the flow
      getch();   //let the user control the flow
}; //end while
    };       //end while


tpname=db.getnextsubtype();    //get next typename
  tpname=db.getnextsubtype();    //get next typename
}   //end while
  }           //end while


}   //end function
}           //end function


void select_type(database db){
void select_type(database db){
Line 1,508: Line 887:
ODB_TYPE utp;
ODB_TYPE utp;
tpname=db.getallsubtypes();
tpname=db.getallsubtypes();
while (tpname!=NULL){
  while (tpname!=NULL){
cout&lt &lt tpname&lt &lt '\n';
  cout<<tpname<<'\n';
tpname=db.getnextsubtype();
  tpname=db.getnextsubtype();
}
  }
cout&lt &lt "Enter name of the type whose extent to browse\n";
  cout<<"Enter name of the type whose extent to browse\n";
cin&gt&gtusertpnm;
  cin>>usertpnm;
utp=db.gettypenamed(usertpnm);
  utp=db.gettypenamed(usertpnm);
if (utp==NULL) cout&lt &lt "\nThere no type named "&lt &lt usertpnm&lt &lt " in the database\n";
  if (utp==NULL) cout<<"\nThere no type named "<<usertpnm<<" in the database\n";
else browse_extent(utp,db);
  else browse_extent(utp,db);
}
}


Line 1,531: Line 910:
char val[16]="Janitors";
char val[16]="Janitors";


//Open a stream over the department type with the condition that
  //Open a stream over the department type with the condition that
//the Name attribute must have Janitors as value
  //the Name attribute must have Janitors as value
q_stream=My_DB.open_stream(typenm,propnm,val);
  q_stream=My_DB.open_stream(typenm,propnm,val);


//The stream may not be opened if so it is best to check before use.
  //The stream may not be opened if so it is best to check before use.
if (q_stream==NULL) {
  if (q_stream==NULL) {
cout&lt &lt "\nError when opening stream bla bla bla...\n";
    cout<<"\nError when opening stream bla bla bla...\n";
return;
    return;
};
    };


//The stream was opened allright. In the remaining we only consider the
  //The stream was opened allright. In the remaining we only consider the
//first found department named Janitors, thus we do not assume the name
  //first found department named Janitors, thus we do not assume the name
//to be a key to departments
  //to be a key to departments


o=My_DB.get(q_stream);
  o=My_DB.get(q_stream);


//If o==NULL yhen there were no such department in the database
  //If o==NULL yhen there were no such department in the database
if (o==NULL) {
  if (o==NULL) {
cout&lt &lt "No department named "&lt &lt val&lt &lt " exist in the database\n";
    cout<<"No department named "<<val<<" exist in the database\n";
return;
    return;
}
    }


//No longer req'd
  //No longer req'd
My_DB.close_stream(q_stream);
  My_DB.close_stream(q_stream);


//reuse q_stream to open the stream to select all employees at
  //reuse q_stream to open the stream to select all employees at
//the department bound to object reference o
  //the department bound to object reference o


q_stream=My_DB.open_stream("Employee","EmployedAt",o);
  q_stream=My_DB.open_stream("Employee","EmployedAt",o);
   if (q_stream==NULL) {
   if (q_stream==NULL) {
cout&lt &lt "\nError when opening stream over employee\n";
    cout<<"\nError when opening stream over employee\n";
return;
    return;
};
    };
cout&lt &lt "\nThese are the Janitors\n-------------------------\n";
  cout<<"\nThese are the Janitors\n-------------------------\n";
obuf.reset_buffer(); //reset the buffer
  obuf.reset_buffer();     //reset the buffer
//Now, we can start browsing the janitors
  //Now, we can start browsing the janitors
jo=My_DB.get(q_stream);
  jo=My_DB.get(q_stream);
while (jo!=NULL){
  while (jo!=NULL){
jo-&gtdisplay(obuf);
    jo->display(obuf);
cout&lt &lt obuf.get_buffer()&lt &lt '\n';
    cout<<obuf.get_buffer()<<'\n';
obuf.reset_buffer();
    obuf.reset_buffer();
jo=My_DB.get(q_stream);
    jo=My_DB.get(q_stream);
};
    };


//Now, Lets select those janitors who earn more than 10000.
  //Now, Lets select those janitors who earn more than 10000.
//This is accomplished by opening a select stream over the
  //This is accomplished by opening a select stream over the
//query stream which selects all janitors.
  //query stream which selects all janitors.


//First, reset the stream
  //First, reset the stream
My_DB.reset_stream(q_stream);
  My_DB.reset_stream(q_stream);
//secondly, open a select stream over q_stream with the
  //secondly, open a select stream over q_stream with the
//condition that the salary should be greater than 10000
  //condition that the salary should be greater than 10000
s_stream=My_DB.select(q_stream,"Salary","&gt",(ODB_INT)10000);
  s_stream=My_DB.select(q_stream,"Salary",">",(ODB_INT)10000);


cout&lt &lt "\n Theese are all the rich janitors\n";
  cout<<"\n Theese are all the rich janitors\n";
obuf.reset_buffer(); //reset the buffer
  obuf.reset_buffer();     //reset the buffer
//Now, we can start browsing the rich janitors
  //Now, we can start browsing the rich janitors
jo=My_DB.get(s_stream);
  jo=My_DB.get(s_stream);
while (jo!=NULL){
  while (jo!=NULL){
jo-&gtdisplay(obuf);
    jo->display(obuf);
cout&lt &lt obuf.get_buffer()&lt &lt '\n';
    cout<<obuf.get_buffer()<<'\n';
obuf.reset_buffer();
    obuf.reset_buffer();
jo=My_DB.get(s_stream);
    jo=My_DB.get(s_stream);
};
    };


My_DB.close_stream(s_stream);}
  My_DB.close_stream(s_stream);}


  void demonstrate_index(database &My_DB){
  void demonstrate_index(database &My_DB){
Line 1,611: Line 990:
ODB_QSTREAM q_stream;
ODB_QSTREAM q_stream;


My_DB.set_tablesize(3000); //3K table size
  My_DB.set_tablesize(3000); //3K table size


//Create a type named testtype
  //Create a type named testtype
My_DB.create_type("Testtype");
  My_DB.create_type("Testtype");


//Create an integer valued property named Int_prop
  //Create an integer valued property named Int_prop
My_DB.add_property("Testtype","Int_prop",_INT_);
  My_DB.add_property("Testtype","Int_prop",_INT_);


//Create 5000 objects and assign random values to
  //Create 5000 objects and assign random values to
//the attribute named Int_prop for each object.
  //the attribute named Int_prop for each object.
cout&lt &lt "\nCreating 50000 objects ...";
  cout<<"\nCreating 50000 objects ...";
i=0;
  i=0;
while (i++&lt 50000){
  while (i++<50000){


//Create an object and assign o to reference it
    //Create an object and assign o to reference it
o=My_DB.create_instance("Testtype");
    o=My_DB.create_instance("Testtype");


//generate a random number in [0..4000]
    //generate a random number in [0..4000]
v=rand()%4000;
    v=rand()%4000;


//set the property named Int_prop of the object o
    //set the property named Int_prop of the object o
//to v
    //to v
o-&gtsetproperty_value("Int_prop",(ODB_INT)v);
    o->setproperty_value("Int_prop",(ODB_INT)v);
};
  };
cout&lt &lt "Done\n";
  cout<<"Done\n";


//Now, lets find all objects with Int_prop = 2345
  //Now, lets find all objects with Int_prop = 2345
//the probability that this number is less than 1
  //the probability that this number is less than 1
//Thus, it may be the case that no object exists
  //Thus, it may be the case that no object exists
//with that property value. To make sure there is
  //with that property value. To make sure there is
//atleast one such object we simply assign an object
  //atleast one such object we simply assign an object
//that value for the property.
  //that value for the property.


//To make sure we do this right we first find out the
  //To make sure we do this right we first find out the
//OID of the last object. Then we subtract a number less
  //OID of the last object. Then we subtract a number less
//than 50000 from the OID. We now have an OID to one of
  //than 50000 from the OID. We now have an OID to one of
//the 50000 object we just generated. Retrieve it and
  //the 50000 object we just generated. Retrieve it and
//set the value.
  //set the value.
i=0;
  i=0;
My_DB.getproperty_value(o,"OID", &(ODB_INT)i);
  My_DB.getproperty_value(o,"OID", &(ODB_INT)i);
i=i-rand()%45000;
  i=i-rand()%45000;
o=My_DB.getobjectwithoid("Testtype",i);
  o=My_DB.getobjectwithoid("Testtype",i);
o-&gtsetproperty_value("Int_prop",(ODB_INT)1234);
  o->setproperty_value("Int_prop",(ODB_INT)1234);


//Now that we know there is an object with 1234 as
  //Now that we know there is an object with 1234 as
//value for the Int_prop property we can start to
  //value for the Int_prop property we can start to
//search for it.
  //search for it.
obuf.reset_buffer();
  obuf.reset_buffer();
q_stream=My_DB.open_stream("Testtype","Int_prop","=",(ODB_INT)1234);
  q_stream=My_DB.open_stream("Testtype","Int_prop","=",(ODB_INT)1234);


//All set for test search. Start timer and go for it
  //All set for test search. Start timer and go for it
ftime(&t1);
  ftime(&t1);
o=My_DB.get(q_stream);
  o=My_DB.get(q_stream);
while (o!=NULL){
  while (o!=NULL){
o-&gtdisplay(obuf);
    o->display(obuf);
cout&lt &lt obuf.get_buffer()&lt &lt '\n';
    cout<<obuf.get_buffer()<<'\n';
obuf.reset_buffer();
    obuf.reset_buffer();
o=My_DB.get(q_stream);
    o=My_DB.get(q_stream);
};
    };


//All objects found, stop timer and check time
  //All objects found, stop timer and check time
ftime(&t2);
  ftime(&t2);
elapsed_time=(t2.time*1000+t2.millitm)-(t1.time*1000+t1.millitm);
  elapsed_time=(t2.time*1000+t2.millitm)-(t1.time*1000+t1.millitm);
cout&lt &lt "\The search took "&lt &lt elapsed_time&lt &lt "millisec without index";
  cout<<"\The search took "<<elapsed_time<<"millisec without index";


//Now, build an index and redo the whole procedure.
  //Now, build an index and redo the whole procedure.
cout&lt &lt "\nBuilding index ...";
  cout<<"\nBuilding index ...";
My_DB.create_index("Testtype", "Int_prop");
  My_DB.create_index("Testtype", "Int_prop");
cout&lt &lt "Done\n";
  cout<<"Done\n";


//reset the stream
  //reset the stream
My_DB.reset_stream(q_stream);
  My_DB.reset_stream(q_stream);
obuf.reset_buffer();
  obuf.reset_buffer();
ftime(&t1);
  ftime(&t1);
o=My_DB.get(q_stream);
  o=My_DB.get(q_stream);
while (o!=NULL){
  while (o!=NULL){
o-&gtdisplay(obuf);
    o->display(obuf);
cout&lt &lt obuf.get_buffer()&lt &lt '\n';
    cout<<obuf.get_buffer()<<'\n';
obuf.reset_buffer();
    obuf.reset_buffer();
o=My_DB.get(q_stream);
    o=My_DB.get(q_stream);
};
    };
ftime(&t2);
  ftime(&t2);
elapsed_time=(t2.time*1000+t2.millitm)-(t1.time*1000+t1.millitm);
  elapsed_time=(t2.time*1000+t2.millitm)-(t1.time*1000+t1.millitm);
cout&lt &lt "\The search took "&lt &lt elapsed_time&lt &lt "millisec with index";
  cout<<"\The search took "<<elapsed_time<<"millisec with index";


//-------------------------------------------------
  //-------------------------------------------------
//--                I M P O R T A N T            --
  //--                I M P O R T A N T            --
//-------------------------------------------------
  //-------------------------------------------------
//-- Since the application now no longer need the--
  //-- Since the application now no longer need the--
//-- 50000objects etc we now deallocate them    --
  //-- 50000objects etc we now deallocate them    --
//-- There are several ways of doing this.      --
  //-- There are several ways of doing this.      --
//-- 1: Open a stream over all Testtype objects  --
  //-- 1: Open a stream over all Testtype objects  --
//-- and deallocate them one by one.            --
  //-- and deallocate them one by one.            --
//-- 2: Since the type named Testtype is not    --
  //-- 2: Since the type named Testtype is not    --
//-- wanted anymore we simply delete the type    --
  //-- wanted anymore we simply delete the type    --
//-- and let ODB do the hard work of deallocating--
  //-- and let ODB do the hard work of deallocating--
//-- everything, e.g. objects indexes ......    --
  //-- everything, e.g. objects indexes ......    --
//--                                            --
  //--                                            --
//-- And also, we allocated a stream local to    --
  //-- And also, we allocated a stream local to    --
//-- to this function. This function must be    --
  //-- to this function. This function must be    --
//-- deallocated to prevent memory leak.        --
  //-- deallocated to prevent memory leak.        --
//-------------------------------------------------
  //-------------------------------------------------


My_DB.delete_type("Testtype");
  My_DB.delete_type("Testtype");
My_DB.close_stream(q_stream);
  My_DB.close_stream(q_stream);


//It is now ok to exit this function
  //It is now ok to exit this function


}
}
Line 1,724: Line 1,103:


int retval;
int retval;
retval=My_DB.load_database("Compdb.odb");
  retval=My_DB.load_database("Compdb.odb");
if (retval&lt 0) cout&lt &lt "\n Failed to load company database ";
  if (retval<0) cout<<"\n Failed to load company database ";
else {
  else {
cout&lt &lt "\n The company database is loaded";
    cout<<"\n The company database is loaded";
My_DB.display(obuf);
    My_DB.display(obuf);
cout&lt &lt "\n"&lt &lt obuf.get_buffer();
    cout<<"\n"<<obuf.get_buffer();
obuf.reset_buffer();
    obuf.reset_buffer();
};
    };


//display the schema of the loaded database
  //display the schema of the loaded database
display_schema(My_DB);
  display_schema(My_DB);


//let the user select and browse a type extent
  //let the user select and browse a type extent
select_type(My_DB);
  select_type(My_DB);


//demonstrate the search interface
  //demonstrate the search interface
find_all_janitors(My_DB);
  find_all_janitors(My_DB);


//show the benefit of indexing when the database
  //show the benefit of indexing when the database
//is large
  //is large
demonstrate_index(My_DB);
  demonstrate_index(My_DB);


cout&lt &lt "\nODB Demo is now finished\n";
  cout<<"\nODB Demo is now finished\n";
return 1;
  return 1;
};
};
</pre>
</pre>
----
----
[[Category:Guides]]
[[Category:Guides]]

Revision as of 16:09, 7 May 2017

By Staffan Flodin

ODB for WARP

An Object-Oriented Database V. 0.1

1.0 Introduction

The purpose of this system is to provide system developers an easy to use database facility with powerful query capabilities. ODB (ObjectDataBase) consists of a graphical maintenance interface where databases may be defined and updated and a database engine.

The database engine is accessed by applicaions through a simple and consistent application programming interface, API. ODB is a light-weight single- user main-memory object-oriented database.

  • ODB supports, subtyping, inheritance, complex objects and all objects have a unique identity hence ODB is object-oriented.
  • It always keeps the data in main memory, thus it is primarily intended for applications with moderately sized data requirements. It uses secondary storage only for storing the database between sessions.
  • It is light-weight because some of the traditional database facilities such as locking and logging does not exist in ODB. The reason is that it is intended to be used as a fast and easy to use storage and search module in any application where traditionally the application developer programmed the data storage, e.g. as sequential file scans etc.
  • It differs from other C++ based object-oriented databases in that it is dynamic in nature. Hence your application is linked and compiled once and the database schema is created or changed dynamically without any recompilation of the application.

2.0 ODB system

The ODB-system consists of two parts: A graphical user interface for creation and maintenance of the database and a C++ API for accessing the database engine from another application.

System Overview Figure 1. System Overview

The graphical user interface faciliates creation, update and deletion of types, properties and objects in an easy manner. The interface also supports loading, saving and creation of ODB-databases.

The C++ API gives any application access to a set of high-level easy to use interface methods to the database engine. The API gives the application access to the same functionality which is available in the user interface. In addition, the API also provides a powerful stream based query interface to the database.

2.1 Using ODB for Warp

The on-line version of ODB contains all functionality of the system, i.e. it is not a special shareware version. If you use ODB and find it useful the cost is US$99. Send an e-mail to sflodin@ozemail.com.au for further details.

2.2 Files

ODB consists of the following files:

  • pmstore.exe The ODB creation and maintenance interface.
  • ODB.h ODB C++ header file.
  • ODB.libThe ODB library.
  • Compdb.odb Sample ODB database files.
  • Demo.cpp Sample ODB application source code.

2.3 System Requirements

The system is intended to run under OS/2 Warp Version 3 or higher. Any other requirements does not exist.

3.0 System overview of ODB

An ODB-database is a collection of types with properties and objects which are instances of types. ODB-types are instances of the system class type and ODB-objects are instances of the object system class [[]]

The types are organized in a subtype-supertype hierarchy where subtypes inherit all properties defined or inherited to its supertype. In addition to the inherited properties new properties can be created. A property is a name which denotes values of objects. Properties can denote values of the following ODB-database types:

  • Integer
  • Real
  • Charstring
  • Object reference
  • Collection of Integer
  • Collection of Real
  • Collection of Charstring
  • Collection of object reference.

ODB-objects are instances of ODB-types where all properties defined or inherited to the type can be assigned values for the object.

Objects can be retreived by their property values. And the extent of a type, i.e. the objects that belongs to a particular type and all subtypes to that type, can be queried through a stream based query interface. To facilitate fast look-up of objects indexes can be built over some or all of the properties of a type.

The database can easily be updated or altered in the following ways:

  • Type creation
  • Property creation
  • Object creation
  • Index creation
  • Type deletion
  • Property deletion
  • Object deletion
  • Index deletion
  • Object modification.

Integrity constraints will be maintained by the system when types or objects are deleted or modified. All properties and indexes are inherited automatically to any newly created subtype of a particular type.

4.0 Database operations

In this section the ODB-database engine methods in the API are described and exemplified as C++ examples. Throughout the examples a database object named My_DB is used to exemplify the ODB-API.

4.1 Database creation

The ODB Database Object

The ODB-API contains a C++ class named database see Figure 3. The database object in your application is a particular important object. It is through the database object which all accesses to the ODB-database and all calls to the database engine go.

The database object contains the database name, an ODB-type named Usertypes, an object identifier (OID) generator and a string table.

The name of the database object is assigned to it at creation or when the database is loaded from file. The type Usertypes is the root of the user defined type tree. This type can not be given user defined properties and objects can not be created from it. Thus, if a type is created without a specified supertype the type named Usertypes will become its supertype by default.

The string table is a character string repository. All strings used in the database are stored in this table. This facilitates fast look-up of string valued properties. There are som cases where the application must access the string table but in most cases ODB handles this for the application.

The OID generator generates OIDs for all new objects or types in the database. The OID can not be changed by the application and the application must never reference object by their OID's as the OIDs of the objects may change when the database is loaded again.

If your application only needs one database this database is declared in your application as:

database My_DB("DBName");

This statement declares the object My_DB to be an ODB-database object with name DBname. All applications using ODB must have a database object. There is no limit set by ODBto the number of databases a single application can use.

If several databases are required in the application these can be created dynamically by using the C++ new-operator as:

new database("OneMoreDB");

The database can be saved and restored from file as:

database My_DB;
My_DB.load_database("Db1.odb");
My.DB.save_database("Db1.odb");

In above statements the database object My_DB is first loaded and then saved with file name Db1.odb.

4.2 Type creation

An ODB-type serve as a template for object creation. Properties and indexes are defined on types. A type can be declared as a subtype to any other type previously defined. If no supertype is declared then the new type will become subtype to the type named Usertypes in the database object. ODB-types are instances of the system type named type, see Figure 2.

Figure 2

An ODB-type inherits all properties defined to its supertype. Any indexes defined to the supertype will also be defined to the new type. Types are created by the ODB-database object as:

My_DB.create_type("Person");
My_DB.create_type("Employee","Person");

Here a type named Person is first created. A supertype has not been specified thus the type named Person will be a subtype of the type Usertypes in the database object My_DB. In the next example a type named Employee is created as a subtype to the type named Person.

If several databases are active in some application there may be types eith identical names in the different databases. Since references to types go through the database object there is never any ambiguity when referencing types by their names.

The return value from the create_type-statement is a reference to the new type or the NULL-reference if the type creation failed. The following example declares a type reference that can be bound to the return value from the create_type statement:

ODB_TYPE My_NewType;
My_NewType=My_DB.create_type("Person");

The return value need to be assigned a reference. The type is inserted in the type hierarchy of the database and can be referenced by its name whenever required.

4.3 Property Creation

Properties are defined to types and for the objects of that type the properties can be given values. Properties are typed where the following types together with their identifiers are supported:

Type Type tag
Integer _INT_
Real _REAL_
Charstring _CHAR_
Object reference _OBJECT_
Collection of Integer _INT_COLLECTION_
Collection of Real _REAL_COLLECTION_
Collection of Charstring _CHAR_COLLECTION_
Collection of object reference. _OBJECT_COLLECTION_

To create an integer valued property named salary to the type named employee in the database My_DB we write:

My_DB.add_property("Employee","Salary",_INT_);

Analogously, an object valued property named Boss is created as:

My_DB.add_property("Employee","Boss",_OBJECT_);

The return value is either less than or greater than zero if the operation failed or succeeded, respectively.

The add_property-statement can be executed any time and all subtypes to the target type that may exist will also be given the property. All instances to the types which has been given a new property will also be given the property. The value however will be undefined and it is up to the application that the value is not referenced prior to being assigned a value.

4.4 Object creation

Objects are created as instances to a particular type. When objects are created these are given all the properties defined to the type. The values of the properties are undefined and must be assigned values prior to being referenced.

To create an instances of the type named Person in the database My_DB we write:

My_DB.create_instance("Person");

The return value is a reference to the newly created object or the NULL reference. To declare a variable which can be assigned the result of the create_instance operation we write:

ODB_REF My_NewObj;
My_NewObj=My_DB.create_instance("Person");

The return value may be discarded but then there is a problem in referencing the object at a later stage. Perhaps the best procedure is to create an object and keep a reference to it. This reference can then be used when the properties of the objects are assigned values. When values are assigned to its properties the reference may be discarded and the object can at a later stage be referenced through its attribute values. This is easily done through the query interface described later in this manual.

4.5 Object modification

Object modification means changing the values of its properties. This is done by using sending the message set_propertyvalue to the database object as:

My_DB.setproperty_value(My_NewObj,"Salary",(ODB_INT)53200);

Where My_NewObj is an ODB-object declared and created e.g as:

ODB_REF My_NewObj;
My_NewObj=My_DB.create_instance("Employee");

The object is given the value 53200 for the property named Salary. Note that constants may require type casting for disambiguation purposes. In this example the constant 53200 is casted to the type ODB_INT. The result from the database method setproperty_value is greater than or less than zero if the operation was successful or failed, respectively.

When property values are modified all indexes are automatically updated to consider the new value for the property.

4.6 Index creation

To make retrievals through some property fast, indexes can be created. For example if a frequent search in the database is on a particular attribute e.g. Salary then an index should be created on that attribute to facilitate fast access to the objects with a particular value for the attribute named Salary.

Indexes are created from the database object through the create_index method. The following example creates an index on the Salary property of the type Employee.

My_DB.create_index("Employee","Salary");

Note that if an index is created to some attribute of a particular type the index will also be created to that attribute in all subtypes. In above example any subtype to Employee will get an index on the salary property. It does not matter if the index is created before or after the subtypes are created. It does not matter if instances to the types the index is created over has been created. All existing instances will be entered into the index structures automatically by ODB.

4.7 Type deletion

Types can be deleted from the database dynamically. This is a particularly costly operation. When a type is deleted so are the subtypes to the type as well as all objects that are instances to the type or to any subtype of the type. Hence, A type deletion is a recursively applied down the type tree rooted at the specified type to delete.

Types are deleted by the database object by sending the delete_type message. The result of the operation is either greater than or less than zero if the operation was successful or failed, respectively. To delete the type Person from the database My_DB we write:

My_DB.delete_type("Person");

This will delete the type named Person, all instances of that type and then recursively delete all subtypes to the Person type. This operation can be costly to perform when there are a lot of objects and types that has to be deleted and their memory freed.

Note especially that any references maintained by the application to types or objects which have been deleted must be reset by the application. Dereferncing such a reference is an undefined operation.

4.8 Property deletion

Single properties can be deleted from types. When a property is deleted from some type it is deleted from all subtypes as well.

When a property is deleted from a type the property is also deleted from all object that are instances to that type, i.e. the extent of the type.

Properties are deleted from types by sending the delete_property message to the database object. To delete the property named Salary from the type named Employee in the database My_DB we write:

My_DB.delete_propery("Employee","Salary");

Note that if the property that is to be deleted is inherited to the specified type that property can not be deleted as that would leave the database schema in an inconsistent state. To properly delete a property it must be deleted from the most general type it is defined for. (A supertype is more general than its subtypes)

This is because the object-oriented model states that any operation op applicable to a type t is also applicable to all subtypes of the type t. Hence, deleting an inherited property would violate this constraint.

4.9 Delete Object

Objects may be deleted. When an object is deleted the system automatically deletes all references to that object from other objects in the database. Any references kept by the application to the deleted object must be reset by the application itself.

Objects are deleted by sending the delete_object message to the database. When deleting an object the database object must be provided a handle to the object that are subject of deletion.

A handle can be obtained through the query interface or other methods that returns object handles.

To exemplify, consider the following example where a handle obj is declared. This handle is then assigned an object through the getobjectwithoid method. This object is then deleted by the database object:

//Declare object handle,
//database and an integer

ODB_REF obj;
database My_DB("ExDB");
ODB_INT i=0;

My_DB.create_type("Person");

//Create 500 objects
while(i++ &lt 500) My_DB.create_object("Person");

//Assign an object to the handle, We can be 
//quite certan there is an object with OID 415 now
obj=My_DB.getobjectwithoid("Person", 415);

//Delete the object
//If there were no object with OID=415
//then obj==NULL but that is all handeled
//by ODB
My_DB.delete_object(obj);

Any other object that reference the deleted object through some attribute will now have that attribute value set to NULL. Note that in this example the object handle obj is outside the control of ODB and must not be used by the application after the deletion. This is up to the application programmer to see to.

4.10 Delete index

Indexes may be deleted by sending the drop_index message to the database object. In the following example the index over Salary for type Employee in database My_DB is deleted:

My_DB.drop_index("Employee","Salary");

The result is greater or less than zero if the operation was successful or failed respectively.

5.0 The query interface

The query interface is a stream based query interface. In a stream based query interface the application opens a stream from which objects can be retrieved one by one until no more objects are returned. Thus, no materialization of the entire set of object that may constitute the result of some query occurs. The basic operations on streams are:

  • Open
  • Get
  • Reset
  • Close

Streams may be opened over any type or over another existing stream. Streams have a state which means that it depends on the previous operations what the result of the get operation will be. As a consequence of this streams may be opened and objects retrieved one by one at arbitrary occasions, the stream will hold its state and return the next object upon request.

Open streams may be reset which means that their state is changed to some initial state. In order to redo a query this is a useful feature that saves the application from having to open and combine some new streams to redo the same query.

Streams which will not be used any more may be closed. Closing a streams means it cannot be reused and any reference to any stream which is closed must not be reused.

There are three different types of streams:

  • Query stream
  • Select stream
  • Semi-join stream

Query streams are opened over types to select all or those object which comply with some condition that can be defined when opening the stream.

Select streams are opened over another stream to select only those objects that satisfy a condition that is defined with the select stream.

Semi-join streams are opened over two other streams. The semi join stream returns those object from the left operand stream for which there exists an object in the right operand stream that satisfy some condition defined over the two streams.

5.1 Opening streams

Streams are opened by sending a message to the database object. The return value is either a reference to the new stream or NULL if the operation was successful or failed, respectively. In this section stream handles and open stream statements are explained.

5.1.1 Stream handles

There are three C++ types that may be used to declare stream handles. A handle, q_strm, to a query_stream is declared as:

ODB_QSTREAM q_strm;

A declaration of a select stream, s_strm, handle is:

ODB_SSTREAM s_strm;

And for a semi-join stream the handle, sj_strm, is declared as:

ODB_SJSTREAM sj_strm;

5.1.2 Opening a query stream

Query streams are opened over some type. This means that the stream accesses the objects that are instances of the type, the type extent, and the instances of its subtypes.

For example, in this figure, if a query stream is opened over the type named Person then all objects of type person and of the type named Employee will be considered as possible objects to output.

A query stream can be opened over a type named Employee as:

My_DB.open_stream("Employee");

This stream will retrieve every object in the extent of type Employee and in the extent of all subtypes to the type named Employee, called the deep extent of type Employee.

A stream can be opened which only retrieves object that satisfy some condition defined with the stream. Assume we want to retrieve all persons aged 18, i.e. all objects in the deep extent of the type named Person that have the value 18 for its age property. This is done as:

My_DB.open_stream("Person","Age", "=",(ODB_INT)18);

In this statement we used the =-operator to compare the values of the objects with the value defined with the stream. For integer and real there are additional comparison operators that can be used. These are:

  • =
  • <
  • <=
  • >
  • >=
  • !=

For object reference, charstring and collection valued properties only equality is meaningful thus when opening streams with condition on such properties the operator can be excluded.

For collections the equality operator is overloaded. If one operand is a collection and the other is a non collection type then the equality means the member operator. If on the other hand both operands are sets then the equality operator means that the same members should be found in both sets, i.e. A=B means that if an object, o, is member of set A then o must also be a member of the set B and vice verca.

To exemplify how streams are opened over character strings, collections and object reference valued properties consider this example:

A stream is to be opened over the Employee type where all Employees which have the name "Bob Smith" should be returned from the stream. This is written as:

My_DB.open_stream("Employee","Name","Bob Smith");

Streams with restrictions over collections and object reference valued properties are opened analogously.

5.1.3 Opening a select stream

Select streams are opened over existing streams to add additional conditions on the objects that are sought. If for example, all Person objects with age greater than 18 and whose name is "John Doe" are interesting we write:

ODB_QSTREAM s1;
ODB_SSTREAM s2;
//Open s1
s1=My_DB.open_stream("Person","Age","&gt =",(ODB_INT)18);

Now, s1 retrieves all objects of type Person with age greater than or equal to 18. The additional name condition can be added by opening a select_stream over this stream as:

s2=My_DB.select(s1,"Name","John Doe");

Here, a stream is opened over the existing stream, s1, and a condition on the name attribute is defined and the result is bound to the stream variable s2. The objects are retrieved from s2 by the get-method.

5.1.4 Open a semi join stream

The semi-join operation is a variant of the traditional join operator in the relational algebra. The semi-join stream is opened over two streams where attribute values from objects in one stream are compared with attribute values of the objects from the other stream. Only objects from one stream is in the result of the stream. The latter is the difference from a traditional join operator where the result is a tuple of values, (See any textbook that covers the relational algebra and relational databases).

To exemplify, assume we have two types, Pet and Person where both have a character string valued name-attribute. If we would like to retrieve those persons that has the same name as a pet has we write:

ODB_QSTREAM qst1,qst2;
ODB_SJSTREAM sjs;

//Open qst1 over persons and
//qst2 over pets
qst1=My_DB.open_stream("Person");
qst2=My_DB.open_stream("Pet");

//Open the semi join stream
sjs=My_DB.semi_join(qst1,"Name",qst2,"Name");

The objects returned will be from the stream qst1 that has the same name-value as any object of type pet.

5.2 Retrieving objects from streams

Given an open stream, objects can be retrieved through that stream by sending the get message to the database object with the stream as additional argument with the message. The result is either an object or NULL depending on the success of the operation.

The following example opens a stream and retrieves all objects that satisfies the condition that their value for the age property must be greater then 65.

ODB_QSTREAM qst;
ODB_REF obj;

//Open the stream
qst=My_DB.open_stream("Person","Age" ,"&gt =",65);
if (qst==NULL) {
	//Do some error recovery
	//qst may be the NULL reference if 
	//the open statement failed
	}
else {
	//Get the first object and 
	//then the rest of the objects
	obj=My_DB.get(qst);
	while(obj!=NULL){
		//do something with obj
		obj=My_DB.get(qst);
		}
} //end else

This example shows the standard way of iterating through a stream. When all objects are retrieved (a NULL reference is returned) the stream can either be closed or reset to its initial state.

5.3 Stream reset

When an opened stream has reached its end or if we by some other reason do not want to retrieve any more objects from it the stream can be reset. When a stream is reset it is set to the same state as if it was just opened.

Streams that will be used again to query the data should be reset instead of closed and recreated. A stream is reset by sending the reset_stream message to the database object as:

My_DB.reset_stream(qst);

Where qst is an object of type query, select or semi join stream.

5.4 Stream close

Streams are closed when it is no longer to be reused again. For composite streams, closing the outer most stream will cause all streams it uses to be closed as well. In Figure4 this means that closing the stream s2 will also close the stream s1.

Figure 4

In the same figure, closing s1 only will leave s2 in an undefined state. Streams are closed by sending the close_stream message to the database object as:

ODB_QSTREAM qst;
// Open qst, and use it
My_DB.close_stream(qst);

After a stream has been closed th application must not try to use the reference unless the reference is assigned another stream.

6.0 Collections

The datatype collection is somewhat more complex than the other datatypes supported by ODB. A collection is similar to a set with the difference that a collection can contain several equal objects or values.

6.1 Creating collections

Collections are created by sending the create_collection message to the database object. On successful creation a handle to an empty collection is returned. This handle must ALWAYS be assigned a reference owned by the application since the collection otherwise will be unaccessible to the application. The application owned reference can be released when the collection is referenced from elswhere, e.g. through an attribute of some object.

A collection which may contain references to other objects is created and assigned a collection handle ch as:

ODB_SET ch;
ch=My_DB.create_collection(_OBJECT_);

Valid type identifiers are to the create_collection method are:

  • _INT_
  • _REAL_
  • _CHARSTRING_
  • _OBJECT_

Thus, it is not possible to declare a collection to contain other collections.

6.2 Adding elements to a collection

When a collection has been created elements may be inserted into the collection. ODB supports two way of doing this; by sending the collection_insert message to the database object or by sending the insert message directly to the collection object.

In the following example a collection of character strings are created and bound to the handle coll. Then two elements are inserted into the set by collection_insert and by insert.

ODB_SET coll;

//Create a collection 
coll=My_DB.create_collection(_CHAR_);
My_DB.collection_insert(coll,"Object");
coll->insert("Orientation");

The difference with these two ways is that by sending the collection_insert message to the database objects all necessary checks are performed before insertion. When the insert message is sent to a collection the application must ensure that the collection is of the correct type and that the collection handle is initialized properly.

The return value from collection_insert is:

  • > 0 upon succesful insert
  • == -1 for improper handle
  • ==-2 if the collection was of another type than the element to insert

6.3 Delete element from collection

Elements may be deleted from a collection by either sending the delete_element message to the set object or by sending the delete_from_collection message to the database object. In the following example objects are deleted from the set coll.

ODB_SET coll;

//Create collection
coll=My_DB.create_collection(_INT_);

//Insert into collection using both
//insertion methods
My_DB.collection_insert(coll,2);
coll->insert(3);

//remove from collection
My_DB.delete_from_collection(coll,3);
coll->delete(2);

Using the delete_from_collection message to the database object is a safer way since all necessary checks are performed before deketion of any element. However, sending the delete message directly to the collection object is slightly faster but then the application must ensure that the handle is initialized etc.

Note especially that before using the delete message directly to the collection object to delete an ODB_CHAR the characterstring must be substituted with the characterstring stored in the string table. The same is true for inserting characterstrings directly into a collection The following example illustrates the correct procedure:

ODB_REF cstr, cstr2;
ODB_SET coll;
coll=My_DB.create_collection(_CHAR_);

//Get a handle to the stored ODB string
//Don't bother if you know it does not
//exist, ODB takes all reqd actions
cstr=My_DB.getstring("ODB");

//Insert it into the collection
My_DB.collection_insert(coll,cstr);

//The following will not delete 
//ODB from the collection
coll.delete("ODB");

//The following will delete 
// "ODB" from the collection
cstr2=My_DB.getstring("ODB");
coll.delete(cstr2);

6.4 Delete collection

Collections are deleted by either sending the delete_collection message to the database object or the delete_set message to the collection object.

7.0 Datatypes

There are a number of ODB specific datatypes and type identifiers that are used to interface ODB in the API.

7.1 C++ Datatypes

There are a number of C++ datatypes and classes defined in the API these are:

C++ datatype Type of data
ODB_INT integer
ODB_REAL real
ODB_OID oid
ODB_CHAR charstring
ODB_REF object handle
ODB_SET collection handle
ODB_QSTREAM query stream handle
ODB_SSTREAM select stream handle
ODB_SJSTREAM semi-join stream handle
ODB_DB database object
ODB_DB_HANDLE odb database handle
ODB_TYPE odb type handle

These types are used by the application using ODB to declare interface variables to ODB.

The C++ classes the API provides are:

C++ Class Ref. decl Description
Database ODB_DB The ODB database class
Type ODB_TYPE(ref.) The ODB type class
Object ODB_REF (ref.) The ODB object class
Outputbuffer N/A The ODB outputbuffer class

7.2 Type identifiers

There are a number of type identifiers defined in the API which are used to specify to ODB which type to consider. These are:

  • _INT_
  • _REAL_
  • _CHAR_
  • _OBJECT_
  • _OBJECT_COLLECTION_
  • _INT_COLLECTION_
  • _REAL_COLLECTION_
  • _CHAR_COLLECTION_

These identifiers are used to identify objects of C++ datatypes as:

Type Id C++ Type
_INT_ ODB_INT
_REAL_ ODB_REAL
_CHAR_ ODB_CHAR
_OBJECT_ ODB_REF
_OBJECT_COLLECTION_ ODB_SET
_INT_COLLECTION_ ODB_SET
_REAL_COLLECTION_ ODB_SET
_CHAR_COLLECTION_ ODB_SET

Table1. Type and Id mapping

Note that the C++ datatype ODB_SET is used to denote all collection types. The type identifiers are used to denote the result type of properties when these are created and to denote which type of elements a collection can contain.

8.0 More interface methods

In addition to database creation, maintenance and querying there a methods to print objects, retrieve object and types etc. these are described here.

8.1 The string table

Each database object contains a stringtable where all strings used by the datbase are stored. Before some operatons involving strings can be executed the application must get a handle to the string maintained by the database otherwise equality tests etc. will not function properly. In most cases ODB does this for you.

A string handle to a prticular string is obtained by sending the getstring message to the database object. In the following example a handle is obtained to the string "ODB Rules".

ODB_CHAR str;
str=My_DB.getstring("ODB Rules");

The following example demonstrates the benefit if the string table:

ODB_CHAR s1, s2;
s1=My_DB.getstring("ODB");
s2=My_DB.getstring("ODB");
if (s1==s2) cout &lt&lt "\nODB Rules\n";
else cout &lt&lt "\nYou cheated\n");

The output will of course be: ODB Rules

8.2 Printing ODB objects

All objects, types and databases may be printed by sending the display message to the object. To support any print-method the application may want to utilize all display methods of all ODB objects print in a buffer which is provided by the ODBAPI.

The display message can be sent to any OBD object with a display buffer as argument as:

displaybuffer buf;
database odb;
ODB_TYPE tp;

//Create a type
tp=odb.create_type("Person");

//print type to buffer
tp->display(buf);

//print buffer to stdout
cout &lt&lt buf.get_buffer();

//reset buffer before used
buf.reset_buffer();

8.2.1 Display buffer

OBD provides a class named outputbuffer which all display methods use. A buffer, Disp_Buf is declared in the application as:

outputbuffer DispBuf;

The buffer can be retrieved by sending the get_buffer message as:

Disp_Buf.getbuffer();

The return value is a reference to a character buffer as:

ODB_CHAR BufPtr
BufPtr=Disp_Buf.getbuffer();

The reference to the buffer can then be used by the application to print the content of the buffer on a widget in a GUI or on a tex display using cout etc.as e.g.:

ODB_CHAR BufPtr
BufPtr=Disp_Buf.getbuffer();
cout << BufPtr;

When the buffer has been printed or to prevent it from overflowing it can be emptied by sending the reset_buffer message to the buffer object as:

DispBuf.reset_buffer();

The buffer size is 1k and if there is not enough free space in it when used by some object, the display output from the object will be lost.

8.3 Getting a type handle

If a handle is required to a type for e.g. printing the gettypenamed message to the database object as:

ODB_TYPE tp;
tp=My_DB.gettypenemrd("Person");

In this example a handle is returned to the type named Person. If no such type exists the NULL handle is returned.

8.4 Getting an object handle

Object handles can retreived by using the query interface, see Section 5.0, or by sending the getobjectwithoid message to the database object as:

ODB_REF o;
o=My_DB.getobjectwithoid("Usertypes",45);

In this example the object with oid equal 45 is returned, if no such object exist a NULL handle is returned. Note especially that there is no guarantee that objects will get the same oid when reloaded from file, in fact it is very likely that it will get another oid.

8.5 Traversing all subtypes of a type

If the application for some reason wants to traverse the type tree of the database the application then first sends the getallsubtypes message to the database object to get the first subtype. All subsequent subtypes are retreived one by one by sending the getnextsubtype message to the database object. The following exaple shows how all types in the database are printed on stdout:

ODB_CHAR tp; //type names
ODB_TYPE tph; //type handle
database My_DB;

My_DB.load_database("Compdb.odb");
tp=My_DB.getallsubtypes();
while(tp!=NULL){
	cout << tp << `\n';
	tp=My_DB.getnextsubtype();
	//Type handles to the subtypes
	//can be obtained as
	tph=My_DB.gettypenamed(tp);
}

8.6 Traversing direct subtypes of a type

A direct subtype of a type, t, is a type whose supertype is the type t. The direct subtypes are traversed by first sending the getsubtypes message to the database object and then sending the getsubtype message to get the other subtype names, one by one as:

char *tp; //type names
ODB_TYPE tph; //type handle
database My_DB;

My_DB.load_database("Compdb.odb");
tp=My_DB.getsubtypes("Person");
while(tp!=NULL){
	cout &lt&lt tp &lt&lt `\n';
	tp=My_DB.getsubtype("Person");
	//Type handles to the subtypes
	//can be obtained as
	tph=My_DB.gettypenamed(tp);
}

In this example all direct subtypes of the type named Person are retreived.

8.7 Getting the supertype to a type

The supertype name of a particular type can be retreived by using the getsupertype message to the database object.

In this example the supertypename of the type named Employee is retreived and then used to get a handle to the supertype to the type named Employee.

ODB_CHAR stpnm;
ODB_TYPE stp;
stpnm=My_DB.getsupertype("Employee");
stp=My_DB.gettypenamed(stpnm);

8.8 Getting all properties of a type

The names of all properties of a type can be retreived b y first sending the getfirstprop messageto the database object and then get the rest of the property names by sending the getnextprop message to the database object.

In this example all properties of the type named Employee are printed to stdout.

ODB_CHAR pn; //prop names
database My_DB;

My_DB.load_database("Compdb.odb");
pn=My_DB.getfirstprop("Employee");
while(pn!=NULL){
	cout &lt&lt pn &lt&lt `\n';
	pn=My_DB.getnextprop("Employee");
}

8.9 Getting the type of a property

As described in Section 4.3 all properties are associated with a type where the type describes which objects the property can have as values.

The type of a property can be retreived by sending the getpropertytype message to the database object. The returned value is a type identifier see section 4.3. In the following example the type identifier is retreived from the name property of the Person type.

ODB_TYPE_TAG ttg;
ttg=My_DB.getpropertytype("Person","Name");

8.10 Getting the value of an object property

Each property denotes a value or other object. To retreive the denoted value or object the application sends the getproperty_value message to the database object.

It is important to note that the return value is a code indicating the success of the operation. The value is retreived by sending the reference to the variable the result is intendet to be retreived into. In the next example the value of a set valued property is retreived into the variable coll:

ODB_SET coll;
ODB_REF obj;

//obj is assigned a person object
//and person have a collection 
//valued property named Parents 
//defined to them.

ok=My_DB.getproperty_value(obj,"Parents",&coll);

The return value is:

  • > 0 if the operation succeeded
  • ==-1 if the object reference was NULL
  • ==-2 if the property did not exist
  • ==-3 if the expected result type did not match the resulttype of the property.

Conclusion

This system is intended to support developers in managing moderately sized data in an easy manner. The data can be created and changed outside the application by using a graphical user interface and later loaded into the application easily without any recompilation or other cumbersome procedure.

The system supports the object-oriented data modelling paradigm for the data stored. The data can easily be queried through a high level stream based interface which the API provides.

This is the firs released version of the system and any bug reports or comments are welcomned to me.

A Sample application

To give an idea of how ODB is used the following example code illustrates the interface. This application file is included in the ftp package ODB.ZIP.


#include <iostream.h>
#include <fstream.h>
#include <conio.h>
#include <time.h>
#include <math.h>
#include <stdlib.h>
#include <sys\timeb.h>
#include "odb.h"

outputbuffer obuf=outputbuffer();


void browse_extent(ODB_TYPE tp,database db){
//this function browses through all objects in the
//extent of the specified type tp
ODB_QSTREAM q_stream;
ODB_CHAR tpname;
ODB_REF o;

  if (tp==NULL) return;     //quit if shitty reference

  tpname=tp->getname();     //this method is not described
          //in the manual, it is found
          //in odb.h

  q_stream=db.open_stream(tpname);

  if (q_stream==NULL) return;   //should not happen
          //(they all say that)

  cout<<"Browsing the extent of type "<<tpname<<'\n';
  obuf.reset_buffer();    //reset the buffer

  //follows is the standard structure of iterating through streams
  //at ****** Any Code ***** application specific code are enterd
  //in below example the object bound to o is just printed to stdout
  //-------------------------------------------------
  //----------- Std stream iteration-----------------
  //-------------------------------------------------
  //--          o=db.get(q_stream);                --
  //--          while (o!=NULL){                   --
  //--            ****** Any Code *****            --
  //--            o=db.get(q_stream);              --
  //--          }                                  --
  //-------------------------------------------------
  //----------- End Std stream iteration-------------
  //-------------------------------------------------
  o=db.get(q_stream);
  while (o!=NULL){
    o->display(obuf);
    cout<<obuf.get_buffer()<<'\n';
    obuf.reset_buffer();
    o=db.get(q_stream);
    }

  //-------------------------------------------------
  //--                I M P O R T A N T            --
  //-------------------------------------------------
  //-- The application MUST deallocate the stream  --
  //-- here, otherwise a memory leak would occur as--
  //-- the stream can not be referenced again      --
  //-- Alternatively, declare it as STATIC         --
  //-------------------------------------------------
  db.close_stream(q_stream);
  }


void display_schema(database db){
//For each type in the loaded database schema
//display its information by sending the display message to the
//type object and then for each type display its propperties
ODB_CHAR tpname, propname;
ODB_TYPE tphandle;

  obuf.reset_buffer();  //empty any old crap in the buffer

  tpname=db.getallsubtypes();   //get the first typename

  while (tpname!=NULL){     //loop over all typenames

    tphandle=db.gettypenamed(tpname); //given a type name the handle
              //can be fetched
    if (tphandle!=NULL) {
      //display the type object and all its properties
      tphandle->display(obuf);
      cout<<"\n--------------------------------------\n";
      cout<<obuf.get_buffer()<<'\n';
      obuf.reset_buffer();
      cout<<"\n------- Press any key for more -------\n" ;
      getch();    //let the user control the flow
    };        //end while

  tpname=db.getnextsubtype();     //get next typename
  }            //end while

}            //end function

void select_type(database db){
//In this function the user is provided all usertypes
//in the database and then promted to select one whose
//extent are to be browsed
ODB_CHAR tpname;
char usertpnm[20];
ODB_TYPE utp;
tpname=db.getallsubtypes();
  while (tpname!=NULL){
  cout<<tpname<<'\n';
  tpname=db.getnextsubtype();
  }
  cout<<"Enter name of the type whose extent to browse\n";
  cin>>usertpnm;
  utp=db.gettypenamed(usertpnm);
  if (utp==NULL) cout<<"\nThere no type named "<<usertpnm<<" in the database\n";
  else browse_extent(utp,db);
}

void find_all_janitors(database My_DB){
//In this function all janitors are retreived.
//This is done by first finding the department
//object with "Janitors" as value in to the
//Name attribute
ODB_QSTREAM q_stream;
ODB_SSTREAM s_stream;
ODB_REF o,jo;
char typenm[16]="Department";
char propnm[8]="Name";
char val[16]="Janitors";

  //Open a stream over the department type with the condition that
  //the Name attribute must have Janitors as value
  q_stream=My_DB.open_stream(typenm,propnm,val);

  //The stream may not be opened if so it is best to check before use.
  if (q_stream==NULL) {
    cout<<"\nError when opening stream bla bla bla...\n";
    return;
    };

  //The stream was opened allright. In the remaining we only consider the
  //first found department named Janitors, thus we do not assume the name
  //to be a key to departments

  o=My_DB.get(q_stream);

  //If o==NULL yhen there were no such department in the database
  if (o==NULL) {
    cout<<"No department named "<<val<<" exist in the database\n";
    return;
    }

  //No longer req'd
  My_DB.close_stream(q_stream);

  //reuse q_stream to open the stream to select all employees at
  //the department bound to object reference o

  q_stream=My_DB.open_stream("Employee","EmployedAt",o);
  if (q_stream==NULL) {
    cout<<"\nError when opening stream over employee\n";
    return;
    };
  cout<<"\nThese are the Janitors\n-------------------------\n";
  obuf.reset_buffer();      //reset the buffer
  //Now, we can start browsing the janitors
  jo=My_DB.get(q_stream);
  while (jo!=NULL){
    jo->display(obuf);
    cout<<obuf.get_buffer()<<'\n';
    obuf.reset_buffer();
    jo=My_DB.get(q_stream);
    };

  //Now, Lets select those janitors who earn more than 10000.
  //This is accomplished by opening a select stream over the
  //query stream which selects all janitors.

  //First, reset the stream
  My_DB.reset_stream(q_stream);
  //secondly, open a select stream over q_stream with the
  //condition that the salary should be greater than 10000
  s_stream=My_DB.select(q_stream,"Salary",">",(ODB_INT)10000);

  cout<<"\n Theese are all the rich janitors\n";
  obuf.reset_buffer();      //reset the buffer
  //Now, we can start browsing the rich janitors
  jo=My_DB.get(s_stream);
  while (jo!=NULL){
    jo->display(obuf);
    cout<<obuf.get_buffer()<<'\n';
    obuf.reset_buffer();
    jo=My_DB.get(s_stream);
    };

  My_DB.close_stream(s_stream);}

 void demonstrate_index(database &My_DB){
//In this fn a new type is created. The type is then
//given an integer valued proerty which can be assigned
//random values.
//First create 5000 objects and find the ones with a particular
//value for the attribute. Measure the time for this. Then create
//an index and redo the search and measure time.
struct timeb t1,t2;
double elapsed_time;
ODB_REF o;
int i,v;
ODB_QSTREAM q_stream;

  My_DB.set_tablesize(3000); //3K table size

  //Create a type named testtype
  My_DB.create_type("Testtype");

  //Create an integer valued property named Int_prop
  My_DB.add_property("Testtype","Int_prop",_INT_);

  //Create 5000 objects and assign random values to
  //the attribute named Int_prop for each object.
  cout<<"\nCreating 50000 objects ...";
  i=0;
  while (i++<50000){

    //Create an object and assign o to reference it
    o=My_DB.create_instance("Testtype");

    //generate a random number in [0..4000]
    v=rand()%4000;

    //set the property named Int_prop of the object o
    //to v
    o->setproperty_value("Int_prop",(ODB_INT)v);
  };
  cout<<"Done\n";

  //Now, lets find all objects with Int_prop = 2345
  //the probability that this number is less than 1
  //Thus, it may be the case that no object exists
  //with that property value. To make sure there is
  //atleast one such object we simply assign an object
  //that value for the property.

  //To make sure we do this right we first find out the
  //OID of the last object. Then we subtract a number less
  //than 50000 from the OID. We now have an OID to one of
  //the 50000 object we just generated. Retrieve it and
  //set the value.
  i=0;
  My_DB.getproperty_value(o,"OID", &(ODB_INT)i);
  i=i-rand()%45000;
  o=My_DB.getobjectwithoid("Testtype",i);
  o->setproperty_value("Int_prop",(ODB_INT)1234);

  //Now that we know there is an object with 1234 as
  //value for the Int_prop property we can start to
  //search for it.
  obuf.reset_buffer();
  q_stream=My_DB.open_stream("Testtype","Int_prop","=",(ODB_INT)1234);

  //All set for test search. Start timer and go for it
  ftime(&t1);
  o=My_DB.get(q_stream);
  while (o!=NULL){
    o->display(obuf);
    cout<<obuf.get_buffer()<<'\n';
    obuf.reset_buffer();
    o=My_DB.get(q_stream);
    };

  //All objects found, stop timer and check time
  ftime(&t2);
  elapsed_time=(t2.time*1000+t2.millitm)-(t1.time*1000+t1.millitm);
  cout<<"\The search took "<<elapsed_time<<"millisec without index";

  //Now, build an index and redo the whole procedure.
  cout<<"\nBuilding index ...";
  My_DB.create_index("Testtype", "Int_prop");
  cout<<"Done\n";

  //reset the stream
  My_DB.reset_stream(q_stream);
  obuf.reset_buffer();
  ftime(&t1);
  o=My_DB.get(q_stream);
  while (o!=NULL){
    o->display(obuf);
    cout<<obuf.get_buffer()<<'\n';
    obuf.reset_buffer();
    o=My_DB.get(q_stream);
    };
  ftime(&t2);
  elapsed_time=(t2.time*1000+t2.millitm)-(t1.time*1000+t1.millitm);
  cout<<"\The search took "<<elapsed_time<<"millisec with index";

  //-------------------------------------------------
  //--                I M P O R T A N T            --
  //-------------------------------------------------
  //-- Since the application now no longer need the--
  //-- 50000objects etc we now deallocate them     --
  //-- There are several ways of doing this.       --
  //-- 1: Open a stream over all Testtype objects  --
  //-- and deallocate them one by one.             --
  //-- 2: Since the type named Testtype is not     --
  //-- wanted anymore we simply delete the type    --
  //-- and let ODB do the hard work of deallocating--
  //-- everything, e.g. objects indexes ......     --
  //--                                             --
  //-- And also, we allocated a stream local to    --
  //-- to this function. This function must be     --
  //-- deallocated to prevent memory leak.         --
  //-------------------------------------------------

  My_DB.delete_type("Testtype");
  My_DB.close_stream(q_stream);

  //It is now ok to exit this function

}
int main (void){
database My_DB("");

int retval;
  retval=My_DB.load_database("Compdb.odb");
  if (retval<0) cout<<"\n Failed to load company database ";
  else {
    cout<<"\n The company database is loaded";
    My_DB.display(obuf);
    cout<<"\n"<<obuf.get_buffer();
    obuf.reset_buffer();
    };

  //display the schema of the loaded database
  display_schema(My_DB);

  //let the user select and browse a type extent
  select_type(My_DB);

  //demonstrate the search interface
  find_all_janitors(My_DB);

  //show the benefit of indexing when the database
  //is large
  demonstrate_index(My_DB);

  cout<<"\nODB Demo is now finished\n";
  return 1;
};