Krita Source Code Documentation
Loading...
Searching...
No Matches
KisResourceCacheDb.cpp File Reference
#include "KisResourceCacheDb.h"
#include <QSqlError>
#include <QSqlQuery>
#include <QSqlDatabase>
#include <QBuffer>
#include <QVersionNumber>
#include <QStandardPaths>
#include <QDir>
#include <QDirIterator>
#include <QStringList>
#include <QElapsedTimer>
#include <QDataStream>
#include <QByteArray>
#include <QMessageBox>
#include <KritaVersionWrapper.h>
#include <klocalizedstring.h>
#include <KisBackup.h>
#include <kis_debug.h>
#include <KisUsageLogger.h>
#include <KisSqlQueryLoader.h>
#include <KisDatabaseTransactionLock.h>
#include "KisResourceLocator.h"
#include "KisResourceLoaderRegistry.h"
#include "ResourceDebug.h"
#include <kis_assert.h>
#include <KisCppQuirks.h>

Go to the source code of this file.

Functions

QString changeToEmptyIfNull (QString s)
 
QSqlError createDatabase (const QString &location)
 
QSqlError runUpdateScript (const QString &script, const QString &message)
 
QSqlError runUpdateScriptFile (const QString &path, const QString &message)
 
bool updateSchemaVersion ()
 

Variables

const QString dbDriver = "QSQLITE"
 
const QString METADATA_RESOURCES = "resources"
 
const QString METADATA_STORAGES = "storages"
 

Function Documentation

◆ changeToEmptyIfNull()

QString changeToEmptyIfNull ( QString s)

Definition at line 67 of file KisResourceCacheDb.cpp.

68{
69 return s.isNull() ? QString("") : s;
70}

◆ createDatabase()

QSqlError createDatabase ( const QString & location)

initialization is completed, transaction is over, now enable the foreign_keys constraint if necessary

initialization is completed, transaction is over, now enable the foreign_keys constraint if necessary

Definition at line 142 of file KisResourceCacheDb.cpp.

143{
144 // NOTE: if the id's of Unknown and Memory in the database
145 // will change, and that will break the queries that
146 // remove Unknown and Memory storages on start-up.
155
156 QDir dbLocation(location);
157 if (!dbLocation.exists()) {
158 dbLocation.mkpath(dbLocation.path());
159 }
160
161 std::optional<QSqlDatabase> existingDatabase =
162 QSqlDatabase::database(QSqlDatabase::defaultConnection, false);
163
164 const bool databaseConnectionExists = !QSqlDatabase::connectionNames().isEmpty()
165 && existingDatabase->isValid() && existingDatabase->isOpen();
166
167 if (databaseConnectionExists && existingDatabase->tables().contains("version_information")) {
168 return QSqlError();
169 }
170
171 existingDatabase = std::nullopt;
172
173 QSqlDatabase db;
174
175 if (!databaseConnectionExists) {
176 db = QSqlDatabase::addDatabase(dbDriver);
177 db.setDatabaseName(location + "/" + KisResourceCacheDb::resourceCacheDbFilename);
178
179 if (!db.open()) {
180 warnDbMigration << "Could not connect to resource cache database";
181 return db.lastError();
182 }
183 } else {
184 db = QSqlDatabase::database();
185 }
186
187 // will be filled correctly later
188 QVersionNumber oldSchemaVersionNumber;
189 QVersionNumber newSchemaVersionNumber = QVersionNumber::fromString(KisResourceCacheDb::databaseVersion);
190
191
192 QStringList tables = QStringList() << "version_information"
193 << "storage_types"
194 << "resource_types"
195 << "storages"
196 << "tags"
197 << "resources"
198 << "versioned_resources"
199 << "resource_tags"
200 << "metadata"
201 << "tags_storages"
202 << "tag_translations";
203
204 QStringList dbTables;
205 // Verify whether we should recreate the database
206 {
207 bool allTablesPresent = true;
208 dbTables = db.tables();
209 Q_FOREACH(const QString &table, tables) {
210 if (!dbTables.contains(table)) {
211 allTablesPresent = false;
212 break;
213 }
214 }
215
216 bool schemaIsOutDated = false;
217 QString schemaVersion = "0.0.0";
218 QString kritaVersion = "Unknown";
219 int creationDate = 0;
220
221 if (dbTables.contains("version_information")) {
222 // Verify the version number
223
224 {
225 QSqlQuery q(
226 "SELECT database_version\n"
227 ", krita_version\n"
228 ", creation_date\n"
229 "FROM version_information\n"
230 "ORDER BY id\n"
231 "DESC\n"
232 "LIMIT 1;\n");
233
234 if (!q.exec()) {
235 warnDbMigration << "Could not retrieve version information from the database." << q.lastError();
236 abort();
237 }
238 q.first();
239 schemaVersion = q.value(0).toString();
240 kritaVersion = q.value(1).toString();
241 creationDate = q.value(2).toInt();
242 }
243
244 oldSchemaVersionNumber = QVersionNumber::fromString(schemaVersion);
245 newSchemaVersionNumber = QVersionNumber::fromString(KisResourceCacheDb::databaseVersion);
246
247 if (QVersionNumber::compare(oldSchemaVersionNumber, newSchemaVersionNumber) != 0) {
248
249 infoDbMigration << "Old schema:" << schemaVersion << "New schema:" << newSchemaVersionNumber;
250
251 schemaIsOutDated = true;
253
254 if (newSchemaVersionNumber == QVersionNumber::fromString("0.0.18")
255 && QVersionNumber::compare(oldSchemaVersionNumber, QVersionNumber::fromString("0.0.14")) >= 0
256 && QVersionNumber::compare(oldSchemaVersionNumber, QVersionNumber::fromString("0.0.18")) < 0) {
257
258 bool from14to15 = oldSchemaVersionNumber == QVersionNumber::fromString("0.0.14");
259
260 bool from15to16 = oldSchemaVersionNumber == QVersionNumber::fromString("0.0.14")
261 || oldSchemaVersionNumber == QVersionNumber::fromString("0.0.15");
262
263 bool from16to17 = oldSchemaVersionNumber == QVersionNumber::fromString("0.0.14")
264 || oldSchemaVersionNumber == QVersionNumber::fromString("0.0.15")
265 || oldSchemaVersionNumber == QVersionNumber::fromString("0.0.16");
266
267 bool from17to18 = oldSchemaVersionNumber == QVersionNumber::fromString("0.0.14")
268 || oldSchemaVersionNumber == QVersionNumber::fromString("0.0.15")
269 || oldSchemaVersionNumber == QVersionNumber::fromString("0.0.16")
270 || oldSchemaVersionNumber == QVersionNumber::fromString("0.0.17");
271
272 KisDatabaseTransactionLock transactionLock(QSqlDatabase::database());
273
274 bool success = true;
275 if (from14to15) {
276 QSqlError error = runUpdateScript(
277 "ALTER TABLE resource_tags\n"
278 "ADD COLUMN active INTEGER NOT NULL DEFAULT 1",
279 "Update resource tags table (add \'active\' column)");
280 if (error.type() != QSqlError::NoError) {
281 success = false;
282 }
283 }
284 if (success && from15to16) {
285 infoDbMigration << "Going to update indices";
286
287 QStringList indexes = QStringList() << "tags" << "resources" << "tag_translations" << "resource_tags";
288
289 Q_FOREACH(const QString &index, indexes) {
290 QSqlError error = runUpdateScriptFile(":/create_index_" + index + ".sql",
291 QString("Create index for %1").arg(index));
292 if (error.type() != QSqlError::NoError) {
293 success = false;
294 }
295 }
296 }
297
298 if (success && from16to17) {
299 QSqlError error = runUpdateScriptFile(":/create_index_resources_signature.sql",
300 "Create index for resources_signature");
301 if (error.type() != QSqlError::NoError) {
302 success = false;
303 }
304 }
305
306 if (success && from17to18) {
307 {
308 QSqlError error = runUpdateScriptFile(":/0_0_18_0001_cleanup_metadata_table.sql",
309 "Cleanup and deduplicate metadata table");
310 if (error.type() != QSqlError::NoError) {
311 success = false;
312 }
313 }
314 if (success) {
315 QSqlError error = runUpdateScriptFile(":/0_0_18_0002_update_metadata_table_constraints.sql",
316 "Update metadata table constraints");
317 if (error.type() != QSqlError::NoError) {
318 success = false;
319 }
320 }
321 if (success) {
322 QSqlError error = runUpdateScriptFile(":/create_index_metadata_key.sql",
323 "Create index for metadata_key");
324 if (error.type() != QSqlError::NoError) {
325 success = false;
326 }
327 }
328 }
329
330 if (success) {
331 if (!updateSchemaVersion()) {
332 success = false;
333 }
334
335 transactionLock.commit();
336
337 if (success) {
338 QSqlError error = runUpdateScript("VACUUM",
339 "Vacuum database after updating schema");
340 if (error.type() != QSqlError::NoError) {
341 success = false;
342 }
343 }
344 } else {
345 transactionLock.rollback();
346 }
347
348 schemaIsOutDated = !success;
349
350 }
351
352 if (schemaIsOutDated) {
353 QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The resource database scheme has changed. Krita will backup your database and create a new database."));
354 if (QVersionNumber::compare(oldSchemaVersionNumber, QVersionNumber::fromString("0.0.14")) > 0) {
356 }
357 db.close();
358 QFile::remove(location + "/" + KisResourceCacheDb::resourceCacheDbFilename);
359 db.open();
360 }
361 }
362
363 }
364
365 if (allTablesPresent && !schemaIsOutDated) {
366 KisUsageLogger::log(QString("Database is up to date. Version: %1, created by Krita %2, at %3")
367 .arg(schemaVersion)
368 .arg(kritaVersion)
369 .arg(QDateTime::fromSecsSinceEpoch(creationDate).toString()));
370
374
375 return QSqlError();
376 }
377 }
378
379 KisUsageLogger::log(QString("Creating database from scratch (%1, %2).")
380 .arg(oldSchemaVersionNumber.toString().isEmpty() ? QString("database didn't exist") : ("old schema version: " + oldSchemaVersionNumber.toString()))
381 .arg("new schema version: " + newSchemaVersionNumber.toString()));
382
383 KisDatabaseTransactionLock transactionLock(QSqlDatabase::database());
384
385 // Create tables
386 Q_FOREACH(const QString &table, tables) {
387 QSqlError error =
388 runUpdateScriptFile(":/create_" + table + ".sql", QString("Create table %1").arg(table));
389 if (error.type() != QSqlError::NoError) {
390 return error;
391 }
392 }
393
394 {
395 // metadata table constraints were updated in version 0.0.18
396 QSqlError error = runUpdateScriptFile(":/0_0_18_0002_update_metadata_table_constraints.sql",
397 "Update metadata table constraints");
398
399 if (error.type() != QSqlError::NoError) {
400 return error;
401 }
402 }
403
404 // Create indexes
405 QStringList indexes;
406
407 // these indexes came in version 0.0.16
408 indexes << "storages" << "versioned_resources" << "tags" << "resources" << "tag_translations" << "resource_tags";
409
410 // this index came in version 0.0.17
411 indexes << "resources_signature";
412
413 // this index came in version 0.0.18
414 indexes << "metadata_key";
415
416 Q_FOREACH(const QString &index, indexes) {
417 QSqlError error = runUpdateScriptFile(":/create_index_" + index + ".sql",
418 QString("Create index for %1").arg(index));
419 if (error.type() != QSqlError::NoError) {
420 return error;
421 }
422 }
423
424 // Fill lookup tables
425 {
426 QFile f(":/fill_storage_types.sql");
427 if (f.open(QFile::ReadOnly)) {
428 QString sql = f.readAll();
429 Q_FOREACH(const QString &originType, KisResourceCacheDb::storageTypes) {
430 const QString updateStep = QString("Register storage type: %1").arg(originType);
431 QSqlQuery q(sql);
432 q.addBindValue(originType);
433 if (!q.exec()) {
434 warnDbMigration << "Could execute DB update step:" << updateStep << q.lastError();
435 warnDbMigration << " faulty statement:" << sql;
436 return db.lastError();
437 }
438 infoDbMigration << "Completed DB update step:" << updateStep;
439 }
440 }
441 else {
442 return QSqlError("Error executing SQL", QString("Could not find SQL fill_storage_types.sql."), QSqlError::StatementError);
443 }
444 }
445
446 {
447 QFile f(":/fill_resource_types.sql");
448 if (f.open(QFile::ReadOnly)) {
449 QString sql = f.readAll();
450 Q_FOREACH(const QString &resourceType, KisResourceLoaderRegistry::instance()->resourceTypes()) {
451 const QString updateStep = QString("Register resource type: %1").arg(resourceType);
452 QSqlQuery q(sql);
453 q.addBindValue(resourceType);
454 if (!q.exec()) {
455 warnDbMigration << "Could execute DB update step:" << updateStep << q.lastError();
456 warnDbMigration << " faulty statement:" << sql;
457 return db.lastError();
458 }
459 infoDbMigration << "Completed DB update step:" << updateStep;
460 }
461 }
462 else {
463 return QSqlError("Error executing SQL", QString("Could not find SQL fill_resource_types.sql."), QSqlError::StatementError);
464 }
465 }
466
467 if (!updateSchemaVersion()) {
468 return QSqlError("Error executing SQL", QString("Could not update schema version."), QSqlError::StatementError);
469 }
470
471 transactionLock.commit();
472
476
477 return QSqlError();
478}
QList< QString > QStringList
const QString dbDriver
bool updateSchemaVersion()
QSqlError runUpdateScriptFile(const QString &path, const QString &message)
QSqlError runUpdateScript(const QString &script, const QString &message)
static bool numberedBackupFile(const QString &filename, const QString &backupDir=QString(), const QString &backupExtension=QStringLiteral("~"), const uint maxBackups=10)
Definition KisBackup.cpp:38
static const QString resourceCacheDbFilename
filename of the database
static QStringList storageTypes
kinds of places where resources can be stored
static const QString databaseVersion
current schema version
static void synchronizeForeignKeysState()
static KisResourceLoaderRegistry * instance()
static void saveTags()
saveTags saves all tags to .tag files in the resource folder
static KisResourceLocator * instance()
static QString storageTypeToUntranslatedString(StorageType storageType)
static void log(const QString &message)
Logs with date/time.
#define warnDbMigration
Definition kis_debug.h:86
#define infoDbMigration
Definition kis_debug.h:66
QString toString(const QString &value)

References detail::KisDatabaseTransactionLockAdapter::commit(), KisResourceCacheDb::databaseVersion, dbDriver, infoDbMigration, KisResourceLoaderRegistry::instance(), KisResourceLocator::instance(), KisUsageLogger::log(), KisBackup::numberedBackupFile(), KisResourceCacheDb::resourceCacheDbFilename, KisDatabaseTransactionLock::rollback(), runUpdateScript(), runUpdateScriptFile(), KisResourceLocator::saveTags(), KisResourceCacheDb::storageTypes, KisResourceStorage::storageTypeToUntranslatedString(), KisResourceCacheDb::synchronizeForeignKeysState(), updateSchemaVersion(), and warnDbMigration.

◆ runUpdateScript()

QSqlError runUpdateScript ( const QString & script,
const QString & message )

Definition at line 124 of file KisResourceCacheDb.cpp.

125{
126 try {
127
128 KisSqlQueryLoader loader("", script);
129 loader.exec();
130
131 } catch (const KisSqlQueryLoader::SQLException &e) {
132 warnDbMigration.noquote() << "ERROR: Could execute DB update step:" << message;
133 warnDbMigration.noquote() << " error" << e.message;
134 warnDbMigration.noquote() << " sql-error:" << e.sqlError.text();
135 return e.sqlError;
136 }
137
138 infoDbMigration << "Completed DB update step:" << message;
139 return QSqlError();
140}

References KisSqlQueryLoader::exec(), infoDbMigration, KisSqlQueryLoader::SQLException::message, KisSqlQueryLoader::SQLException::sqlError, and warnDbMigration.

◆ runUpdateScriptFile()

QSqlError runUpdateScriptFile ( const QString & path,
const QString & message )

Definition at line 95 of file KisResourceCacheDb.cpp.

96{
97 try {
98
99 KisSqlQueryLoader loader(path);
100 loader.exec();
101
102 } catch (const KisSqlQueryLoader::FileException &e) {
103 warnDbMigration.noquote() << "ERROR: Could not execute DB update step:" << message;
104 warnDbMigration.noquote() << " error" << e.message;
105 warnDbMigration.noquote() << " file:" << e.filePath;
106 warnDbMigration.noquote() << " file-error:" << e.fileErrorString;
107 return
108 QSqlError("Error executing SQL",
109 QString("Could not find SQL file %1").arg(e.filePath),
110 QSqlError::StatementError);
111 } catch (const KisSqlQueryLoader::SQLException &e) {
112 warnDbMigration.noquote() << "ERROR: Could not execute DB update step:" << message;
113 warnDbMigration.noquote() << " error" << e.message;
114 warnDbMigration.noquote() << " file:" << e.filePath;
115 warnDbMigration.noquote() << " statement:" << e.statementIndex;
116 warnDbMigration.noquote() << " sql-error:" << e.sqlError.text();
117 return e.sqlError;
118 }
119
120 infoDbMigration << "Completed DB update step:" << message;
121 return QSqlError();
122}

References KisSqlQueryLoader::exec(), KisSqlQueryLoader::FileException::fileErrorString, KisSqlQueryLoader::FileException::filePath, infoDbMigration, KisSqlQueryLoader::FileException::message, and warnDbMigration.

◆ updateSchemaVersion()

bool updateSchemaVersion ( )

Definition at line 72 of file KisResourceCacheDb.cpp.

73{
74 QFile f(":/fill_version_information.sql");
75 if (f.open(QFile::ReadOnly)) {
76 QString sql = f.readAll();
77 QSqlQuery q;
78 if (!q.prepare(sql)) {
79 warnDbMigration << "Could not prepare the schema information query" << q.lastError() << q.boundValues();
80 return false;
81 }
83 q.addBindValue(KritaVersionWrapper::versionString());
84 q.addBindValue(QDateTime::currentDateTimeUtc().toSecsSinceEpoch());
85 if (!q.exec()) {
86 warnDbMigration << "Could not insert the current version" << q.lastError() << q.boundValues();
87 return false;
88 }
89
90 infoDbMigration << "Filled version table";
91 }
92 return true;
93}
KRITAVERSION_EXPORT QString versionString(bool checkGit=false)

References KisResourceCacheDb::databaseVersion, infoDbMigration, KritaVersionWrapper::versionString(), and warnDbMigration.

Variable Documentation

◆ dbDriver

const QString dbDriver = "QSQLITE"

Definition at line 41 of file KisResourceCacheDb.cpp.

◆ METADATA_RESOURCES

const QString METADATA_RESOURCES = "resources"

Definition at line 42 of file KisResourceCacheDb.cpp.

◆ METADATA_STORAGES

const QString METADATA_STORAGES = "storages"

Definition at line 43 of file KisResourceCacheDb.cpp.