Skip to content
This repository was archived by the owner on Mar 4, 2023. It is now read-only.

Commit c88c9b3

Browse files
committed
added basic migration test
1 parent ebff4ab commit c88c9b3

File tree

9 files changed

+459
-129
lines changed

9 files changed

+459
-129
lines changed

src/datasync/localstore.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ void LocalStore::beginWriteTransaction(const ObjectKey &key, bool exclusive)
718718

719719
void LocalStore::exec(QSqlQuery &query, const ObjectKey &key) const
720720
{
721-
if(!query.exec()) {
721+
if(!query.exec()) { //TODO do same for prepare
722722
throw LocalStoreException(_defaults,
723723
key,
724724
query.executedQuery().simplified(),

src/datasync/migrationhelper.cpp

Lines changed: 152 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "defaults.h"
44
#include "localstore_p.h"
55
#include "defaults_p.h"
6+
#include "remoteconnector_p.h"
67

78
#include <QtCore/QThreadPool>
89
#include <QtCore/QStandardPaths>
@@ -60,7 +61,8 @@ MigrationRunnable::MigrationRunnable(const Defaults &defaults, MigrationHelper *
6061
#define QTDATASYNC_LOG _logger
6162

6263
#define cleanDb() do { \
63-
db.close(); \
64+
if(_flags.testFlag(MigrationHelper::MigrateData)) \
65+
db.close(); \
6466
db = QSqlDatabase(); \
6567
QSqlDatabase::removeDatabase(dbName); \
6668
} while(false)
@@ -105,20 +107,45 @@ void MigrationRunnable::run()
105107
<< storageDir.absolutePath();
106108

107109
//open the database
108-
auto dbName = QStringLiteral("database_migration_") + _defaults.setupName();
109-
auto db = QSqlDatabase::addDatabase(QStringLiteral("SQLITE"), dbName);
110-
db.setDatabaseName(storageDir.absoluteFilePath(QStringLiteral("store.db")));
111-
if(!db.open())
112-
dbError(db);
110+
QString dbName = QStringLiteral("database_migration_") + _defaults.setupName();
111+
auto db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), dbName);
112+
if(_flags.testFlag(MigrationHelper::MigrateData)) {
113+
db.setDatabaseName(storageDir.absoluteFilePath(QStringLiteral("store.db")));
114+
if(!db.open())
115+
dbError(db);
116+
else {
117+
if(!db.tables().contains(QStringLiteral("DataIndex")) ||
118+
!db.tables().contains(QStringLiteral("SyncState"))) {
119+
logWarning() << "Missing tables in database. Either DataIndex or SyncState could not be found";
120+
cleanDb();
121+
migrationDone(false);
122+
return;
123+
} else
124+
logDebug() << "Successfully opened database";
125+
}
126+
}
113127

114128
//count the number of things to migrate
115-
QSqlQuery countQuery(db);
116-
countQuery.prepare(QStringLiteral("SELECT Count(*) FROM DataIndex"));
117-
if(!countQuery.exec())
118-
dbError(countQuery);
119-
if(!countQuery.first())
120-
dbError(countQuery);
121-
auto migrationCount = countQuery.value(0).toInt();
129+
int migrationCount = 0;
130+
if(_flags.testFlag(MigrationHelper::MigrateData)) {
131+
QSqlQuery countQuery(db);
132+
if(!countQuery.exec(QStringLiteral("SELECT Count(*) FROM ("
133+
" SELECT i.Type AS Type, i.Key AS Key "
134+
" FROM DataIndex i "
135+
" LEFT JOIN SyncState s "
136+
" ON i.Type = s.Type AND i.Key = s.Key "
137+
" UNION "
138+
" SELECT s.Type AS Type, s.Key AS Key "
139+
" FROM SyncState s "
140+
" LEFT JOIN DataIndex i "
141+
" ON s.Type = i.Type AND s.Key = i.Key "
142+
" WHERE i.Type IS NULL "
143+
")")))
144+
dbError(countQuery);
145+
if(!countQuery.first())
146+
dbError(countQuery);
147+
migrationCount = countQuery.value(0).toInt();
148+
}
122149
if(_flags.testFlag(MigrationHelper::MigrateRemoteConfig))
123150
migrationCount++;
124151
if(migrationCount == 0) {
@@ -139,20 +166,20 @@ void MigrationRunnable::run()
139166

140167
//migrate key by key
141168
copyConf(oldSettings, QStringLiteral("RemoteConnector/remoteEnabled"),
142-
currentSettings, QStringLiteral("connector/enabled"));
169+
currentSettings, QStringLiteral("connector/") + RemoteConnector::keyRemoteEnabled);
143170
copyConf(oldSettings, QStringLiteral("RemoteConnector/remoteUrl"),
144-
currentSettings, QStringLiteral("connector/remote/url"));
171+
currentSettings, QStringLiteral("connector/") + RemoteConnector::keyRemoteUrl);
145172
// copyConf(oldSettings, QStringLiteral("RemoteConnector/verifyPeer"),
146173
// currentSettings, QStringLiteral("connector/remote/verifyPeer")); //TODO add?
147174
copyConf(oldSettings, QStringLiteral("RemoteConnector/sharedSecret"),
148-
currentSettings, QStringLiteral("connector/remote/accessKey"));
175+
currentSettings, QStringLiteral("connector/") + RemoteConnector::keyRemoteAccessKey);
149176

150177
copyConf(oldSettings, QStringLiteral("NetworkExchange/name"),
151-
currentSettings, QStringLiteral("connector/remote/accessKey"));
178+
currentSettings, QStringLiteral("connector/") + RemoteConnector::keyDeviceName);
152179

153180
//special: copy headers
154181
oldSettings->beginGroup(QStringLiteral("RemoteConnector/headers"));
155-
currentSettings->beginGroup(QStringLiteral("connector/deviceName"));
182+
currentSettings->beginGroup(QStringLiteral("connector/") + RemoteConnector::keyRemoteHeaders);
156183
foreach(auto key, oldSettings->childKeys()) {
157184
copyConf(oldSettings, key,
158185
currentSettings, key);
@@ -165,103 +192,120 @@ void MigrationRunnable::run()
165192
}
166193

167194
//next: migrate all the data step by step
168-
LocalStore store(_defaults, &scope);
169-
for(auto offset = 0; true; offset += 1000) {
170-
QSqlQuery entryQuery(db);
171-
entryQuery.prepare(QStringLiteral("SELECT DataIndex.Type, DataIndex.Key, File, Changed, SyncState.Type, SyncState.Key "
172-
"FROM DataIndex "
173-
"FULL OUTER JOIN SyncState "
174-
"ON DataIndex.Type = SyncState.Type AND DataIndex.Key = SyncState.Key "
175-
"ORDER BY DataIndex.Type, DataIndex.Key, SyncState.Type, SyncState.Key"
176-
"LIMIT 1000 OFFSET ?"));
177-
entryQuery.addBindValue(offset);
178-
if(!entryQuery.exec())
179-
dbError(entryQuery);
180-
181-
if(!entryQuery.first())
182-
break;
183-
do {
184-
//extract data
185-
auto type = entryQuery.value(0).toByteArray();
186-
QString key = entryQuery.value(1).toString();
187-
if(type.isEmpty()) {
188-
type = entryQuery.value(4).toByteArray();
189-
key = entryQuery.value(5).toString();
190-
}
191-
auto file = entryQuery.value(2).toString();
192-
auto state = entryQuery.value(3).toInt();
193-
194-
ObjectKey objKey(type, key);
195-
switch (state) {
196-
case 0: //unchanged
197-
if(!_flags.testFlag(MigrationHelper::MigrateChanged)) //when not migrating changes assume changed
198-
state = 1;
199-
Q_FALLTHROUGH();
200-
case 1: //changed
201-
{
202-
//find the storage directory
203-
auto tName = QString::fromUtf8("store/_" + QByteArray(type).toHex());
204-
auto mDir = storageDir;
205-
if(!mDir.cd(tName)) {
206-
logWarning() << "Failed to find file of stored data. Skipping"
207-
<< objKey;
208-
continue; //scope is the do-while loop
209-
}
195+
if(_flags.testFlag(MigrationHelper::MigrateData)) {
196+
LocalStore store(_defaults, &scope);
197+
for(auto offset = 0; true; offset += 100) {
198+
QSqlQuery entryQuery(db);
199+
if(!entryQuery.prepare(QStringLiteral("SELECT Type, Key, File, Changed FROM ( "
200+
" SELECT i.Type AS Type, i.Key AS Key, i.File AS File, s.Changed AS Changed "
201+
" FROM DataIndex i "
202+
" LEFT JOIN SyncState s "
203+
" ON i.Type = s.Type AND i.Key = s.Key "
204+
" UNION "
205+
" SELECT s.Type AS Type, s.Key AS Key, i.File AS File, s.Changed AS Changed "
206+
" FROM SyncState s "
207+
" LEFT JOIN DataIndex i "
208+
" ON s.Type = i.Type AND s.Key = i.Key "
209+
" WHERE i.Type IS NULL "
210+
") ORDER BY Type, Key "
211+
"LIMIT 100 OFFSET ?")))
212+
dbError(entryQuery);
213+
entryQuery.addBindValue(offset);
214+
if(!entryQuery.exec())
215+
dbError(entryQuery);
216+
217+
if(!entryQuery.first())
218+
break;
219+
do {
220+
//extract data
221+
ObjectKey key {
222+
entryQuery.value(0).toByteArray(),
223+
entryQuery.value(1).toString()
224+
};
225+
QString file = entryQuery.value(2).toString() + QStringLiteral(".dat");
226+
auto state = entryQuery.value(3).toInt();
227+
228+
switch (state) {
229+
case 0: //unchanged
230+
if(!_flags.testFlag(MigrationHelper::MigrateChanged)) //when not migrating changes assume changed
231+
state = 1;
232+
Q_FALLTHROUGH();
233+
case 1: //changed
234+
{
235+
//find the storage directory
236+
auto tName = QString::fromUtf8("store/_" + key.typeName.toHex());
237+
auto mDir = storageDir;
238+
if(!mDir.cd(tName)) {
239+
logWarning() << "Failed to find file of stored data. Skipping"
240+
<< key << " with missing path:" << tName;
241+
migrationProgress();
242+
continue; //scope is the do-while loop
243+
}
210244

211-
//read the data file as json
212-
QFile inFile(mDir.absoluteFilePath(file));
213-
if(!inFile.open(QIODevice::ReadOnly)) {
214-
logWarning().noquote() << "Failed to open file of stored data. Skipping"
215-
<< objKey << "with file error:"
216-
<< inFile.errorString();
217-
continue; //scope is the do-while loop
218-
}
219-
auto data = QJsonDocument::fromBinaryData(inFile.readAll()).object();
220-
inFile.close();
221-
if(data.isEmpty()) {
222-
logWarning() << "Failed read file of stored data. Skipping"
223-
<< objKey;
224-
continue; //scope is the do-while loop
225-
}
245+
//read the data file as json
246+
QFile inFile(mDir.absoluteFilePath(file));
247+
if(!inFile.exists()) {
248+
logWarning() << "Failed to find file of stored data. Skipping"
249+
<< key << " with missing path:" << inFile.fileName();
250+
migrationProgress();
251+
continue; //scope is the do-while loop
252+
}
253+
if(!inFile.open(QIODevice::ReadOnly)) {
254+
logWarning().noquote() << "Failed to open file of stored data. Skipping"
255+
<< key << "with file error:"
256+
<< inFile.errorString();
257+
migrationProgress();
258+
continue; //scope is the do-while loop
259+
}
260+
auto data = QJsonDocument::fromBinaryData(inFile.readAll()).object();
261+
inFile.close();
262+
if(data.isEmpty()) {
263+
logWarning() << "Failed read file of stored data. Skipping"
264+
<< key;
265+
migrationProgress();
266+
continue; //scope is the do-while loop
267+
}
226268

227-
//store in the store...
228-
auto scope = store.startSync(objKey);
229-
LocalStore::ChangeType type;
230-
quint64 version;
231-
QString fileName;
232-
tie(type, version, fileName, std::ignore) = store.loadChangeInfo(scope);
233-
if(type != LocalStore::NoExists && !_flags.testFlag(MigrationHelper::MigrateOverwriteData)) {
234-
logDebug() << "Skipping" << objKey << "as it would overwrite existing data";
235-
continue;
236-
}
237-
store.storeChanged(scope, version + 1, fileName, data, state == 1, type);
238-
store.commitSync(scope);
239-
logDebug() << "Migrated dataset" << objKey << "from the old store to the new one";
240-
break;
241-
}
242-
case 2: //deleted
243-
if(_flags.testFlag(MigrationHelper::MigrateChanged)) {
244-
//only when migrating changes, store the delete change
245-
auto scope = store.startSync(objKey);
269+
//store in the store...
270+
auto scope = store.startSync(key);
246271
LocalStore::ChangeType type;
247272
quint64 version;
248-
tie(type, version, std::ignore, std::ignore) = store.loadChangeInfo(scope);
273+
QString fileName;
274+
tie(type, version, fileName, std::ignore) = store.loadChangeInfo(scope);
249275
if(type != LocalStore::NoExists && !_flags.testFlag(MigrationHelper::MigrateOverwriteData)) {
250-
logDebug() << "Skipping" << objKey << "as it would overwrite existing data";
276+
logDebug() << "Skipping" << key << "as it would overwrite existing data";
277+
migrationProgress();
251278
continue;
252279
}
253-
store.storeDeleted(scope, version, true, type);
280+
store.storeChanged(scope, version + 1, fileName, data, state == 1, type);
254281
store.commitSync(scope);
255-
logDebug() << "Migrated deleted dataset" << objKey << "from the old store to the new one";
282+
logDebug() << "Migrated dataset" << key << "from the old store to the new one";
283+
migrationProgress();
256284
break;
257285
}
258-
break;
259-
default:
260-
Q_UNREACHABLE();
261-
}
262-
263-
migrationProgress();
264-
} while(entryQuery.next());
286+
case 2: //deleted
287+
if(_flags.testFlag(MigrationHelper::MigrateChanged)) {
288+
//only when migrating changes, store the delete change
289+
auto scope = store.startSync(key);
290+
LocalStore::ChangeType type;
291+
quint64 version;
292+
tie(type, version, std::ignore, std::ignore) = store.loadChangeInfo(scope);
293+
if(type != LocalStore::NoExists && !_flags.testFlag(MigrationHelper::MigrateOverwriteData)) {
294+
logDebug() << "Skipping" << key << "as it would overwrite existing data";
295+
migrationProgress();
296+
continue;
297+
}
298+
store.storeDeleted(scope, version + 1, true, type);
299+
store.commitSync(scope);
300+
logDebug() << "Migrated deleted dataset" << key << "from the old store to the new one";
301+
}
302+
migrationProgress();
303+
break;
304+
default:
305+
Q_UNREACHABLE();
306+
}
307+
} while(entryQuery.next());
308+
}
265309
}
266310

267311
//cleanup merge
@@ -304,5 +348,8 @@ void MigrationRunnable::copyConf(QSettings *old, const QString &oldKey, QSetting
304348
current->setValue(newKey, old->value(oldKey));
305349
logDebug().noquote() << "Migrated old settings" << QString(old->group() + QLatin1Char('/') + oldKey)
306350
<< "to new settings as" << QString(current->group() + QLatin1Char('/') + newKey);
351+
} else {
352+
logDebug().noquote() << "Skipping migration of key" << QString(old->group() + QLatin1Char('/') + oldKey)
353+
<< "to new settings as" << QString(current->group() + QLatin1Char('/') + newKey);
307354
}
308355
}

src/datasync/migrationhelper_p.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define QTDATASYNC_MIGRATIONHELPER_P_H
33

44
#include <QtCore/QRunnable>
5+
#include <QtCore/QPointer>
56

67
#include "qtdatasync_global.h"
78
#include "migrationhelper.h"

0 commit comments

Comments
 (0)