namespace DB { class Db; typedef std::tr1::shared_ptrПользовательский интерфейс очевиден, нам как разработчикам интересна реализация механизма pimpl, а также производство наследников. Pimpl реализован очень просто. Имеется класс (описанный в хидере db/dbimpl.h) DbImpl содержащий объекты БД (например IBPP::Database) для каждой СУБД:DbPtr; class Db { public: void SetServer(const std::string &value) { server = value; } std::string GetServer() { return server; } void SetDatabase(const std::string &value) { database = value; } std::string GetDatabase() { return database; } void SetUser(const std::string &value) { user = value; } std::string GetUser() { return user; } void SetPassword(const std::string &value) { password = value; } std::string GetPassword() { return password; } void SetRole(const std::string &value) { role = value; } std::string GetRole() { return role; } void SetCharset(const std::string &value) { charset = value; } std::string GetCharset() { return charset; } virtual bool Connected() = 0; virtual void Connect() = 0; virtual void Disconnect() = 0; static DbPtr make(const std::string &); virtual ~Db() {} protected: std::string server, database, user, password, role, charset; bool connectdue; typedef std::tr1::shared_ptr ImplPtr; ImplPtr pimpl; void MakeImpl(); Db() {} public: ImplPtr GetImpl() { return pimpl; } typedef Tr* (*CreateTrCallback)(DbPtr); typedef Query* (*CreateQueryCallback)(DbPtr, TrPtr); CreateTrCallback CreateTr; CreateQueryCallback CreateQuery; }; }
namespace DB { class DbImpl { public: #ifdef USE_FB IBPP::Database fbdb; #endif #ifdef USE_MYSQL MYSQL *mysqldb; #endif DbImpl() { ++guid; id = guid; } int GetId() { return id; } private: static int guid; int id; }; }Конструктор класса задает уникальный id для объекта, этот id нужен для определения при создании DB::Query принадлежит ли объект транзакции объекту БД. Поскольку создание объектов физических БД в DbImpl происходит в члене Db Connect() введена переменная connectdue. Её нужно использовать в реализации Connected() и вызывать pimpl->mydb->Connected() только если она true. Для создания pimpl в Db, конструкторы наследников должны вызвать метод MakeImpl().
namespace DB { class Tr; std::tr1::shared_ptrTrPtr; class Tr { public: virtual void Start() = 0; virtual void Commit() = 0; virtual void Rollback() = 0; static TrPtr make(DbPtr); virtual ~Tr() {} protected: typedef std::tr1::shared_ptr ImplPtr; ImplPtr pimpl; void MakeImpl(const int&); Tr() {} public: ImplPtr GetImpl() { return pimpl; } }; } Пользовательский интерфейс стандартен для sql транзакций. TrImpl реализован аналогично DbImpl, единственное отличие, он запоминает id объекта Db. Интересна реализация производящей функции (она использует указатель на функцию, член Db, CreateTr): using namespace DB; void CheckDBConnected(DbPtr db) { if (!db->Connected()) { throw EXC::exception("Database instance not connected."); } } TrPtr Tr::make(DbPtr db) { CheckDBConnected(db); return TrPtr(db->CreateTr(db)); }
DB::Query
DB::Query - основной интерфейс к sql данным. Он обрабатывает sql запросы и позволяет получить доступ к результирующей выборке, а так-же используется для выполнения запросов update, insert и delete. В отличие от Db и Tr идиома pimpl здесь не используется, класс является обычным абстрактным базовым с невиртуальным интерфейсом. Наследники должны реализовать только виртуальные функции. Через pimpl Db и Tr имеется возможность получить доступ к физическому интерфейсу БД и транзакции. Поскольку все наследники должны реализовать определенное поведение, класс будет рассмотрен максимально подробно. Определение класса:namespace DB { struct Date { int day, month, year; }; struct Time { int sec, min, hour; }; struct DateTime { int day, month, year, sec, min, hour;; }; enum FieldType { int_, float_, double_, string_, date_, time_, timestamp_, blob_ }; class Query; typedef std::tr1::shared_ptrРасмотрим подробнее каждую функцию член:QueryPtr; class Query { public: virtual void SetSQL(const std::string &sql) = 0; virtual void SetParam(const size_t ord, const int value) = 0; //ord - param order, start from 0 virtual void SetParam(const size_t ord, const float &value) = 0; virtual void SetParam(const size_t ord, const double &value) = 0; virtual void SetParam(const size_t ord, const std::string &value) = 0; virtual void SetParam(const size_t ord, const Date &value) = 0; virtual void SetParam(const size_t ord, const Time &value) = 0; virtual void SetParam(const size_t ord, const DateTime &value) = 0; virtual void SetBlobParam(const size_t ord, const std::string &value) = 0; virtual void ExecSQL() = 0; virtual void Open() = 0; virtual void Close() = 0; virtual size_t GetFieldsCount() = 0; virtual std::string GetFieldName(const size_t field_n) = 0; virtual FieldType GetFieldType(const size_t field_n) = 0; virtual bool Fetch() = 0; virtual int GetIntFieldValue(const size_t field_n) = 0; virtual float GetFloatFieldValue(const size_t field_n) = 0; virtual double GetDoubleFieldValue(const size_t field_n) = 0; virtual std::string GetStrFieldValue(const size_t field_n) = 0; virtual Date GetDateFieldValue(const size_t field_n) = 0; virtual Time GetTimeFieldValue(const size_t field_n) = 0; virtual DateTime GetDateTimeFieldValue(const size_t field_n) = 0; virtual std::string GetBlobFieldValue(const size_t field_n) = 0; static QueryPtr make(DbPtr, TrPtr); virtual ~Query() {} protected: Query() {} }; }
void SetSQL(const std::string &sql) - Устанавливает sql запрос. Запрос может быть параматерическим. Для обозначения параметра используется символ ?. Например: select * from table where id = ?
void SetParam(const int ord, const TYPE &value) - Устанавливает значение параметра с номером ord. Нумерация параметров начинается с нуля. Имеется 7 перегруженных функций для каждого поддерживаемого типа поля.
void SetBlobParam(const int ord, const std::string &value) - Аналогично, SetParam, но предназначена специально для blob полей.
void ExecSQL() - Запуск не возвращающего данных sql запроса.
void Open() - Запуск sql запроса, возвращающего данные.
void Close() - Закрытие набора данных и очистка кэша.
int GetFieldsCount() - Возвращает количество полей в выборке.
std::string GetFieldName(const int n) - Возвращает имя поля, по номеру в выборке. Нумерация с 0.
FieldType GetFieldType(const int n) - Возвращает тип поля
bool Fetch() - Перемещает "курсор" в выборке на одну позицию вниз и возвращает true если не достигнут конец выборки.
int GetIntFieldValue(const int n) - Возвращает значение поля типа int с номером n (нумерация с 0). Для остальных типов аналогично.
std::string GetStrFieldValue(const int n) - Возвращает значение поля в виде строки. Эта функция должна возвращать данные для ВСЕХ типов в преобразованном к string виду.
Реализация фабричного метода аналогична Tr::make:using namespace DB; QueryPtr Query::make(DbPtr db, TrPtr tr) { CheckDBConnected(db); if (db->GetImpl()->GetId() != tr->GetImpl()->GetDbId()) { throw EXC::exception("Transaction belongs to another object Db."); } return QueryPtr(db->CreateQuery(db, tr)); }Как видно, здесь осуществляется проверка подключена ли БД и соответсвует ли транзакция указанному объекту БД.
Как добавить поддержку своей СУБД
1. Создать файлы mydb.h mydb.cpp myquery.h и myquery.cpp в директории db в которых написать определения и реализации наследников Db, Tr и Query. Сделать #include "db/mydb.h" и "db/myquery.h" в db.cpp
2. Добавить в DbImpl и TrImpl члены - указатели на физическую реализацию своей СУБД.
3. Cделать директивы препроцессора для возможности отключения сборки с вашей СУБД и #include в db/dbimpl.h, а так-же сгенерировать новый Makefile
4. Выбрать символьный идентификатор для фабрики DbFactory (например "mydb") (он же будет использоваться в файле параметров)
5. В реализации mydb.cpp сделать производителей для myDb, myTr и myQuery.
6. Зарегистрировать производителя myDb в фабрике DbFactory
7. В конструкторе myDb вызвать MakeImpl(); для создания pimpl и инициализировать CreateTr и CreateQuery указателями на производителей.namespace { DB::Db* CreateMyDb() { return new myDb; } DB::Tr* CreateMyTr(DB::DbPtr db) { return new myTr(db); } DB::Query* CreateMyQuery(DB::DbPtr db, DB::TrPtr tr) { return new myQuery(db, tr); } const bool registered = DB::DbFactory::Instance().RegisterDb("mydb", &CreateMyDb); }; myDb::myDb() { MakeImpl(); id_type = "mydb"; CreateTr = &CreateMyTr; CreateQuery = &CreateMyQuery; }
DF::Df
Df - это класс работы с файлом данных. Данные в файле представлены в виде блоков, которые имеют следующую структуру:
tsize data
t - символ определяющий тип блока, t - таблица, f - поле, d - данные
size - размер данных блока. После размера всегда должен быть пробел
data - непосредственно данные блока. Для блоков с типом t и f в них содержится название таблицы либо поля, для блоков типа d - собственно данные.
Блоки следуют в файле следующим образом:
<таблица><поле><данные><поле><данные> <таблица><поле><данные><поле><данные>
Собственно класс DF::Df позволяет последовательно читать блоки данных из файла и последовательно их добавлять. Рассмотрим его интерфейс:namespace DF { class Df; struct DataBlock; typedef boost::shared_ptrКак видно для оперирования блоками данных имеется структура DF::DataBlock доступная только через shared_ptr.DfPtr; typedef boost::shared_ptr DataBlockPtr; struct DataBlock { public: enum BlockType { t, f, d }; BlockType type; std::string data; DataBlock() {} private: DataBlock(const DataBlock&) {} DataBlock& operator=(const DataBlock&) {} }; enum IOMode { read, write }; class Df { public: void WriteBlock(DataBlockPtr db); bool Fetch(); DataBlockPtr ReadBlock(); void Open(const IOMode &mode); void Close(); static DfPtr make(const std::string &filename); virtual ~Df() {} }; }
Рассмотрим функции класса:
void Open(const IOMode &mode) - Открывает файл на чтение либо на запись. При открытии на запись, файл затирается.
void Close() - Закрывает файл. Если файл был открыт на запись, буферы сбрасываются на диск.
void WriteBlock(DataBlockPtr db) - Добавляет блок в конец файла.
bool Fetch() - Возвращает true если не достигнут конец файла. Курсор не перемещается.
DataBlockPtr ReadBlock() - Возвращает блок данных и перемещает курсор к следующему.
PA::Params
PA::Params - это класс инкапсулирующий всё необходимые для работы программы параметры. Он принимает параметры коммандной строки, а так-же читает XML конфиг. Для парсинга XML используется библиотека pugixml.