POCO Data Connectors Developer Guide
Author: Applied Informatics Software Engineering GmbH and Contributors
Date: 2009-11-24

# POCO Data Library

# POCO Data Connectors Developer Guide

## Contents

## Overview

Developing one's own Data Connector implementation is rather straight-forward. Just implement the following interfaces:

- Poco::Data::AbstractBinder
- Poco::Data::AbstractExtractor
- Poco::Data::StatementImpl
- Poco::Data::SessionImpl
- Poco::Data::Connector
- optional: Poco::Data::AbstractPreparation

It is recommended to implement the classes from top to down (ie. start with Binder and Extractor) and to use a namespace that has `Poco::Data` as parent, e.g.`Poco::Data::SQLite` .

## AbstractBinder

An `AbstractBinder` is a class that maps values to placeholders. It is also responsible to bind primitive C++ data types to database data types. The constructor of the subclass should receive everything needed to bind variables to placeholders by position. An example taken from the SQLite implementation would be:

    Binder::Binder(sqlite3_stmt* pStmt):
        _pStmt(pStmt)
    {
    }

    void Binder::bind(std::size_t pos, const Poco::Int32& val)
    {
        int rc = sqlite3_bind_int(_pStmt, (int)pos, val);
        checkReturn(rc);
    }

    void Binder::bind(std::size_t pos, const Poco::Int16& val)
    {
        Poco::Int32 tmp = val;
        bind(pos, tmp);
    }

SQLite only needs an sqlite3_stmt as internal state, Int32 is bound via sqlite3_bind_int and Int16 values are mapped to Int32 values.

### Complete Interface

All methods are public.

    AbstractBinder();
        /// Creates the AbstractBinder.

    virtual ~AbstractBinder();
        /// Destroys the AbstractBinder.

    virtual void bind(std::size_t pos, const Poco::Int8 &val) = 0;
        /// Binds an Int8.

    virtual void bind(std::size_t pos, const Poco::UInt8 &val) = 0;
        /// Binds an UInt8.

    virtual void bind(std::size_t pos, const Poco::Int16 &val) = 0;
        /// Binds an Int16.

    virtual void bind(std::size_t pos, const Poco::UInt16 &val) = 0;
        /// Binds an UInt16.

    virtual void bind(std::size_t pos, const Poco::Int32 &val) = 0;
        /// Binds an Int32.

    virtual void bind(std::size_t pos, const Poco::UInt32 &val) = 0;
        /// Binds an UInt32.

    virtual void bind(std::size_t pos, const Poco::Int64 &val) = 0;
        /// Binds an Int64.

    virtual void bind(std::size_t pos, const Poco::UInt64 &val) = 0;
        /// Binds an UInt64.

    virtual void bind(std::size_t pos, const bool &val) = 0;
        /// Binds a boolean.

    virtual void bind(std::size_t pos, const float &val) = 0;
        /// Binds a float.

    virtual void bind(std::size_t pos, const double &val) = 0;
        /// Binds a double.

    virtual void bind(std::size_t pos, const char &val) = 0;
        /// Binds a single character.

    virtual void bind(std::size_t pos, const char* const &pVal) = 0;
        /// Binds a const char ptr.

    virtual void bind(std::size_t pos, const std::string& val) = 0;
        /// Binds a string.

    virtual void bind(std::size_t pos, const BLOB& val) = 0;
        /// Binds a BLOB.

    virtual void reset() = 0;
        /// Resets the internal state, called before a rebind

## AbstractExtractor

An `AbstractExtractor` takes a result row and extracts from a given position one single value. It performs the reverse operation to the `AbstractBinder`, ie. it maps database types to primitive C++ types. An `AbstractExtractor` also has to handle null values. If it detects a null value, it is not allowed to modify the incoming value but will simply return false. An example taken from the SQLite implementation:

    Extractor::Extractor(sqlite3_stmt* pStmt):
        _pStmt(pStmt)
    {
    }

    bool Extractor::extract(std::size_t pos, Poco::Int32& val)
    {
        if (isNull(pos<[
            return false;
        val = sqlite3_column_int(_pStmt, (int)pos);
        return true;
    }

    bool Extractor::extract(std::size_t pos, Poco::Int16& val)
    {
        if (isNull(pos<[
            return false;
        val = sqlite3_column_int(_pStmt, (int)pos);
        return true;
    }

### Complete Interface

All methods are public.

    AbstractExtractor();
        /// Creates the AbstractExtractor.

    virtual ~AbstractExtractor();
        /// Destroys the AbstractExtractor.

    virtual bool extract(std::size_t pos, Poco::Int8& val) = 0;
        /// Extracts an Int8. Returns false if null was received.

    virtual bool extract(std::size_t pos, Poco::UInt8& val) = 0;
        /// Extracts an UInt8. Returns false if null was received.

    virtual bool extract(std::size_t pos, Poco::Int16& val) = 0;
        /// Extracts an Int16. Returns false if null was received.

    virtual bool extract(std::size_t pos, Poco::UInt16& val) = 0;
        /// Extracts an UInt16. Returns false if null was received.

    virtual bool extract(std::size_t pos, Poco::Int32& val) = 0;
        /// Extracts an Int32. Returns false if null was received.

    virtual bool extract(std::size_t pos, Poco::UInt32& val) = 0;
        /// Extracts an UInt32. Returns false if null was received.

    virtual bool extract(std::size_t pos, Poco::Int64& val) = 0;
        /// Extracts an Int64. Returns false if null was received.

    virtual bool extract(std::size_t pos, Poco::UInt64& val) = 0;
        /// Extracts an UInt64. Returns false if null was received.

    virtual bool extract(std::size_t pos, bool& val) = 0;
        /// Extracts a boolean. Returns false if null was received.

    virtual bool extract(std::size_t pos, float& val) = 0;
        /// Extracts a float. Returns false if null was received.

    virtual bool extract(std::size_t pos, double& val) = 0;
        /// Extracts a double. Returns false if null was received.

    virtual bool extract(std::size_t pos, char& val) = 0;
        /// Extracts a single character. Returns false if null was received.

    virtual bool extract(std::size_t pos, std::string& val) = 0;
        /// Extracts a string. Returns false if null was received.

    virtual bool extract(std::size_t pos, BLOB& val) = 0;
        /// Extracts a BLOB. Returns false if null was received.

## AbstractPreparation

`AbstractPreparation` is an optional interface responsible for preparing an extract. If you need it depends on the `DataConnector` you implement. For example, SQLite can do perfectly without it, ODBC instead requires it. SQLite doesn't need it because it works as follows:

- sendQuery
- getNextResult
- extract single row values from result set

This works because SQLites getNextResult provides the data as string, i.e. it doesn't need any type information.

The ODBC implementation is different:

- register/prepare for each column an output location
- getNextResult
- extract for each row the value by copying the content of the previously registered output location

`AbstractPreparation` is responsible for the first step. A typical prepare implementation will look like that:

    void prepare(std::size_t pos, Poco::Int32 val)
    {
       _myVec[pos] =  Poco::Any a(val);
       int* i = AnyCast<int>(&_myVec[pos]);
       //register int* i for output, Db specific
    }

Extract now changes to:

    bool Extractor::extract(std::size_t pos, Poco::Int16& val)
    {
        if (isNull(pos))
            return false;
        val = AnyCast<int>(_myVec[pos]);
        return true;
    }

### Complete Interface

    AbstractPreparation();
        /// Creates the AbstractPreparation.

    virtual ~AbstractPreparation();
        /// Destroys the AbstractPreparation.

    virtual void prepare(std::size_t pos, Poco::Int8) = 0;
        /// Prepares an Int8.

    virtual void prepare(std::size_t pos, Poco::UInt8) = 0;
        /// Prepares an UInt8.

    virtual void prepare(std::size_t pos, Poco::Int16) = 0;
        /// Prepares an Int16.

    virtual void prepare(std::size_t pos, Poco::UInt16) = 0;
        /// Prepares an UInt16.

    virtual void prepare(std::size_t pos, Poco::Int32) = 0;
        /// Prepares an Int32.

    virtual void prepare(std::size_t pos, Poco::UInt32) = 0;
        /// Prepares an UInt32.

    virtual void prepare(std::size_t pos, Poco::Int64) = 0;
        /// Prepares an Int64.

    virtual void prepare(std::size_t pos, Poco::UInt64) = 0;
        /// Prepares an UInt64.

    virtual void prepare(std::size_t pos, bool) = 0;
        /// Prepares a boolean.

    virtual void prepare(std::size_t pos, float) = 0;
        /// Prepares a float.

    virtual void prepare(std::size_t pos, double) = 0;
        /// Prepares a double.

    virtual void prepare(std::size_t pos, char) = 0;
        /// Prepares a single character.

    virtual void prepare(std::size_t pos, const std::string& ) = 0;
        /// Prepares a string.

    virtual void prepare(std::size_t pos, const BLOB&) = 0;

Note that it is recommended to prepare a statement only once in the compileImpl of `StatementImpl`. The AbstractPrepare objects (which make use of `AbstractPreparation` can be created by iterating over the Extractor objects of the StatementImpl:

    Poco::Data::AbstractExtractingVec::iterator it = extractings().begin();
    Poco::Data::AbstractExtractingVec::iterator itEnd = extractings().end();
    std::size_t pos = 0; // sqlite starts with pos 0 for results! your DB maybe with 1
    for (; it != itEnd; ++it)
    {
         AbstractPrepare* pPrep = (*it)->createPrepareObject(pPreparation, pos);
        _prepareVec.push_back(pPrep);
        (*it)->extract(pos);
        pos += (*it)->numOfColumnsHandled();
    }

## StatementImpl

A `StatementImpl` stores as member a Binder and an Extractor (optional a Preparation object) and is responsible for compiling, binding, fetching single rows from the database and invoking the Extracting objects. The interface it has to implement is given as:

    public:
        StatementImpl();
            /// Creates the StatementImpl.

        virtual ~StatementImpl();
            /// Destroys the StatementImpl.

    protected:
        virtual bool hasNext() = 0;
            /// Returns true if a call to next() will return data. Note that the
            /// implementation must support several consecutive calls to hasNext
            /// without data getting lost, ie. hasNext(); hasNext(); next() must
            /// be equal to hasNext(); next();

        virtual void next() = 0;
            /// Retrieves the next row from the resultset.
            /// Will throw, if the resultset is empty.
            /// Expects the statement to be compiled and bound

        virtual bool canBind() const = 0;
            /// Returns if another bind is possible.

        virtual void compileImpl() = 0;
            /// Compiles the statement, doesn't bind yet.
            /// From now on AbstractBinder and AbstractExtractor
            /// will be used

        virtual void bindImpl() = 0;
            /// Binds parameters.

        virtual AbstractExtractor& extractor() = 0;
            /// Returns the concrete extractor used by the statement.

        virtual AbstractBinder& binder() = 0;
            /// Returns the concrete binder used by the statement.

The Extracting and Binding objects can be accessed via the calls to the super-class methods extractings() and bindings(). A high-level bind implementation will look like this:

    [...]
    Poco::Data::AbstractBindingVec& binds = bindings();
    std::size_t pos = 1; // or 0 depending on your database
    Poco::Data::AbstractBindingVec::iterator it = binds.begin();
    Poco::Data::AbstractBindingVec::iterator itEnd = binds.end();
    for (; it != itEnd && (*it)->canBind(); ++it)
    {
        (*it)->bind(pos);
        pos += (*it)->numOfColumnsHandled();
    }

A high-level next implementation:

    if (!hasNext())
        throw Poco::Data::DataException("No data received");
    int nCol = countColumnsInResult...;
    poco_assert (columnsHandled() == nCol);
    Poco::Data::AbstractExtractingVec::iterator it = extractings().begin();
    Poco::Data::AbstractExtractingVec::iterator itEnd = extractings().end();
    std::size_t pos = 0; // sqlite starts with pos 0 for results! your DB maybe with 1
    for (; it != itEnd; ++it)
    {
        (*it)->extract(pos);
        pos += (*it)->numOfColumnsHandled();
    }
    enableHasNext();

A high-level hasNext implementation:

    if (enabledhasNext())
    {
        checkIfItHasMoreData
        cacheResult
        disablehasNext()
    }

    return cachedResult;

A high-level compileImpl:

    if (compiled)
        return;

    std::string sqlStmt(toString());

    f database expects placeholders in different format than ":name", parse and replace them

    compile statement;

    create Binder;
    create Extractor;

A high-level canBind:

    bool ret = false;
    if (!bindings().empty() && validCompiledStatement)
        ret = (*bindings().begin())->canBind();

    return ret;

## SessionImpl

The purpose of the `SessionImpl` is simply to open/close a connection to the database, to act as factory for `StatementImpl` objects, and to handle transactions. The connection is opened in the constructor, and closed in the destructor.

    Poco::Data::StatementImpl* createStatementImpl();
        /// Returns an SQLite StatementImpl

    void begin();
        /// Starts a transaction

    void commit();
        /// Commits and ends a transaction

    void rollback();
        /// Aborts a transaction

## Connector

Finally, one needs to implement the `Connector`. Each `Connector` should have a public static const string member named KEY and must have a factory method to create `Poco::AutoPtr` objects of type `SessionImpl`. It should also have a static addToFactory() and a static removeFromFactory() method:

    class My_API Connector: public Poco::Data::Connector
        /// Connector instantiates SessionImpl objects.
    {
    public:
        static const std::string KEY;
            /// Keyword for creating sessions

        Connector();
            /// Creates the Connector.

        ~Connector();
        /// Destroys the Connector.

        Poco::AutoPtr < Poco::Data::SessionImpl > createSession(const std::string& connectionString);
            /// Creates a SessionImpl object and initializes it with the given connectionString.

        static void registerConnector();
            /// Registers the Connector under the Keyword Connector::KEY at the Poco::Data::SessionFactory

        static void unregisterConnector();
            /// Unregisters the Connector under the Keyword Connector::KEY at the Poco::Data::SessionFactory
    };