
namespace DB
{
class Db;
typedef std::tr1::shared_ptr 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;
};
}
Пользовательский интерфейс очевиден, нам как разработчикам интересна реализация
механизма pimpl, а также производство наследников. Pimpl реализован очень просто.
Имеется класс (описанный в хидере db/dbimpl.h) DbImpl содержащий объекты БД
(например IBPP::Database) для каждой СУБД:
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_ptr TrPtr;
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 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() {}
};
}
Как видно для оперирования блоками данных имеется структура DF::DataBlock доступная только через shared_ptr.
Рассмотрим функции класса:
void Open(const IOMode &mode) - Открывает файл на чтение либо на запись. При открытии на запись, файл затирается.
void Close() - Закрывает файл. Если файл был открыт на запись, буферы сбрасываются на диск.
void WriteBlock(DataBlockPtr db) - Добавляет блок в конец файла.
bool Fetch() - Возвращает true если не достигнут конец файла. Курсор не перемещается.
DataBlockPtr ReadBlock() - Возвращает блок данных и перемещает курсор к следующему.
PA::Params
PA::Params - это класс инкапсулирующий всё необходимые для работы программы параметры.
Он принимает параметры коммандной строки, а так-же читает XML конфиг. Для парсинга XML используется библиотека pugixml.