Собственно, очень интересует, как по-нормальному реализовать сохранение/загрузку SQLite БД из памяти / в память.
Сейчас я покажу, как делать не надо.
Примеры нехорошие, но рабочие.
Я их показываю только потому, что пока что иных рабочих способов не нашёл.
Нехороший рабочий пример записи SQLite БД из :memory: в файл
bool writeDBMemory2File( QString filename )
{
// Указанный для записи файл должен быть предварительно уничтожен
QFile file( filename );
if( file.exists() ) file.remove();
// Берём подключение к исходной БД. Подразумевается, что БД уже открыта.
// БД может быть расположена в :memory:.
QSqlDatabase srcdb = QSqlDatabase::database( "main_db" );
// Конструируем запросники для srcdb
QSqlQuery srcQuery( srcdb ), srcSubQuery( srcdb );
// Приаттачиваем БД файла к БД из памяти (Наоборот приаттачивать нельзя,
// так как при приаттачивании или открытии :memory: создаётся новый пустой
// блок памяти. Уже созданный :memory: приаттачить не получится).
if( !srcQuery.exec( QString("ATTACH DATABASE '%1' AS trgdb;").arg(filename) ) )
qDebug() << "[writeDBMemory2File]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
// Перегоняем таблицы из исходной БД в конечную БД
if( !srcQuery.exec( "SELECT name FROM sqlite_master WHERE type = 'table';" ) )
qDebug() << "[writeDBMemory2File]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
while( srcQuery.next() )
{
QString tableName = srcQuery.record().value( "name" ).toString();
// Для таблицы 'sqlite_sequence' требуется особый подход
if( tableName == "sqlite_sequence" ) continue;
if( !srcSubQuery.exec( QString("CREATE TABLE trgdb.'%1' AS SELECT * FROM '%1';").arg(tableName) ) )
qDebug() << "[writeDBMemory2File]\n" << srcSubQuery.lastQuery() << "\n" << srcSubQuery.lastError().text();
}
// Провоцируем создание таблицы sqlie_sequence, так как напрямую её
// создавать нельзя.
if( !srcQuery.exec( "CREATE TABLE trgdb.___tmp ( id INTEGER PRIMARY KEY AUTOINCREMENT, value INTEGER );" ) )
qDebug() << "[writeDBMemory2File]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
if( !srcQuery.exec( "INSERT INTO trgdb.___tmp( value ) VALUES( 1 );" ) )
qDebug() << "[writeDBMemory2File]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
if( !srcQuery.exec( "DROP TABLE trgdb.___tmp;" ) )
qDebug() << "[writeDBMemory2File]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
// Перегоняем счётчики генераторов ключей
if( !srcQuery.exec( "SELECT name, seq FROM sqlite_sequence;" ) )
qDebug() << "[writeDBMemory2File]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
while( srcQuery.next() )
{
QString sName = srcQuery.record().value( "name" ).toString();
int sSeq = srcQuery.record().value( "seq" ).toInt();
if( !srcSubQuery.exec( QString("INSERT INTO trgdb.sqlite_sequence(name, seq) VALUES( '%1', %2 );").arg(sName).arg(sSeq) ) )
qDebug() << "[writeDBMemory2File]\n" << srcSubQuery.lastQuery() << "\n" << srcSubQuery.lastError().text();
}
// Отцепляем приаттаченную БД файла
if( !srcQuery.exec( "DETACH trgdb;" ) )
qDebug() << "[writeDBMemory2File]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
// Для перегонки индексов файловая БД должна быть корневой
// Поэтому создаём отдельное подключение и пытаемся подключиться
QSqlDatabase::addDatabase( "QSQLITE", "file_db" );
{
// Процесс работы с БД изолирован, так как в случае ошибки
// removeDatabase требует деструкцию всех компонентов, относящихся
// к удаляемому соединению
QSqlDatabase trgdb = QSqlDatabase::database( "file_db", false );
if( trgdb.open() )
{
// Конструируем запросник для новооткрытого соединения. Конструировать
// запросники можно только для уже открытых соединений, иначе
// будем получать ошибки типа "Out of memory".
QSqlQuery trgQuery( trgdb );
// Перегоняем индексы
if( !srcQuery.exec( "SELECT sql FROM sqlite_master WHERE type = 'index';" ) )
qDebug() << "[writeDBMemory2File]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
while( srcQuery.next() )
{
if( !trgQuery.exec( QString("%1;").arg(srcQuery.record().value( "sql" ).toString()) ) )
qDebug() << "[writeDBMemory2File]\n" << trgQuery.lastQuery() << "\n" << trgQuery.lastError().text();
}
}
trgdb.close();
}
QSqlDatabase::removeDatabase( "file_db" );
return true;
}
Нехороший рабочий пример чтения SQLite БД из файла в :memory:
bool readDBFile2Memory( QString filename )
{
// Указанный файл должен существовать
QFile file( filename );
if( !file.exists() ) return false;
// Берём подключение к исходной БД. Подразумевается, что БД уже открыта.
// БД может быть расположена в :memory:.
QSqlDatabase srcdb = QSqlDatabase::database( "main_db" );
// Очищаем БД в памяти
srcdb.close();
srcdb.open();
// Конструируем запросники для srcdb (создавать их можно только после
// операций открытия/переоткрытия БД)
QSqlQuery srcQuery( srcdb ), srcSubQuery( srcdb );
// Приаттачиваем БД файла к БД из памяти (Наоборот приаттачивать нельзя,
// так как при приаттачивании или открытии :memory: создаётся новый пустой
// блок памяти. Уже созданный :memory: приаттачить не получится).
if( !srcQuery.exec( QString("ATTACH DATABASE '%1' AS trgdb;").arg(filename) ) )
qDebug() << "[readDBFile2Memory]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
// Перегоняем таблицы из БД файла в БД памяти
if( !srcQuery.exec( "SELECT name FROM trgdb.sqlite_master WHERE type = 'table';" ) )
qDebug() << "[readDBFile2Memory]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
while( srcQuery.next() )
{
QString tableName = srcQuery.record().value( "name" ).toString();
// Для таблицы 'sqlite_sequence' требуется особый подход
if( tableName == "sqlite_sequence" ) continue;
if( !srcSubQuery.exec( QString("CREATE TABLE '%1' AS SELECT * FROM trgdb.'%1';").arg(tableName) ) )
qDebug() << "[readDBFile2Memory]\n" << srcSubQuery.lastQuery() << "\n" << srcSubQuery.lastError().text();
}
// Провоцируем создание таблицы sqlie_sequence, так как напрямую её
// создавать нельзя.
if( !srcQuery.exec( "CREATE TABLE ___tmp ( id INTEGER PRIMARY KEY AUTOINCREMENT, value INTEGER );" ) )
qDebug() << "[readDBFile2Memory]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
if( !srcQuery.exec( "INSERT INTO ___tmp( value ) VALUES( 1 );" ) )
qDebug() << "[readDBFile2Memory]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
if( !srcQuery.exec( "DROP TABLE ___tmp;" ) )
qDebug() << "[readDBFile2Memory]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
// Перегоняем счётчики генераторов ключей
if( !srcQuery.exec( "SELECT name, seq FROM trgdb.sqlite_sequence;" ) )
qDebug() << "[readDBFile2Memory]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
while( srcQuery.next() )
{
QString sName = srcQuery.record().value( "name" ).toString();
int sSeq = srcQuery.record().value( "seq" ).toInt();
if( !srcSubQuery.exec( QString("INSERT INTO sqlite_sequence(name, seq) VALUES( '%1', %2 );").arg(sName).arg(sSeq) ) )
qDebug() << "[readDBFile2Memory]\n" << srcSubQuery.lastQuery() << "\n" << srcSubQuery.lastError().text();
}
// Перегоняем индексы
if( !srcQuery.exec( "SELECT sql FROM trgdb.sqlite_master WHERE type = 'index';" ) )
qDebug() << "[readDBFile2Memory]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
while( srcQuery.next() )
{
if( !srcSubQuery.exec( QString("%1;").arg(srcQuery.record().value( "sql" ).toString()) ) )
qDebug() << "[readDBFile2Memory]\n" << srcSubQuery.lastQuery() << "\n" << srcSubQuery.lastError().text();
}
// Отцепляем приаттаченную БД файла
if( !srcQuery.exec( "DETACH trgdb;" ) )
qDebug() << "[readDBFile2Memory]\n" << srcQuery.lastQuery() << "\n" << srcQuery.lastError().text();
return true;
}
Так делать в первую очередь не хорошо, потому что лично мне не известно, гарантированно ли вся БД перенесётся (достаточно ли только таблиц, индексов и sqlite_sequence). Во-вторых, если записей много, то операция получается вовсе не быстрой, так как делается множество малополезных в данной ситуации построчных операций.
Теперь о том, как было бы неплохо реализовать по-нормальному.
На сайте SQLite есть статья, где рассказывается о том, что вообще для сохранения БД из памяти в файл и наоборот используется механизм резервирования. Приведены даже примеры как. Но, как становится понятно из статьи, делается это через SQLite API.
В Qt для таких случаев, вроде бы, предусмотрена возможность получить хэндлер API-шного подключения: QSqlDriver->Handle();
Есть в справке даже пример получения такого хэндлера.
QSqlDatabase db = ...;
QVariant v = db.driver()->handle();
if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*")==0) {
// v.data() returns a pointer to the handle
sqlite3 *handle = *static_cast<sqlite3 **>(v.data());
if (handle != 0) { // check that it is not NULL
...
}
}
Но вот только для того, чтобы такой код откомпилировался, нужно подключить библиотеку SQLite. А вот как это сделать, чего-то мне найти не удалося.
Я нашёл ещё одну возможно полезную в данной ситуации статью.
Там как раз вопрос про работу с SQLite API, и там у автора темы даже что-то вроде бы получилось.
Автор говорит, что ему помогло вот так:
Цитата("Автор говорит")
The problem is solved by adding
the following line to the .pro file
LIBS += -L/usr/lib -lsqlite3
and added #include "sqlite3.h"
Судя по "/usr/lib" это, скорее всего, вариант для Linux. Также, скорее всего, SQLite там был установлен отдельно (коли в /usr/lib).
Собственно вопрос
Господа, не подскажете, а как можно подключить SQLite библиотеку под Windows? И обязательно ли это должна быть отдельная библиотека (а не из комплекта Qt)?