Subsections
The Generic C++ API
Initialization
The minimal EYEDB C++ program is as follows:
#include <eyedb/eyedb.h>
int
main(int argc, char *argv[])
{
eyedb::init(argc, argv);
// ...
eyedb::release();
return 0;
}
A few remarks about this code:
- the file eyedb/eyedb.h contains the whole EYEDB C++ API; except
for some specific administration or hacker tasks, it is not necessary
to include any other eyedb files.
- the EYEDB C++ layer must be initialized using one of the static method
init of the namespace eyedb:
- static void eyedb::init()
- static void eyedb::init(int &argc, char *argv[])
The first method only initializes the EYEDB layer while the second
one performs also some command line option processing.
For instance, the option -port=<port> allows you to use
the port <port> as the default connection port to the EYEDB
server, while the option -logdev=stderr displays log information
on the standard error.
The option -help-eyedb-options displays a brief usage for each standard options:
-U <user>|@, --user=<user>|@ User name
-P [<passwd>], --passwd[=<passwd>] Password
--host=<host> eyedbd host
--port=<port> eyedbd port
--inet Use the tcp_port variable if port is not set
--dbm=<dbmfile> EYEDBDBM database file
--conf=<conffile> Configuration file
--logdev=<logfile> Output log file
--logmask=<mask> Output log mask
--logdate=on|off Control date display in output log
--logtimer=on|off Control timer display in output log
--logpid=on|off Control pid display in output log
--logprog=on|off Control progname display in output log
--error-policy=<value> Control error policy: status|exception|abort|stop|echo
--trans-def-mag=<magorder> Default transaction magnitude order
--arch Display the client architecture
-v, --version Display the version
--help-eyedb-options Display this message
Note that all the standard command line options recognized in the
argc/argv array are suppressed from this array by
eyedb::init(int &argc, char *argv[]).
- the last statement eyedb::release() allows you to release all
the EYEDB allocated resources and to close opened databases and connections.
Note that this statement is optionnal as all EYEDB allocated resources,
opened databases and connections will be automatically released or closed
in the exit() function.
Connection Setting-up
To manage objects within a database we need to open this database.
But before opening any database we need to establish a connection with
the EYEDB server.
The connection to the EYEDB server is realized through the
eyedb::Connection class as follows:
eyedb::Connection conn;
conn.open();
A few remarks about this code:
- the construction of an eyedb::Connection instance (first line of code)
does not perform any actual actions: it only constructs a runtime instance.
- to establish the connection, one needs to use the
eyedb::Connection::open(const char *host=0, const char *port=0) method.
This method has two optionnal arguments: host and port.
If these arguments are not specified, their values are taken from the
configuration or
from the command line options -host=<host> and
-port=<port> if specified.
- in case of an error happened during the connection setting-up,
a status is returned or an exception is raised depending on the
chosen error policy. The default error policy is the status error
policy which means that each EYEDB method returns a status
implemented by the eyedb::Status class. The special status
eyedb::Success (in fact a null pointer) means that the operation
has been performed successfully:
The exception error policy means that each EYEDB method throws
an exception, implemented by the class eyedb::Exception, when an error
happened:
Note that eyedb::Status is an alias for const eyedb::Exception *.
To use the exception error policy, one needs to call the following
method before any operation:
eyedb::Exception::setMode(eyedb::Exception::ExceptionMode);
Although the exception error policy is not currently the default one in
EYEDB, we recommend to use it: it makes code clearer and safer.
In the following examples we use the exception error policy
to avoid any error management noise in the introduced C++ code.
Database Opening
To open a database one uses the eyedb::Database class as follows:
const char *dbname = argv[1];
eyedb::Database db(dbname);
db.open(&conn, eyedb::Database::DBRW);
- as the eyedb::Connection constructor, the eyedb::Database
constructor does not perform any actual operation: it constructs
a runtime instance.
- to open a database one uses the eyedb::Database::open methods
which takes the following arguments:
- a pointer to an opened eyedb::Connection instance.
- the opening flag which can be either eyedb::Database::DBRead for
read-only opening or eyedb::Database::DBRW for read-write opening.
Note that there are a dozen of opening modes that are introduced in
the reference manual.
- the user authentication
- the password authentication
The two last arguments are optionnal: if not specified, their values
are taken from the configuration file or from the command line options
-user=<user> and -passwd=<passwd>, or from
the standard input when using -passwd without given value.
Note that an EYEDB client can manage several connections and
several databases on each connection, for instance:
eyedb::Connection conn_local;
conn_local.open();
eyedb::Connection conn_remote;
conn_remote.open("arzal.zoo.com", 7620);
eyedb::Database db_1("foo");
db_1.open(&conn_local, eyedb::Database::DBRW);
eyedb::Database db_2("EYEDBDBM");
db_2.open(&conn_local, eyedb::Database::DBRead, "guest", "guest");
eyedb::Database db_3("droopy");
db_2.open(&conn_remote, eyedb::Database::DBRW, "droopy", "xyztu");
Transaction Management
Any object operation - storing or loading for instance -
within a database must be done in the scope of a transaction.
A transaction is an unit with atomicity, coherency and integrity.
- Atomicity means that the transaction modifications are either
realized (commit) or not realized at all (rollback or abort).
- Coherency means that a transaction starts from a coherent database
state, and leaves the database in a coherent state.
- Integrity means that a transaction modification is not lost, even
in case of a process, operating system or hardware failure.
A transaction scope is composed of a starting point, transactionBegin,
and an ending point, transactionCommit or transactionAbort:
eyedb::Database db(dbname);
db.open(&conn, eyedb::Database::DBRW);
db.transactionBegin();
// ... object operations
db.transactionCommit();
A call to eyedb::Database::transactionCommit() means that all the
operations performed in the transaction scope will be stored in
the database, while a call to eyedb::Database::transactionAbort() means
that all the operations will be forgotten.
Currently, EYEDB does not support nested transactions but it allows you
to write code such as:
db.transactionBegin(); // level 0 begin
// ... object operations
db.transactionBegin(); // level 1 begin
// ... object operations
db.transactionAbort(); // level 1 abort
// ... object operations
db.transactionCommit(); // level 0 commit
But the abort at level 1 is without effect: it will
not be performed; only the commit at level 0 will be performed.
One can give parameters to the transaction that one begins by setting an
optional argument of type eyedb::TransactionParams to the
transactionBegin method. The TransactionParams type is
composed of the following public attributes :
- the trsmode argument controls the transaction mode,
- the lockmode argument controls the object lock policy,
- the recovmode argument controls the recovery mode,
- the magorder argument controls the size of the allocated
tables for the transaction,
- the ratioalrt argument controls the error returned if
ratioalrt != 0 and trans object number > ratioalrt * magorder
- the wait_timeout argument controls
wait timeout value.
For instance :
TransactionParams params; // create params with default values
params.lockmode = eyedb::ReadNWriteX; // objects are not locked for reading
// and locked exclusive for writing
params.magorder = 100000000; // transaction can deal with about
// 100 millions of objects
db.transactionBegin(params);
Refer to the reference manual to get more information about these arguments.
Schema and Class Manipulation
The EYEDB C++ API provides runtime facilities to manipulate the EYEDB
classes. In fact, as the class class inherits from the
class object, EYEDB classes can be manipulated as objects.
A class is composed of a list of attributes, constraints,
variables, methods, triggers and indexes.
The classes are gathered through a schema instance tied to
each database.
A class can be a system class, for instance the class class, the
class object, the class agregat or a user class, for
instance the class Person, the class Employee.
To illustrate this object model, we are going to show how to display the
user classes of a given database:
eyedb::Database db(dbname);
db.open(&conn, eyedb::Database::DBRW);
db.transactionBegin();
eyedb::LinkedListCursor c(db.getSchema()->getClassList());
eyedb::Class *cls;
while (c.getNext((void*&)cls))
if (!cls->isSystem())
cout << cls;
db.transactionCommit();
As shown here, this code is very simple:
- database opening as we have seen before.
- linked list cursor creation on the database schema class list.
- display of each class in the list which is not a system class.
For instance, to display all the classes of type struct which contains
an attribute named age:
eyedb::LinkedListCursor c(db.getSchema->getClassList());
eyedb::Class *cls;
while (c.getNext((void*&)cls))
if (cls->asStructClass()) {
int attr_cnt;
const eyedb::Attribute **attrs = cls->getAttributes(attr_cnt);
for (int i = 0; i < attr_cnt; i++)
if (!strcmp(attrs[i]->getName(), "age")) {
cout << cls;
break;
}
}
Object Manipulation
There are two types of objects: runtime objects and database objects.
Runtime objects are the OML (Object Manipulation Language) objects,
for instance C++ or Java objects, while the database objects are
the objects stored in a database.
There are two types of runtime objects: persistent runtime objects and
transient runtime objects.
A runtime object is persistent if it is tied to a database object.
Otherwise, it is transient.
Contrary to some other OODBMS, EYEDB does not
provide an automatic synchronisation between persistent runtime objects
and database objects.
When setting values on a persistent runtime object, we do not modify
the tied database object.
We must call the store method on the persistent runtime object
to update the tied database object.
Note that any persistent runtime object manipulation must be done
in the scope of a transaction.
To illustrate object manipulations, we introduce a simple concrete
example.
This example will be used in the whole continuation of this chapter.
The example is as follows:
//
// person.odl
//
enum CivilState {
Lady = 0x10,
Sir = 0x20,
Miss = 0x40
};
class Address {
attribute string street;
attribute string<32> town;
attribute string country;
};
class Person {
attribute string name;
attribute int age;
attribute Address addr;
attribute Address other_addrs[];
attribute CivilState cstate;
attribute Person * spouse inverse Person::spouse;
attribute set<Car *> cars inverse owner;
attribute array<Person *> children;
int change_address(in string street, in string town,
out string oldstreet, out string oldtown);
static int getPersonCount();
index on name;
};
class Car {
attribute string brand;
attribute int num;
Person *owner inverse cars;
};
class Employee extends Person {
attribute long salary;
};
This file is located at prefix/share/doc/eyedb/examples/C++Binding/schema-oriented/share/schema.odl.
Creating Runtime Objects
Using the C++ API, we cannot create directly a database object.
We must create first a runtime object.
To create a runtime object we invoke the newObj method
of the object class.
For instance, to create a runtime Person object, we need
to invoke the newObj method of the Person runtime class
as follows:
eyedb::Class *cls = db.getSchema()->getClass("Person");
eyedb::Object *p = cls->newObj(&db);
The eyedb::Class::newObj(eyedb::Database * = 0) is the class instantiation
method for both persistent and transient object.
A transient object is created using the newObj without any argument,
while a persistent object is created using the same method with
a valid database runtime pointer.
Note that as long as the store method has not been called,
the persistent runtime object is not yet tied to a database object.
So, if we follow strictly the definition of runtime objects,
it is not yet a persistent runtime object; but as soon as a runtime
object is created using the newObj method with a valid database
pointer, we will say that it is persistent.
Synchronizing Runtime Objects to Database Objects
When a persistent object is stored in the database using the
store method, an unique object identifier OID is allocated
to this object.
This OID can be acceded using the method eyedb::Object::getOid(),
for instance to display the allocated OID:
eyedb::Object *p = cls->newObj(&db);
cout << "before storing: " << p->getOid() << endl;
p->store();
cout << "after storing: " << p->getOid() << endl;
The output displayed by the previous code is something as follows:
before storing: NULL
after storing: 1456.3.38475637:oid
As shown here, before the first call of the store method, the OID is
not set; a NULL is displayed.
The created OID is composed of three fields:
- the object number : 1456
- the database identifier : 3
- a magic number : 38475637
The database identifier designates, in an unique way, a database while
the object number designates, in an unique way, an object within a database.
The magic number, which is a random generated number, ensures more security
in the object identification process.
Setting Attribute Values to a Runtime Object
Assume that we want to set a name and a age values to a Person instance.
Here is a way to do so:
eyedb::Class *cls = db.getSchema()->getClass("Person");
eyedb::Object *p = cls->newObj(&db);
// getting attributes from class
const eyedb::Attribute *attr_name = cls->getAttribute("name");
const eyedb::Attribute *attr_age = cls->getAttribute("age");
// setting name attribute value
attr_name->setSize(p, strlen("john")+1);
attr_name->setValue(p, (eyedb::Data)"john", strlen("john")+1, 0);
// setting age attribute value
eyedb::_int32 age = 27;
attr_age->setValue(p, (eyedb::Data)&age, 1, 0);
We need to do a few remarks about this code:
- to get specific named attribute within a class,
we use the method eyedb::Class::getAttribute(const char *).
This method returns a pointer to an eyedb::Attribute which contains
a complete description of this attribute: type, name, size, position and
so on.
- to set an attribute value for the instance p, we
use the method eyedb::Attribute::setValue(eyedb::Object *o, eyedb::Data data,
int nb, int from) whose arguments are as follows:
- eyedb::Object *o: the runtime object pointer to modify.
- eyedb::Data data: the pointer to the attribute value to set.
- int nb: for an array, the number of values to set.
- int from: for an array, the starting index of the values to set.
- the eyedb::Attribute::setSize(eyedb::Object *, eyedb::Size) method is
used for the attribute name because this attribute is of variable
size (remember the schema description : string name).
So, before setting the attribute value, we must set the size of this
attribute value.
- remember that the database object tied to this persistent object has not
been changed in the database: only the transient values have been changed.
To change the database object, one needs to use the method
eyedb::Object::store() as follows:
p->store();
The store method allows you to synchonize the transient values
of a persistent object with the database.
To avoid all this class and attribute manipulation and to deal with
direct access attribute methods, one needs to use the eyedbodl
tool which allows you to generate specific C++ code from a specific database
schema.
For instance, using this tool, the previous code becomes:
Person *p = new Person(&db);
p->setName("john");
p->setAge(27);
p->store();
The class Person, the methods setName and setAge
have been generated by the eyedbodl tool in a very simple way.
Refer to the second part of this chapter the Schema-Oriented
Generated C++ API.
Loading Database Objects
To load an object from a database, one needs to give its OID to
the eyedb::Database::loadObject method, for instance:
eyedb::Oid oid("1456.3.38475637:oid");
eyedb::Object *o;
db.loadObject(oid, o);
cout << "object " << oid << " is of class " << o->getClass()->getName()
<< endl;
cout << o;
The previous code loads the object from the database, displays its
oid and class name and displays the whole object.
Getting Attribute Values from a Runtime Object
The process to get attribute values from a runtime object is very similar
to the process to set attribute values.
For instance to get the name and age attribute values of
the previous loaded object, one can do as follows:
eyedb::Oid oid("1456.3.38475637:oid");
eyedb::Object *o;
db.loadObject(oid, o);
// getting attributes from class
const eyedb::Attribute *attr_name = cls->getAttribute("name");
const eyedb::Attribute *attr_age = cls->getAttribute("age");
// getting name attribute size
eyedb::Size name_length;
attr_name->getSize(o, name_length);
// getting name attribute value
char *name = new char[name_length];
attr_name->getValue(o, (eyedb::Data *)name, name_length, 0);
cout << "name is : " << name << endl;
delete [] name;
// getting age attribute value
eyedb::_int32 age;
attr_age->getValue(o, (eyedb::Data *)&age, 1, 0);
cout << "age is : " << age << endl;
To get an attribute value we use the method
eyedb::Attribute::getValue(const eyedb::Object *o, eyedb::Data *data,
int nb, int from, eyedb::Bool * isnull = 0) whose arguments are as follows:
- eyedb::Object *o: the runtime object pointer.
- eyedb::Data data: the pointer to the attribute value to get: this
pointer must be allocated correctly according to the returned value type.
It is why we get first the size of the name attribute value to allocate
the returned buffer with a valid size.
- int nb: for an array, the number of values to get.
- int from: for an array, the starting index of the values to get.
- eyedb::Bool *isnull: an optionnal boolean to check if the attribute
value is null (i.e. not initialized).
If we want to get the spouse value of the loaded person,
we must do something a little bit more complicated:
eyedb::Oid oid("1456.3.38475637:oid");
eyedb::Object *o;
db.loadObject(oid, o);
// getting spouse attribute from class
const eyedb::Attribute *attr_spouse = cls->getAttribute("spouse");
eyedb::Oid spouse_oid;
attr_spouse->getOid(o, &spouse_oid);
if (spouse_oid.isValid()) {
eyedb::Object *spouse;
db.loadObject(spouse_oid, spouse);
cout << "spouse is: " << spouse;
}
To get the spouse attribute value, we need to get first the
spouse OID using the eyedb::Attribute::getOid method on
the spouse attribute.
Then, if the OID is valid, we load the spouse from the
found OID.
Once again, using the eyedbodl tool, all the previous code
becomes very simple:
eyedb::Oid oid("1456.3.38475637:oid");
eyedb::Object *o;
db.loadObject(oid, o);
Person *p = Person_c(o);
cout << "name is : " << p->getName() << endl;
cout << "age is : " << p->getAge() << endl;
cout << "spouse is: " << p->getSpouse();
Loading Database Objects using OQL
We have seen in the previous section how to load a database object from its
OID. The problem is that the OID is a rather
hidden concept and there are very few chances to know an object OID
before having loaded it.
To load database objects it seems more reasonnable to use a query language
such as OQL.
The EYEDB C++ API allows you to perform any OQL queries using the class
eyedb::OQL.
For instance to get all Person whose age is less than a given value:
db.transactionBegin();
eyedb::OQL q(&db, "select Person.age < %d", given_age);
eyedb::ObjectArray obj_arr(eyedb::True);
q.execute(obj_arr);
for (int i = 0; i < obj_arr.getCount(); i++)
cout << obj_arr[i];
A few remarks about this code:
- remember that any persistent runtime object manipulation must be done
in the scope of a transaction: it is why the first statement is a call
to the transactionBegin method. In most of the previous code examples,
we volontary omit this call.
- the class eyedb::OQL is used to perform
any OQL query. The main constructor eyedb::OQL(eyedb::Database *db, const char *fmt, ...) allows you to make an OQL query in a simple way.
The arguments are as follows:
- the database pointer within which to perform the query.
- the format of the query in a sprintf style.
- the other arguments are the arguments related to the previous
format.
- to get all the objects returned by the query, we use the
eyedb::OQL::execute(eyedb::ObjectArray &) method. This method filled
the object array reference given as input parameter.
- the method eyedb::ObjectArray::getCount() returned the number
of objects contained in an object array.
- the [] eyedb::ObjectArray operator has been overloaded to
allow you to perform direct access to the contained objects: obj_arr[i] is
the object #i within the object array.
- the argument eyedb::True to the eyedb::ObjectArray constructor
means that we want that all the contained objects to be deleted
when this object array will be deleted.
Sometimes we want to perform a query to get only a part of some objects.
For instance, to get the name of all persons whose age is less than
a given value, there are two ways:
- the first one is to get all the persons whose age is less
than the given value using an OQL query, and then get their name value
as follows:
eyedb::OQL q(&db, "select Person.age < %d", given_age);
eyedb::ObjectArray obj_arr(eyedb::True);
q.execute(obj_arr);
const eyedb::Attribute *attr_name = cls->getAttribute("name");
for (int i = 0; i < obj_arr.getCount(); i++) {
// getting name attribute size
eyedb::Size name_length;
attr_name->getSize(obj_arr[i], name_length);
// getting name attribute value
char *name = new char[name_length];
attr_name->getValue(obj_arr[i], (eyedb::Data *)name, name_length, 0);
cout << "name of #" << i << " is : " << name << endl;
delete [] name;
}
- the second one is to perform directly an appropriate query as follows:
eyedb::OQL q(&db, "(select Person.age < %d).name", given_age);
eyedb::ValueArray val_arr;
q.execute(val_arr);
for (int i = 0; i < val_arr.getCount(); i++)
cout << "name of #" << i << " is : " << val_arr[i].str << endl;
In this case, the returned value are not object values but string values.
So we cannot use the execute(eyedb::ObjectArray&) method to get
these values but the more general form execute(eyedb::ValueArray&)
An eyedb::ValueArray instance is an array of eyedb::Value instances.
The eyedb::Value class is the most general form of an OQL returned value.
It can take the form of a integer, a string, an OID, an object and
so on.
Note that this second way is more efficient as only the person
name are returned from the server and not the full object.
Releasing Runtime Objects
All the runtime objects which have been allocated by the client code
or by a load or query method must be released by the client code.
To release an eyedb::Object or inherited class instance, we must use
the eyedb::Object::release() method as follows:
eyedb::Object *o1 = cls->newObj();
// ...
o1->release();
eyedb::Object *o2;
db.loadObject(oid, o2);
// ...
o2->release();
The C++ delete operator is forbiden: if you try to use this operator
on any eyedb::Object instance, you will get an error message at runtime.
Note that if you release a persistent runtime object you do not release
the tied database object.
Refer to the section Memory Management to understand the whole
memory policy of the C++ API.
Removing Database Objects
To remove a database object, we need to use the
eyedb::Object::remove() method or the eyedb::Database::removeObject(const eyedb::Oid &oid) method, for instance:
db.transactionBegin();
o->remove();
o->release();
db.transactionCommit();
or:
db.transactionBegin();
db.removeObject(oid);
db.transactionCommit();
When calling one of the previous remove methods, it is not necessary
to call the store method after.
EyeDB manual