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:
  1. 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.
  2. the EYEDB C++ layer must be initialized using one of the static method init of the namespace eyedb:
    1. static void eyedb::init()
    2. 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[]).
  3. 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:
  1. the construction of an eyedb::Connection instance (first line of code) does not perform any actual actions: it only constructs a runtime instance.
  2. 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.
  3. 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);
  1. as the eyedb::Connection constructor, the eyedb::Database constructor does not perform any actual operation: it constructs a runtime instance.
  2. to open a database one uses the eyedb::Database::open methods which takes the following arguments:
    1. a pointer to an opened eyedb::Connection instance.
    2. 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.
    3. the user authentication
    4. 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.
  1. Atomicity means that the transaction modifications are either realized (commit) or not realized at all (rollback or abort).
  2. Coherency means that a transaction starts from a coherent database state, and leaves the database in a coherent state.
  3. 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 :
  1. the trsmode argument controls the transaction mode,
  2. the lockmode argument controls the object lock policy,
  3. the recovmode argument controls the recovery mode,
  4. the magorder argument controls the size of the allocated tables for the transaction,
  5. the ratioalrt argument controls the error returned if ratioalrt != 0 and trans object number > ratioalrt * magorder
  6. 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:
  1. database opening as we have seen before.
  2. linked list cursor creation on the database schema class list.
  3. 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:
  1. the object number : 1456
  2. the database identifier : 3
  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:
  1. 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.
  2. 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:
    1. eyedb::Object *o: the runtime object pointer to modify.
    2. eyedb::Data data: the pointer to the attribute value to set.
    3. int nb: for an array, the number of values to set.
    4. int from: for an array, the starting index of the values to set.
  3. 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.
  4. 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:
  1. eyedb::Object *o: the runtime object pointer.
  2. 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.
  3. int nb: for an array, the number of values to get.
  4. int from: for an array, the starting index of the values to get.
  5. 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:
  1. 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.
  2. 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:
    1. the database pointer within which to perform the query.
    2. the format of the query in a sprintf style.
    3. the other arguments are the arguments related to the previous format.
  3. 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.
  4. the method eyedb::ObjectArray::getCount() returned the number of objects contained in an object array.
  5. 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.
  6. 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:
  1. 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;
      }
    
  2. 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