Firebird interfaces.

Firebird's OO API is based on use of interfaces. That interfaces, though looking in some aspects like OLE2 interfaces (some of them have addRef() and release() methods) are non standard and have features, missing in ither widely used types of interfaces. First of all Firebird interfaces are language independent – that means that to define/use them one need not use language specific constructions like class in C++, interface may be defined using any language having concepts of array and pointer to procedure/function. Next interfaces are versioned – i.e. we support different versions of same interface. Binary layout of interfaces is designed to support that features very efficient (there is no need in additional virtual calls like in OLE2/COM with it's QueryInterface) but it's not convenient for direct use from most languages. Therefore language-specific wrappers should better be designed for different languages making use of API easier. Currently we have wrappers for C++ and Pascal, Java is coming soon. From end-user POV calls from C++ and Pascal have absolutely no difference, though some additional language-specific features present in C++ (like automatic status check after API calls) are missing in Pascal.

Typically database API is used to access data stored in database. Firebird OO API certainly performs this task but in addition it supports writing your own plugins – modules, making it possible to enhance Firebird capabilities according to your needs. Therefore this document contains 2 big parts – accessing databases and writing plugins. Certainly some interfaces (like status vector) are used in both parts of API, they will be discussed in data access part and freely referenced later when discussing plugins. Therefore even if you plan to write some plugin you should better start with the first part of this document. Moreover a lot of plugins need to access databases themselves and data access API is typically needed for it.

Firebird installation package contains a number of live samples of use of OO API – they are in examples/interfaces (database access) and examples/dbcrypt (plugin performing fictitious database encryption) directories. It's supposed that the reader is familiar with ISC API used in Firebird since interbase times.

Accessing databases.

# Creating database and attaching to existing database.

First of all we need to get access to IMaster interface. IMaster is primary Firebird interface, required to access all the rest of interfaces. Therefore there is a special way of accessing it – the only one needed to use OO API plain function called fb_get_master_interface(). This function has no parameters and always succeeds. There is one and only one instance of IMaster per Firebird client library, therefore one need not care about releasing memory, used by master interface. A simplest way to access it from your program is to have appropriate global or static variable:

static IMaster* master = fb_get_master_interface();

For a lot of methods, used in Firebird API, first parameter is IStatus interface. It's a logical replacement of ISC_STATUS_ARRAY, but works separately with errors and warnings (not mixing them in same array), can contain unlimited number of errors inside and (this will be important if you plan to implement IStatus yourself) always keeps strings, referenced by it, inside interface. Typically you need at least one instance of IStatus to call other methods. You obtain it from IMaster:

IStatus* st = master->getStatus();

If method getStatus() fails for some reason (OOM for example) it returns NULL – obviously we can't use generic error reporting method which is based on use of IStatus here.

Now we are going to deal with first interface, directly related to database calls. This is IProvider – interface called this way cause it's exactly that interface that must be implemented by any provider in Firebird. Firebird client library also has it's own implementation of IProvider, which must be used to start any database activity. To obtain it we call IMaster's method:

IProvider* prov = master->getDispatcher();

When attaching to existing database or moreover creating new one it's often necessary to pass a lot of additional parameters (logon/password, page size for new database, etc.) to API call. Having separate language-level parameters is close to unreal – we will have to modify a call too often to add new parameters, and number of them will be very big no matter of the fact that typically one needs to pass not too much of them. Therefore to pass additional parameters special in-memory data structure, called database parameters block (DPB) is used. Format of it is well defined, and it's possible to build DPB byte after byte. But it's much easier to use special interface IXpbBuilder , which simplifies creation of various parameters blocks. To obtain an instance of IXpbBuilder you must know one more generic-use interface of firebird API – IUtil . It's a kind of placeholder for the calls that do not fit well in other places. So we do

IUtil* utl = master->getUtilInterface();

IXpbBuilder* dpb = utl->getXpbBuilder(&status, IXpbBuilder::DPB, NULL, 0);

This creates empty parameters' block builder of DPB type. Now adding required parameter to it is trivial:

dpb->insertInt(&status, isc_dpb_page_size, 4 * 1024);

will make firebird to create new database with pagesize equal to 4Kb and meaning of

dpb->insertString(&status, isc_dpb_user_name, “sysdba”);

dpb->insertString(&status, isc_dpb_password, “masterkey”);

is (I hope) obvious.

The following is C++ specific: We are almost ready to call createDatabase() method of IProvider, but before it a few words about concept of Status Wrapper should be said. Status wrapper is not an interface, it's very thin envelope for IStatus interface. It helps to customize behavior of C++ API (change a way how errors, returned in IStatus interface, are processed). For the first time we recommend use of ThrowStatusWrapper, which raises C++ exception each time an error is returned in IStatus.

ThrowStatusWrapper status(st);

Now we may create new empty database:

att = prov->createDatabase(&status, "fbtests.fdb", dpb->getBufferLength(&status), dpb->getBuffer(&status));

printf("Database fbtests.fdb created\n");

Pay attention that we do not check status after the call to createDatabase(), because in case of error C++ or Pascal exception will be raised (therefore it's very good idea to have try/catch/except syntax in your program). We also use two new functions from IXpbBuilder – getBufferLength() and getBuffer(), which extract data from interface in native parameters block format. As you can see there is no need to check explicitly for status of functions, returning intermediate results.

Detaching from just created database is trivial:

att->detach(&status);

Now it remains to enclose all operators into try block and write a handler in catch block. When using ThrowStatusWrapper you should always catch defined in C++ API exception class FbException, in Pascal you must also work with class FbException. Exception handler block in simplest case may look this way:

catch (const FbException& error)

{

char buf[256];

utl->formatStatus(buf, sizeof(buf), error.getStatus());

fprintf(stderr, "%s\n", buf);

}

Pay attention that here we use one more function from IUtil – formatStatus(). It returns in buffer text, describing an error (warning), stored in IStatus parameter.

To attach to existing database just use attachDatabase() method of IProvider instead createDatabase(). All parameters are the same for both methods.

att = prov->attachDatabase(&status, "fbtests.fdb", 0, NULL);

This sample is using no additional DPB parameters. Take into account that without logon/password any remote connection will fail if no trusted authorization plugin is configured. Certainly login info may be also provided in environment (in ISC_USER and ISC_PASSWORD variables) like it was before.

Our examples contain complete samples, dedicated except others to creating databases – 01.create.cpp and 01.create.pas. When samples are present it will be very useful to build and try to run appropriate samples when reading this document.

# Starting transaction and executing trivial SQL operator in it.

Only creating empty databases is definitely not enough to work with RDBMS. We want to be able to create various objects (like tables and so on) in database and insert data in that tables. Any operation within database is performed by firebird under transaction control. Therefore first of all we must learn to start transaction.

Master interface:

1. IStatus* getStatus()

2. IProvider* getDispatcher()

3. IPluginManager* getPluginManager()

4. ITimerControl* getTimerControl()

5. IDtc* getDtc()

6. IUtil* getUtilInterface()

7. IConfigManager* getConfigManager()

Provider methods:

1. IAttachment* attachDatabase(StatusType* status, const char* fileName, unsigned dpbLength, const unsigned char* dpb)

2. IAttachment* createDatabase(StatusType* status, const char* fileName, unsigned dpbLength, const unsigned char* dpb)

3. IService* attachServiceManager(StatusType* status, const char* service, unsigned spbLength, const unsigned char* spb)

4. void shutdown(StatusType* status, unsigned timeout, const int reason)

   void setDbCryptCallback(StatusType* status, ICryptKeyCallback* cryptCallback)

Status interface:

1. void init()

2. unsigned getState()

3. void setErrors2(unsigned length, const intptr_t* value)

4. void setWarnings2(unsigned length, const intptr_t* value)

5. void setErrors(const intptr_t* value)

6. void setWarnings(const intptr_t* value)

7. const intptr_t* getErrors()

8. const intptr_t* getWarnings()

9. IStatus* clone()

Constants defined by Status interface:

STATE_WARNINGS -

STATE_ERRORS

RESULT_ERROR = -1;

RESULT_OK = 0;

RESULT_NO_DATA = 1;

RESULT_SEGMENT = 2;

Util methods:

1. void getFbVersion(StatusType* status, IAttachment* att, IVersionCallback* callback)

2. void loadBlob(StatusType* status, ISC_QUAD* blobId, IAttachment* att, ITransaction* tra, const char* file, FB_BOOLEAN txt)

3. void dumpBlob(StatusType* status, ISC_QUAD* blobId, IAttachment* att, ITransaction* tra, const char* file, FB_BOOLEAN txt)

4. void getPerfCounters(StatusType* status, IAttachment* att, const char* countersSet, ISC_INT64* counters)

5. IAttachment* executeCreateDatabase(StatusType* status, unsigned stmtLength, const char* creatDBstatement, unsigned dialect, FB_BOOLEAN* stmtIsCreateDb)

6. void decodeDate(ISC_DATE date, unsigned* year, unsigned* month, unsigned* day)

7. void decodeTime(ISC_TIME time, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions)

8. ISC_DATE encodeDate(unsigned year, unsigned month, unsigned day)

9. ISC_TIME encodeTime(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions)

10. unsigned formatStatus(char* buffer, unsigned bufferSize, IStatus* status)

11. unsigned getClientVersion()

12. IXpbBuilder* getXpbBuilder(StatusType* status, unsigned kind, const unsigned char* buf, unsigned len)

13. unsigned setOffsets(StatusType* status, IMessageMetadata* metadata, IOffsetsCallback* callback)

XpbBuilder methods:

1. void clear(StatusType* status)

2. void removeCurrent(StatusType* status)

3. void insertInt(StatusType* status, unsigned char tag, int value)

4. void insertBigInt(StatusType* status, unsigned char tag, ISC_INT64 value)

5. void insertBytes(StatusType* status, unsigned char tag, const void* bytes, unsigned length)

6. void insertTag(StatusType* status, unsigned char tag)

7. FB_BOOLEAN isEof(StatusType* status)

8. void moveNext(StatusType* status)

9. void rewind(StatusType* status)

10. FB_BOOLEAN findFirst(StatusType* status, unsigned char tag)

11. FB_BOOLEAN findNext(StatusType* status)

12. unsigned char getTag(StatusType* status)

13. unsigned getLength(StatusType* status)

14. int getInt(StatusType* status)

15. ISC_INT64 getBigInt(StatusType* status)

16. const char* getString(StatusType* status)

17. const unsigned char* getBytes(StatusType* status)

18. unsigned getBufferLength(StatusType* status)

19. const unsigned char* getBuffer(StatusType* status)