Table of Contents

Minimalist Sample Code

This section of the documentation drives you, without explaining all the details, through short program excerpts. The purpose is to show you the big picture : how you can create or connect to a database and start working with it.

The Basics

Compiling

All your project files which use IBPP obviously need to include the library header file.

#include "ibpp.h"

You don't need to include other files from the Firebird development kit, like ibase.h or iberror.h.

Linking

You also obviously need to link your project with the ibpp 'library' (actually with the IBPP source code files).

Connecting to an existing database

You will use one IBPP::Database instance per connection to a database. Of course, you can handle as many connections as you want, to a same database, or to multiple databases, on a single or multiple local or remote servers.

try
{
    IBPP::Database db = IBPP::DatabaseFactory("myserver", "/data/db.fdb", "username", "password");
    db->Connect();
    ...
    db->Disconnect();
}
catch (IBPP::Exception& e)
{
    cout << e.ErrorMessage();
}

In the above code, the DatabaseFactory() call gets an interface pointer to a Database and assigns it to a smart pointer. This won't actually connect to the database. Then the Connect() method is called (through the smart pointer) and the connection is attempted with whatever server, database name, user, and password you provided. In case of failure, IBPP will throw an IBPP::Exception. Throwing C++ exception is the only mean of reporting errors. IBPP has no return value on most of its methods. Disconnects() closes the connection. You can later connect again by re-issuing a Connect(). Or you can recyle the 'db' variable to connect to establish another different connection:

...
db = IBPP::DatabaseFactory("otherserver", "/local/otherdb.fdb", "username", "password");
db->Connect();
...

Should you forgot to Disconnect() before assigning the new interface, IBPP will take care of cleanup for you. Upon assignment of the new interface, the smart pointer will first Release() the previous one. If this was the last reference to that interface, the old interface itself will be destructed, and as part of this the connection will be cleanly closed, rolling back any pending transactions, and so on…

Using a transaction

Once connected to the database, you will start one or multiple transactions. This is the simplest way of starting a transaction :

...
IBPP::Database db = IBPP::DatabaseFactory("myserver", "/data/db.fdb"
                             "username", "password");
db->Connect();
IBPP::Transaction tr = IBPP::TransactionFactory(db);
tr->Start();
...
tr->Commit();    // Or tr->Rollback();
...

The above code will start a default 'write' transaction. It uses defaults which correspond to:

IBPP::Transaction tr = IBPP::TransactionFactory(db, IBPP::amWrite, IBPP::ilConcurrency, IBPP::lrWait);

This is a transaction for writing, using the isolation level 'Concurrency', and a lock resolution of 'Wait'. This is the most common transaction in the Firebird world. See ibpp.h for other options. Look for the TAM, TIL and TLR enumerations, respectively for Access Mode, Isolation Level and Lock Resolution.

Executing statements

To act upon the database you also need one or multiple statements. The IBPP::Statement is the interface through which you will really work on the database. It will let you submit SQL statements for execution, will help providing parameters for these statements and will be your one-stop shop for reading the results of the statements. Here is a short partial sample, db is supposed Connected() and tr is supposed Started() :

...
std::string fn;
std::string ln;
IBPP::Statement st = IBPP::StatementFactory(db, tr);
st->Execute("SELECT firstname, lastname FROM sometable ORDER BY lastname");
while (st->Fetch())
{
    st->Get(1, fn);
    st->Get(2, ln);
    cout << fn.c_str() << " - " << ln.c_str() << "\n";
}
...

Let's comment the above code. A Statement is tightly linked to the database and the transaction in the context of which it will run, hence the db and tr parameters to the TransactionFactory() method. A statement that does not need to run multiple times can be handled straight to Execute() method. That's what we did here. Being a SELECT the statement returns possibly zero, one or multiple rows of data. In all cases, we will get the rows of the result set using the Fetch() method of the statement. Each Fetch() will return the next row of results. The first Fetch returns the first row. It is not implicitly fetched. When there are no more rows (or none), Fetch() simply returns 'false'.

Another example using query parameters:

...
std::string fn;
std::string ln;
IBPP::Statement st = IBPP::StatementFactory(db, tr);
st->Prepare("SELECT age, firstname, lastname FROM sometable ORDER BY lastname "
            "WHERE age = ?");
for (int age = 30; age < 40; age++)
{
    cout << "People of age " << age << ":\n";
    st->Set(1, age);
    st->Execute();
    while (st->Fetch())
    {
        st->Get(1, fn);
        st->Get(2, ln);
        cout << fn.c_str() << " - " << ln.c_str() << "\n";
    }
}
...

In this example, we Prepare() the statement instead of calling Execute(). This will allow us to repetedly Execute() the same statement, without the overhead of the preparation. Before each Execute(), we set the value of the input parameters (the question marks).