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.
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:
IAttachment* 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.
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. Here we do not discuss distributed transactions (supported by IDtc interface) to avoid unneeded to most users overcomplication. Starting of non-distributed transaction is very simple and done via attachment interface:
ITransaction* tra = att->startTransaction(&status, 0, NULL);
In this sample default transaction parameters are used – no TPB is passed to startTransaction() method. If you need non-default parameters you may create appropriate IXpbBuilder, add required items to it:
IXpbBuilder* tpb = utl->getXpbBuilder(&status, IXpbBuilder::TPB, NULL, 0);
tpb->insertTag(&status, isc_tpb_read_committed);
and pass resulting TPB to startTransaction():
ITransaction* tra = att->startTransaction(&status, tpb->getBufferLength(&status), tpb->getBuffer(&status));
Transaction interface is used as a parameter in a lot of other API calls but itself it does not perform any actions except commit/rollback transaction, may be retaining:
tra->commit(&status);
You may take a look at how to start and commit transaction in examples 01.create.cpp and 01.create.pas.
With started transaction we are ready to execute our first SQL operators. Used for it execute() method in IAttachment is rather universal and may be also used to execute SQL operators with input and output parameters (which is typical for EXECUTE PROCEDURE statement), but right now we will use the simple most form of it. Both DDL and DML operators can be executed:
att->execute(&status, tra, 0, "create table dates_table (d1 date)", SQL_DIALECT_V6, NULL, NULL, NULL, NULL);
tra->commitRetaining(&status);
att->execute(&status, tra, 0, "insert into dates_table values (CURRENT_DATE)", SQL_DIALECT_V6, NULL, NULL, NULL, NULL);
As you can see transaction interface is a required parameter for execute() method (must be NULL only if you execute START TRANSACTION statement). Next follows length of SQL operator (may be zero causing use of C rules to determine string length), text of operator and SQL dialect that should be used for it. The following for NULLs stand for metadata descriptions and buffers of input parameters and output data. Complete description of this method is provided in IAttachment interface.
You may take a look at how to start and commit transaction in examples 01.create.cpp and 01.create.pas.
There are 2 ways to execute statement with input parameters. Choice of correct method depends upon do you need to execute it more than once and do you know in advance format of parameters. When that format is known and statement is needed to be run only once single call to IAttachment::execute() may be used. In other cases SQL statement should be prepared first and after it executed, may be many times with different parameters.
To prepare SQL statement for execution use prepare() method of IAttachment interface:
IStatement* stmt = att->prepare(&status, tra, 0, “UPDATE department SET budget = ? * budget + budget WHERE dept_no = ?”,
SQL_DIALECT_V6, IStatement::PREPARE_PREFETCH_METADATA);
If you are not going to use parameters description from firebird (i.e. format of parameters is known to you in advance) please use IStatement::PREPARE_PREFETCH_NONE instead PREPARE_PREFETCH_METADATA – this will save client/server traffic and server resources a bit.
In ISC API XSQLDA is used to describe format of statement parameters. New API does not use XSQLDA – instead interface IMessageMetadata is used. A set of input parameters (and also a row fetched from cursor) is described in firebird API in same way and later called message. IMessageMetadata is passed as a parameter to the methods performing message exchange between your program and database engine. There are many ways to have an instance of IMessageMetadata – one can:
get it from IStatement,
build using IMetadataBuilder interface,
have your own implementation of this interface.
We will not discuss own implementation here, one may take a look at example 05.user_metadata.cpp for details.
void getInfo(StatusType* status, unsigned itemsLength, const unsigned char* items, unsigned bufferLength, unsigned char* buffer)
ITransaction* startTransaction(StatusType* status, unsigned tpbLength, const unsigned char* tpb)
ITransaction* reconnectTransaction(StatusType* status, unsigned length, const unsigned char* id)
IRequest* compileRequest(StatusType* status, unsigned blrLength, const unsigned char* blr)
void transactRequest(StatusType* status, ITransaction* transaction, unsigned blrLength, const unsigned char* blr, unsigned inMsgLength, const unsigned char* inMsg, unsigned outMsgLength, unsigned char* outMsg)
IBlob* createBlob(StatusType* status, ITransaction* transaction, ISC_QUAD* id, unsigned bpbLength, const unsigned char* bpb)
IBlob* openBlob(StatusType* status, ITransaction* transaction, ISC_QUAD* id, unsigned bpbLength, const unsigned char* bpb)
int getSlice(StatusType* status, ITransaction* transaction, ISC_QUAD* id, unsigned sdlLength, const unsigned char* sdl, unsigned paramLength, const unsigned char* param, int sliceLength, unsigned char* slice)
void putSlice(StatusType* status, ITransaction* transaction, ISC_QUAD* id, unsigned sdlLength, const unsigned char* sdl, unsigned paramLength, const unsigned char* param, int sliceLength, unsigned char* slice)
void executeDyn(StatusType* status, ITransaction* transaction, unsigned length, const unsigned char* dyn)
IStatement* prepare(StatusType* status, ITransaction* tra, unsigned stmtLength, const char* sqlStmt, unsigned dialect, unsigned flags)
ITransaction* execute(StatusType* status, ITransaction* transaction, unsigned stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, void* outBuffer)
IResultSet* openCursor(StatusType* status, ITransaction* transaction, unsigned stmtLength, const char* sqlStmt, unsigned dialect, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, const char* cursorName, unsigned cursorFlags)
IEvents* queEvents(StatusType* status, IEventCallback* callback, unsigned length, const unsigned char* events)
void cancelOperation(StatusType* status, int option)
void ping(StatusType* status)
void detach(StatusType* status)
void dropDatabase(StatusType* status)
ITransaction* join(StatusType* status, ITransaction* one, ITransaction* two)
IDtcStart* startBuilder(StatusType* status)
void addAttachment(StatusType* status, IAttachment* att)
void addWithTpb(StatusType* status, IAttachment* att, unsigned length, const unsigned char* tpb)
ITransaction* start(StatusType* status)
IStatus* getStatus()
IProvider* getDispatcher()
IPluginManager* getPluginManager()
ITimerControl* getTimerControl()
IDtc* getDtc()
IUtil* getUtilInterface()
IConfigManager* getConfigManager()
unsigned getCount(StatusType* status)
const char* getField(StatusType* status, unsigned index)
const char* getRelation(StatusType* status, unsigned index)
const char* getOwner(StatusType* status, unsigned index)
const char* getAlias(StatusType* status, unsigned index)
unsigned getType(StatusType* status, unsigned index)
FB_BOOLEAN isNullable(StatusType* status, unsigned index)
int getSubType(StatusType* status, unsigned index)
unsigned getLength(StatusType* status, unsigned index)
int getScale(StatusType* status, unsigned index)
unsigned getCharSet(StatusType* status, unsigned index)
unsigned getOffset(StatusType* status, unsigned index)
unsigned getNullOffset(StatusType* status, unsigned index)
IMetadataBuilder* getBuilder(StatusType* status)
unsigned getMessageLength(StatusType* status)
void setType(StatusType* status, unsigned index, unsigned type)
void setSubType(StatusType* status, unsigned index, int subType)
void setLength(StatusType* status, unsigned index, unsigned length)
void setCharSet(StatusType* status, unsigned index, unsigned charSet)
void setScale(StatusType* status, unsigned index, unsigned scale)
void truncate(StatusType* status, unsigned count)
void moveNameToIndex(StatusType* status, const char* name, unsigned index)
void remove(StatusType* status, unsigned index)
unsigned addField(StatusType* status)
IMessageMetadata* getMetadata(StatusType* status)
IAttachment* attachDatabase(StatusType* status, const char* fileName, unsigned dpbLength, const unsigned char* dpb)
IAttachment* createDatabase(StatusType* status, const char* fileName, unsigned dpbLength, const unsigned char* dpb)
IService* attachServiceManager(StatusType* status, const char* service, unsigned spbLength, const unsigned char* spb)
void shutdown(StatusType* status, unsigned timeout, const int reason)
void setDbCryptCallback(StatusType* status, ICryptKeyCallback* cryptCallback)
void getInfo(StatusType* status, unsigned itemsLength, const unsigned char* items, unsigned bufferLength, unsigned char* buffer)
unsigned getType(StatusType* status)
const char* getPlan(StatusType* status, FB_BOOLEAN detailed)
ISC_UINT64 getAffectedRecords(StatusType* status)
IMessageMetadata* getInputMetadata(StatusType* status)
IMessageMetadata* getOutputMetadata(StatusType* status)
ITransaction* execute(StatusType* status, ITransaction* transaction, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, void* outBuffer)
IResultSet* openCursor(StatusType* status, ITransaction* transaction, IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, unsigned flags)
void setCursorName(StatusType* status, const char* name)
void free(StatusType* status)
unsigned getFlags(StatusType* status)
Constants defined by Statement interface:
PREPARE_PREFETCH_NONE = 0;
PREPARE_PREFETCH_TYPE = 1;
PREPARE_PREFETCH_INPUT_PARAMETERS = 2;
PREPARE_PREFETCH_OUTPUT_PARAMETERS = 4;
PREPARE_PREFETCH_LEGACY_PLAN = 8;
PREPARE_PREFETCH_DETAILED_PLAN = 16;
PREPARE_PREFETCH_AFFECTED_RECORDS = 32;
PREPARE_PREFETCH_FLAGS = 64;
PREPARE_PREFETCH_METADATA
PREPARE_PREFETCH_ALL
FLAG_HAS_CURSOR = 1;
FLAG_REPEAT_EXECUTE = 2;
CURSOR_TYPE_SCROLLABLE = 1;
void init()
unsigned getState()
void setErrors2(unsigned length, const intptr_t* value)
void setWarnings2(unsigned length, const intptr_t* value)
void setErrors(const intptr_t* value)
void setWarnings(const intptr_t* value)
const intptr_t* getErrors()
const intptr_t* getWarnings()
IStatus* clone()
Constants defined by Status interface:
STATE_WARNINGS -
STATE_ERRORS
RESULT_ERROR = -1;
RESULT_OK = 0;
RESULT_NO_DATA = 1;
RESULT_SEGMENT = 2;
void getInfo(StatusType* status, unsigned itemsLength, const unsigned char* items, unsigned bufferLength, unsigned char* buffer)
void prepare(StatusType* status, unsigned msgLength, const unsigned char* message)
void commit(StatusType* status)
void commitRetaining(StatusType* status)
void rollback(StatusType* status)
void rollbackRetaining(StatusType* status)
void disconnect(StatusType* status)
ITransaction* join(StatusType* status, ITransaction* transaction)
ITransaction* validate(StatusType* status, IAttachment* attachment)
ITransaction* enterDtc(StatusType* status)
void getFbVersion(StatusType* status, IAttachment* att, IVersionCallback* callback)
void loadBlob(StatusType* status, ISC_QUAD* blobId, IAttachment* att, ITransaction* tra, const char* file, FB_BOOLEAN txt)
void dumpBlob(StatusType* status, ISC_QUAD* blobId, IAttachment* att, ITransaction* tra, const char* file, FB_BOOLEAN txt)
void getPerfCounters(StatusType* status, IAttachment* att, const char* countersSet, ISC_INT64* counters)
IAttachment* executeCreateDatabase(StatusType* status, unsigned stmtLength, const char* creatDBstatement, unsigned dialect, FB_BOOLEAN* stmtIsCreateDb)
void decodeDate(ISC_DATE date, unsigned* year, unsigned* month, unsigned* day)
void decodeTime(ISC_TIME time, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions)
ISC_DATE encodeDate(unsigned year, unsigned month, unsigned day)
ISC_TIME encodeTime(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions)
unsigned formatStatus(char* buffer, unsigned bufferSize, IStatus* status)
unsigned getClientVersion()
IXpbBuilder* getXpbBuilder(StatusType* status, unsigned kind, const unsigned char* buf, unsigned len)
unsigned setOffsets(StatusType* status, IMessageMetadata* metadata, IOffsetsCallback* callback)
void clear(StatusType* status)
void removeCurrent(StatusType* status)
void insertInt(StatusType* status, unsigned char tag, int value)
void insertBigInt(StatusType* status, unsigned char tag, ISC_INT64 value)
void insertBytes(StatusType* status, unsigned char tag, const void* bytes, unsigned length)
void insertTag(StatusType* status, unsigned char tag)
FB_BOOLEAN isEof(StatusType* status)
void moveNext(StatusType* status)
void rewind(StatusType* status)
FB_BOOLEAN findFirst(StatusType* status, unsigned char tag)
FB_BOOLEAN findNext(StatusType* status)
unsigned char getTag(StatusType* status)
unsigned getLength(StatusType* status)
int getInt(StatusType* status)
ISC_INT64 getBigInt(StatusType* status)
const char* getString(StatusType* status)
const unsigned char* getBytes(StatusType* status)
unsigned getBufferLength(StatusType* status)
const unsigned char* getBuffer(StatusType* status)
Constants defined by XpbBuilder interface:
static const unsigned DPB = 1;
static const unsigned SPB_ATTACH = 2;
static const unsigned SPB_START = 3;
static const unsigned TPB = 4;