LiteSQL - C++ Object-Relational Persistence Framework
Examples

Creating and Dropping Database

The code below defines a simple database:

<?xml version="1.0"?>
<!DOCTYPE database SYSTEM "litesql.dtd">
<database name="PersonDatabase">
<object name="Person">
<field name="name" type="string"/>
</object>
</database>

The database file is created when PersonDatabase is constructed. First argument of constructor is requested backend. Other possibilities are "postgresql" and "mysql". Refer to class documentation for details.

PersonDatabase db("sqlite3", "database=person.db");

The database has to be created before any Persistent - classes can be used.

db.create();

The database can be dropped using drop() - method.

db.drop();

Suppose the database schema has changed along with new features of the program. Few data fields has been added to Person-class and completely new Persistent-class House has been defined. To upgrade database on disk, use needsUpgrade() and upgrade() - methods.

if (db.needsUpgrade())
db.upgrade();

If new fields has been added to classes, the upgrade operation may be quite costly. All backends do not support table modifications so records has to be copied forth and back.

Upgrade routine will try to preserve data. It will drop fields that are not in new schema and insert NULL-values to new fields.

Storing and Deleting Persistents

A simple Person database:

<?xml version="1.0"?>
<!DOCTYPE database SYSTEM "litesql.dtd">
<database name="PersonDatabase">
<object name="Person">
<field name="name" type="string"/>
<field name="age" type="integer"/>
</object>
</database>

Following code demonstrates how to insert Persons to database:

PersonDatabase db("sqlite3", "database=person.db"); // assumes the database has been created
Person person(db); // construct Person, does not write anything to database
person.name = "Bob"; // assign values to fields
person.age = 20;
person.update(); // writes a new record to database
person.age = 21; // Bob got just older
person.update(); // updates old record
person.id = 100; // force internal identifier (id) to 100
person.update(); // updates old record

Note: if internal identifier (id-field) is changed, relations will not "follow" the object and will not be deleted either. If the object is not replaced with another object, relations should be manually dropped using delRelations-method.

Following code demonstrates how to delete Persons from database:

Person person = select<Person>(db).one(); // any Person will do
person.del(); // person.onDelete() gets called before data is deleted

Selecting Persistents and Expr-API

select-function

template <class T> select(const Database& db, const Expr& filter=Expr());

Template function select<T> returns DataSource which can be used to retrieve Persistent-objects or to create more complex queries.

select-function has two parameters: Database and expression (Expr) which can be used to limit the result set. DataSource DataSource<T> is basically a wrapper for selection query and is used to access objects of type T.

DataSource<T> has following methods:

* idQuery(): returns SelectQuery which returns id-number of Persistent objects
* objectQuery(): returns SelectQuery which returns data rows of Persistent objects
* cursor(): returns Cursor<T> which will return Persistent objects one by one
* one(): returns first Persistent object in the result set. Note: throws NotFound - if the result set is empty
* all(): returns the whole result set as Persistent objects in vector
* count(): returns number of object in the result set
* orderBy(FieldType field, bool ascending=true): result set can be ordered with this method.
* orderByRelation(FieldType id, FieldType field, bool ascending=true): orders the result set by external relation

Selection Examples

An example of select and selectOne with database:

PersonDatabase db("sqlite3", "database=person.db");
vector<Person> = select<Person>(db).all();
Person bob = select<Person>(db, Person::Name == "Bob").one();

SelectQuery-class can be used when creating SQL SELECT-statements. Refer to documentation of SelectQuery for a list of methods. Methods can be combined:

Records recs = db.query(SelectQuery().result("id_")
.source("Person_")
.where(Person::Name == "Bob"));

Selection expressions (filters)

Filters for select are created using Expr-API. The simplest form of a filter expression is <field> <operator>

. Example:

Person::Name == "Bob"

These expression can be combined using parenthesis and connectives:

() && ||

Operators that can be used in expressions are

== > < >= <= ! != .in .like

Persistent's (static) field information can be accessed using static FieldType-objects The name of the FieldType-object is almost the same as the field. Capitalize name of the field to get name of the FieldType-object.

// Person's name
Person::Name

Set-operations intersect, union_ and except

template <class T> intersect(const DataSource<T>& ds1,
const DataSource<T>& ds2);
template <class T> union_(const DataSource<T>& ds1,
const DataSource<T>& ds2);
template <class T> except(const DataSource<T>& ds1,
const DataSource<T>& ds2);

Two DataSources of same type can be used to create a new DataSource which can be used to access intersection, union or difference of the sources.

Notes: -'union' is a reserved word of C so union_ is the name of set operation. -see MySQL specific notes about using these set operations with MySQL

// intersection of Bob's and Bill's friends
DataSource<Person> commonFriends =
intersect(bob.friends().get(), bill.friends().get());
// persons that are Bob's friends and/or Bill's friends
DataSource<Person> allFriends =
union_(bob.friends().get(), bill.friends().get());
// persons that are Bob's friends but not Bill's friends
DataSource<Person> allFriends =
except(bob.friends().get(), bill.friends().get());

Using Relations

A simple database with Person-class and friends-relation.

<?xml version="1.0"?>
<!DOCTYPE database SYSTEM "http://litesql.sourceforge.net/litesql.dtd">
<database name="TestDatabase" namespace="test">
<object name="Person">
<field name="name" type="string"/>
</object>
<relation name="FriendsRelation">
<relate object="Person" handle="friends"/>
<relate object="Person"/>
</relation>
</database>

Relation-class

Usually relations are accessed using a relation handle that is attached to a persistent object. Sometimes, it may be convenient to access relation using static methods of Relation-class.

Methods:

RelationHandle-class

Relation handle is attached to persistent object and it provides convenient access to relation. It can be used to link, unlink or select objects related to relation handle's owner.

RelationHandle-class' methods:

RelationHandle-Examples

A linking example:

Person bill(db), bob(db);
bill.name = "Bill";
bill.update();
bob.name = "Bob";
bob.update();
// both objects must be stored in database before they can be linked
bill.friends().link(bob);
// following statement would throw an exception because they are already friends
// (friends is bidirectional relation)
bob.friends().link(bill);

A fetching example:

Person bob = bill.friends().get(Person::Name == "Bob").one();
vector<Person> billsFriends = bill.friends().get().all();

An unlinking example:

// Bill and Bob are no longer friends
bill.friends().unlink(bob);
~~~~~~~~~~~~~~~Relation-Examples
Same examples as above converted to static methods of FriendsRelation.
A linking example:
~~~~~~~~~~~~~~~{.cpp}
Person bill(db), bob(db);
bill.name = "Bill";
bill.update();
bob.name = "Bob";
bob.update();
// both objects must be stored in database before they can be linked
FriendsRelation::link(db, bill, bob);

A fetching example:

Person bob = FriendsRelation::getPerson2(db,
Person::Name == "Bob",
FriendsRelation::Person1==bill.id).one();
vector<Person> billsFriends =
FriendsRelation::getPerson2(db, Expr(),
FriendsRelation::Person1==bill.id).all();

An unlinking example:

// Bill and Bob are no longer friends
Friendsrelation::unlink(bill, bob);

Upcasting and Virtual Methods

In order to support virtual methods a Persistent object must be upcasted to correct type before calling virtual method.

upcast will return an auto_ptr which holds a pointer to an object of correct type.

upcastCopy is similar to upcast. It does not select an object of correct type from database but just constructs it in memory. Note that constructed object does not have all of its fields. This method is suitable when virtual method to be called does not need any special fields.

An example of upcast:

Playable pl = select<Playable>(db).one();
pl.upcast()->play();

An another example of upcast:

Playable pl = select<Playable>(db).one();
auto_ptr<Playable> uc = pl.upcast();
cout << "Playing " << uc->name << endl;
uc->play();

An example of upcastCopy:

Playable pl = select<Playable>(db).one();
pl.upcastCopy()->makeLogEntry(log);

Using Cursor (iterating the result set row by row)

Often it is not necessary to fetch all objects to memory and then process them.

Memory usage can be minimized using template class Cursor which returns one object at a time.

Creating a cursor(two ways):

Cursor<Record> cur1 = db.cursor(SelectQuery().result("id_").source("Person_"));
Cursor<Person> cur2 = select<Person>(db).cursor();

Iterating through the result set:

for (;cur.rowsLeft(); cur++)
cout << (*cur).name << endl;

The remaining contents of the result set can be retrieved with dump-method:

vector<Person> rest = cur.dump();

Database API: raw queries

If you prefer to access database using raw queries, use plain Database-class and query-method.

escapeSQL-function can be used to escape illegal characters such as '. NULL is a special word and it has to be escaped by the user. 'NULL' maps to NULL and ''NULL'' to 'NULL' etc.

Use Database::groupInsert - if you want to create rows with new integer as the first field.

A query example:

Database db("sqlite3", "database=test.db");
db.query("CREATE TABLE custom(value INTEGER);");
db.query("INSERT INTO custom VALUES (1)");
Records recs = db.query("SELECT * from custom");
for (Records::iterator i = recs.begin(); i != recs.end(); i++)
cout << (*i)[0] << endl;