File indexing completed on 2025-01-19 03:53:32
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2007-04-15 0007 * Description : Database engine abstract database backend 0008 * 0009 * SPDX-FileCopyrightText: 2007-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0010 * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "dbenginebackend_p.h" 0017 0018 // C++ includes 0019 0020 #include <iterator> 0021 0022 // Qt includes 0023 0024 #include <QApplication> 0025 #include <QCoreApplication> 0026 #include <QFileInfo> 0027 #include <QHash> 0028 #include <QRegularExpression> 0029 #include <QSqlDatabase> 0030 #include <QSqlDriver> 0031 #include <QSqlError> 0032 #include <QSqlRecord> 0033 #include <QThread> 0034 #include <QTime> 0035 0036 // Local includes 0037 0038 #include "digikam_debug.h" 0039 #include "dbengineactiontype.h" 0040 0041 namespace Digikam 0042 { 0043 0044 DbEngineLocking::DbEngineLocking() 0045 : lockCount(0) // create a recursive mutex 0046 { 0047 } 0048 0049 // ----------------------------------------------------------------------------------------- 0050 0051 BdEngineBackendPrivate::BusyWaiter::BusyWaiter(BdEngineBackendPrivate* const d) 0052 : AbstractWaitingUnlocker(d, &d->busyWaitMutex, &d->busyWaitCondVar) 0053 { 0054 } 0055 0056 // ----------------------------------------------------------------------------------------- 0057 0058 BdEngineBackendPrivate::ErrorLocker::ErrorLocker(BdEngineBackendPrivate* const d) 0059 : AbstractWaitingUnlocker(d, &d->errorLockMutex, &d->errorLockCondVar) 0060 { 0061 } 0062 0063 // ----------------------------------------------------------------------------------------- 0064 0065 DbEngineThreadData::DbEngineThreadData() 0066 : valid (0), 0067 transactionCount(0) 0068 { 0069 } 0070 0071 DbEngineThreadData::~DbEngineThreadData() 0072 { 0073 if (transactionCount) 0074 { 0075 qCDebug(DIGIKAM_DBENGINE_LOG) << "WARNING !!! Transaction count is" 0076 << transactionCount << "when destroying database!!!"; 0077 } 0078 0079 closeDatabase(); 0080 } 0081 0082 void DbEngineThreadData::closeDatabase() 0083 { 0084 if (!connectionName.isNull()) 0085 { 0086 { 0087 QSqlDatabase database = QSqlDatabase::database(connectionName, false); 0088 0089 if (database.isOpen()) 0090 { 0091 database.close(); 0092 } 0093 } 0094 0095 // Remove connection 0096 0097 QSqlDatabase::removeDatabase(connectionName); 0098 } 0099 0100 // clear variables 0101 0102 valid = 0; 0103 transactionCount = 0; 0104 connectionName = QString(); 0105 lastError = QSqlError(); 0106 } 0107 0108 BdEngineBackendPrivate::BdEngineBackendPrivate(BdEngineBackend* const backend) 0109 : currentValidity (0), 0110 isInTransaction (false), 0111 status (BdEngineBackend::Unavailable), 0112 lock (nullptr), 0113 operationStatus (BdEngineBackend::ExecuteNormal), 0114 errorLockOperationStatus(BdEngineBackend::ExecuteNormal), 0115 errorHandler (nullptr), 0116 q (backend) 0117 { 0118 } 0119 0120 BdEngineBackendPrivate::~BdEngineBackendPrivate() 0121 { 0122 // Must be shut down from the main thread. 0123 // Clean up the QThreadStorage. It deletes any stored data. 0124 0125 threadDataStorage.setLocalData(nullptr); 0126 } 0127 0128 void BdEngineBackendPrivate::init(const QString& name, DbEngineLocking* const l) 0129 { 0130 backendName = name; 0131 lock = l; 0132 0133 qRegisterMetaType<DbEngineErrorAnswer*>("DbEngineErrorAnswer*"); 0134 qRegisterMetaType<QSqlError>(); 0135 } 0136 0137 // "A connection can only be used from within the thread that created it. 0138 // Moving connections between threads or creating queries from a different thread is not supported." 0139 // => one QSqlDatabase object per thread. 0140 // The main class' open/close methods only interact with the "firstDatabase" object. 0141 // When another thread requests a DB, a new connection is opened and closed at 0142 // finishing of the thread. 0143 QSqlDatabase BdEngineBackendPrivate::databaseForThread() 0144 { 0145 DbEngineThreadData* threadData = nullptr; 0146 0147 if (!threadDataStorage.hasLocalData()) 0148 { 0149 threadData = new DbEngineThreadData; 0150 threadDataStorage.setLocalData(threadData); 0151 } 0152 else 0153 { 0154 threadData = threadDataStorage.localData(); 0155 } 0156 0157 // do we need to reopen the database because parameter changed and validity was increased? 0158 0159 if (threadData->valid && (threadData->valid < currentValidity)) 0160 { 0161 threadData->closeDatabase(); 0162 } 0163 0164 QSqlDatabase database; 0165 0166 if (!threadData->connectionName.isNull()) 0167 { 0168 database = QSqlDatabase::database(threadData->connectionName, false); 0169 } 0170 0171 if (!threadData->valid || !database.isOpen()) 0172 { 0173 database = createDatabaseConnection(); 0174 threadData->connectionName = database.connectionName(); 0175 0176 if (database.open()) 0177 { 0178 threadData->valid = currentValidity; 0179 } 0180 else 0181 { 0182 qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while opening the database. Error was" 0183 << database.lastError(); 0184 } 0185 } 0186 0187 return database; 0188 } 0189 0190 QSqlDatabase BdEngineBackendPrivate::createDatabaseConnection() 0191 { 0192 QSqlDatabase database = QSqlDatabase::addDatabase(parameters.databaseType, connectionName()); 0193 QString connectOptions = parameters.connectOptions; 0194 0195 if (parameters.isSQLite()) 0196 { 0197 QStringList toAdd; 0198 0199 // enable shared cache, especially useful with SQLite >= 3.5.0 0200 0201 toAdd << QLatin1String("QSQLITE_ENABLE_SHARED_CACHE"); 0202 0203 // We do our own waiting. 0204 0205 toAdd << QLatin1String("QSQLITE_BUSY_TIMEOUT=0"); 0206 0207 if (!connectOptions.isEmpty()) 0208 { 0209 connectOptions += QLatin1Char(';'); 0210 } 0211 0212 connectOptions += toAdd.join(QLatin1Char(';')); 0213 } 0214 0215 database.setDatabaseName(parameters.databaseNameCore); 0216 database.setConnectOptions(connectOptions); 0217 database.setHostName(parameters.hostName); 0218 database.setPort(parameters.port); 0219 database.setUserName(parameters.userName); 0220 database.setPassword(parameters.password); 0221 0222 return database; 0223 } 0224 0225 void BdEngineBackendPrivate::closeDatabaseForThread() 0226 { 0227 if (threadDataStorage.hasLocalData()) 0228 { 0229 threadDataStorage.localData()->closeDatabase(); 0230 } 0231 } 0232 0233 QSqlError BdEngineBackendPrivate::databaseErrorForThread() 0234 { 0235 if (threadDataStorage.hasLocalData()) 0236 { 0237 return threadDataStorage.localData()->lastError; 0238 } 0239 0240 return QSqlError(); 0241 } 0242 0243 void BdEngineBackendPrivate::setDatabaseErrorForThread(const QSqlError& lastError) 0244 { 0245 if (threadDataStorage.hasLocalData()) 0246 { 0247 threadDataStorage.localData()->lastError = lastError; 0248 } 0249 } 0250 0251 QString BdEngineBackendPrivate::connectionName() 0252 { 0253 return (backendName + QString::number((quintptr)QThread::currentThread())); 0254 } 0255 0256 bool BdEngineBackendPrivate::incrementTransactionCount() 0257 { 0258 return (!threadDataStorage.localData()->transactionCount++); 0259 } 0260 0261 bool BdEngineBackendPrivate::decrementTransactionCount() 0262 { 0263 return (!--threadDataStorage.localData()->transactionCount); 0264 } 0265 0266 bool BdEngineBackendPrivate::isInMainThread() const 0267 { 0268 return (QThread::currentThread() == QCoreApplication::instance()->thread()); 0269 } 0270 0271 bool BdEngineBackendPrivate::isInUIThread() const 0272 { 0273 QApplication* const app = qobject_cast<QApplication*>(QCoreApplication::instance()); 0274 0275 if (!app) 0276 { 0277 return false; 0278 } 0279 0280 return (QThread::currentThread() == app->thread()); 0281 } 0282 0283 bool BdEngineBackendPrivate::reconnectOnError() const 0284 { 0285 return parameters.isMySQL(); 0286 } 0287 0288 bool BdEngineBackendPrivate::isSQLiteLockError(const DbEngineSqlQuery& query) const 0289 { 0290 return parameters.isSQLite() && 0291 ((query.lastError().nativeErrorCode() == QLatin1String("5")) /*SQLITE_BUSY*/ || 0292 (query.lastError().nativeErrorCode() == QLatin1String("6")) /*SQLITE_LOCKED*/); 0293 } 0294 0295 bool BdEngineBackendPrivate::isSQLiteLockTransactionError(const QSqlError& lastError) const 0296 { 0297 return ( 0298 parameters.isSQLite() && 0299 (lastError.type() == QSqlError::TransactionError) && 0300 (lastError.databaseText() == QLatin1String("database is locked")) 0301 ); 0302 0303 // wouldnt it be great if they gave us the database error number... 0304 } 0305 0306 bool BdEngineBackendPrivate::isConnectionError(const DbEngineSqlQuery& query) const 0307 { 0308 // the backend returns connection error e.g. for Constraint Failed errors. 0309 0310 if (parameters.isSQLite()) 0311 { 0312 return false; 0313 } 0314 0315 return ( 0316 (query.lastError().type() == QSqlError::ConnectionError) || 0317 (query.lastError().nativeErrorCode() == QLatin1String("2006")) 0318 ); 0319 } 0320 0321 bool BdEngineBackendPrivate::needToConsultUserForError(const DbEngineSqlQuery&) const 0322 { 0323 // no such conditions found and defined as yet 0324 0325 return false; 0326 } 0327 0328 bool BdEngineBackendPrivate::needToHandleWithErrorHandler(const DbEngineSqlQuery& query) const 0329 { 0330 return (isConnectionError(query) || needToConsultUserForError(query)); 0331 } 0332 0333 bool BdEngineBackendPrivate::checkRetrySQLiteLockError(int retries) 0334 { 0335 if (!(retries % 25)) 0336 { 0337 qCDebug(DIGIKAM_DBENGINE_LOG) << "Database is locked. Waited" << retries*10; 0338 } 0339 0340 const int uiMaxRetries = 50; 0341 const int maxRetries = 1000; 0342 0343 if (retries > qMax(uiMaxRetries, maxRetries)) 0344 { 0345 if (retries > (isInUIThread() ? uiMaxRetries : maxRetries)) 0346 { 0347 qCWarning(DIGIKAM_DBENGINE_LOG) << "Detected locked database file. There is an active transaction. Waited but giving up now."; 0348 0349 return false; 0350 } 0351 } 0352 0353 BusyWaiter waiter(this); 0354 waiter.wait(10); 0355 0356 return true; 0357 } 0358 0359 void BdEngineBackendPrivate::debugOutputFailedQuery(const QSqlQuery& query) const 0360 { 0361 qCDebug(DIGIKAM_DBENGINE_LOG) << "Failure executing query:\n" 0362 << query.executedQuery() 0363 << "\nError messages:" << query.lastError().driverText() << query.lastError().databaseText() 0364 << query.lastError().nativeErrorCode() << query.lastError().type() 0365 << "\nBound values: " << 0366 0367 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0368 0369 query.boundValues() 0370 0371 #else 0372 0373 query.boundValues().values() 0374 0375 #endif 0376 0377 ; 0378 } 0379 0380 void BdEngineBackendPrivate::debugOutputFailedTransaction(const QSqlError& error) const 0381 { 0382 qCDebug(DIGIKAM_DBENGINE_LOG) << "Failure executing transaction. Error messages:\n" 0383 << error.driverText() << error.databaseText() 0384 << error.nativeErrorCode() << error.type(); 0385 } 0386 0387 void BdEngineBackendPrivate::transactionFinished() 0388 { 0389 // wakes up any BusyWaiter waiting on the busyWaitCondVar. 0390 // Possibly called under d->lock->mutex lock, so we do not lock the busyWaitMutex 0391 0392 busyWaitCondVar.wakeOne(); 0393 } 0394 0395 /** 0396 * Set the wait flag to queryStatus. Typically, call this with Wait. 0397 */ 0398 void BdEngineBackendPrivate::setQueryOperationFlag(BdEngineBackend::QueryOperationStatus status) 0399 { 0400 // Enforce lock order (first main mutex, second error lock mutex) 0401 0402 QMutexLocker l(&errorLockMutex); 0403 0404 // this change must be done under errorLockMutex lock 0405 0406 errorLockOperationStatus = status; 0407 operationStatus = status; 0408 } 0409 0410 /** 0411 * Set the wait flag to queryStatus and wake all waiting threads. 0412 * Typically, call wakeAll with status ExecuteNormal or AbortQueries. 0413 */ 0414 void BdEngineBackendPrivate::queryOperationWakeAll(BdEngineBackend::QueryOperationStatus status) 0415 { 0416 QMutexLocker l(&errorLockMutex); 0417 operationStatus = status; 0418 errorLockOperationStatus = status; 0419 errorLockCondVar.wakeAll(); 0420 } 0421 0422 bool BdEngineBackendPrivate::checkOperationStatus() 0423 { 0424 while (operationStatus == BdEngineBackend::Wait) 0425 { 0426 ErrorLocker locker(this); 0427 locker.wait(); 0428 } 0429 0430 if (operationStatus == BdEngineBackend::ExecuteNormal) 0431 { 0432 return true; 0433 } 0434 else if (operationStatus == BdEngineBackend::AbortQueries) 0435 { 0436 return false; 0437 } 0438 0439 return false; 0440 } 0441 0442 /// Returns true if the query shall be retried 0443 bool BdEngineBackendPrivate::handleWithErrorHandler(const DbEngineSqlQuery* const query) 0444 { 0445 if (errorHandler) 0446 { 0447 setQueryOperationFlag(BdEngineBackend::Wait); 0448 0449 ErrorLocker locker(this); 0450 bool called = false; 0451 QSqlError lastError = query ? query->lastError() : databaseForThread().lastError(); 0452 QString lastQuery = query ? query->lastQuery() : QString(); 0453 0454 if (!query || isConnectionError(*query)) 0455 { 0456 called = QMetaObject::invokeMethod(errorHandler, "connectionError", 0457 Qt::AutoConnection, 0458 Q_ARG(DbEngineErrorAnswer*, this), 0459 Q_ARG(QSqlError, lastError), 0460 Q_ARG(QString, lastQuery)); 0461 } 0462 else if (needToConsultUserForError(*query)) 0463 { 0464 called = QMetaObject::invokeMethod(errorHandler, "consultUserForError", 0465 Qt::AutoConnection, 0466 Q_ARG(DbEngineErrorAnswer*, this), 0467 Q_ARG(QSqlError, lastError), 0468 Q_ARG(QString, lastQuery)); 0469 } 0470 else 0471 { 0472 // unclear what to do. 0473 0474 errorLockOperationStatus = BdEngineBackend::ExecuteNormal; 0475 operationStatus = BdEngineBackend::ExecuteNormal; 0476 0477 return true; 0478 } 0479 0480 if (called) 0481 { 0482 locker.wait(); 0483 } 0484 else 0485 { 0486 qCWarning(DIGIKAM_DBENGINE_LOG) << "Failed to invoke DbEngineErrorHandler. Aborting all queries."; 0487 operationStatus = BdEngineBackend::AbortQueries; 0488 } 0489 0490 switch (operationStatus) 0491 { 0492 case BdEngineBackend::ExecuteNormal: 0493 case BdEngineBackend::Wait: 0494 return true; 0495 case BdEngineBackend::AbortQueries: 0496 return false; 0497 } 0498 } 0499 else 0500 { 0501 // TODO check if it's better to use an own error handler for kio slaves. 0502 // But for now, close only the database in the hope, that the next 0503 // access will be successful. 0504 0505 closeDatabaseForThread(); 0506 } 0507 0508 return false; 0509 } 0510 0511 void BdEngineBackendPrivate::connectionErrorContinueQueries() 0512 { 0513 // Attention: called from out of context, maybe without any lock 0514 0515 QMutexLocker l(&lock->mutex); 0516 queryOperationWakeAll(BdEngineBackend::ExecuteNormal); 0517 } 0518 0519 void BdEngineBackendPrivate::connectionErrorAbortQueries() 0520 { 0521 // Attention: called from out of context, maybe without any lock 0522 0523 QMutexLocker l(&lock->mutex); 0524 queryOperationWakeAll(BdEngineBackend::AbortQueries); 0525 } 0526 0527 // ----------------------------------------------------------------------------------------- 0528 0529 BdEngineBackendPrivate::AbstractUnlocker::AbstractUnlocker(BdEngineBackendPrivate* const d) 0530 : count(0), 0531 d (d) 0532 { 0533 // Why two mutexes? The main mutex is recursive and won't work with a condvar. 0534 0535 // acquire lock 0536 0537 d->lock->mutex.lock(); 0538 0539 // store lock count 0540 0541 count = d->lock->lockCount; 0542 0543 // set lock count to 0 0544 0545 d->lock->lockCount = 0; 0546 0547 // unlock 0548 0549 for (int i = 0 ; i < count ; ++i) 0550 { 0551 d->lock->mutex.unlock(); 0552 } 0553 } 0554 0555 void BdEngineBackendPrivate::AbstractUnlocker::finishAcquire() 0556 { 0557 // drop lock acquired in first line. Main mutex is now free. 0558 // We maintain lock order (first main mutex, second error lock mutex) 0559 // but we drop main mutex lock for waiting on the cond var. 0560 0561 d->lock->mutex.unlock(); 0562 } 0563 0564 BdEngineBackendPrivate::AbstractUnlocker::~AbstractUnlocker() 0565 { 0566 // lock main mutex as often as it was locked before 0567 0568 for (int i = 0 ; i < count ; ++i) 0569 { 0570 d->lock->mutex.lock(); 0571 } 0572 0573 // update lock count 0574 0575 d->lock->lockCount += count; 0576 } 0577 0578 // ----------------------------------------------------------------------------------------- 0579 0580 BdEngineBackendPrivate::AbstractWaitingUnlocker::AbstractWaitingUnlocker(BdEngineBackendPrivate* const d, 0581 QMutex* const mutex, 0582 QWaitCondition* const condVar) 0583 : AbstractUnlocker(d), 0584 mutex(mutex), 0585 condVar(condVar) 0586 { 0587 // Why two mutexes? The main mutex is recursive and won't work with a condvar. 0588 // lock condvar mutex (lock only if main mutex is locked) 0589 0590 mutex->lock(); 0591 0592 finishAcquire(); 0593 } 0594 0595 BdEngineBackendPrivate::AbstractWaitingUnlocker::~AbstractWaitingUnlocker() 0596 { 0597 // unlock condvar mutex. Both mutexes are now free. 0598 0599 mutex->unlock(); 0600 0601 // now base class destructor is executed, reallocating main mutex 0602 } 0603 0604 bool BdEngineBackendPrivate::AbstractWaitingUnlocker::wait(unsigned long time) 0605 { 0606 return condVar->wait(mutex, time); 0607 } 0608 0609 // ----------------------------------------------------------------------------------------- 0610 0611 /** This suspends the current thread if the query status as 0612 * set by setFlag() is Wait and until the thread is woken with wakeAll(). 0613 * The CoreDbAccess mutex will be unlocked while waiting. 0614 */ 0615 void BdEngineBackendPrivate::ErrorLocker::wait() 0616 { 0617 // we use a copy of the flag under lock of the errorLockMutex to be able to check it here 0618 0619 while (d->errorLockOperationStatus == BdEngineBackend::Wait) 0620 { 0621 wait(); 0622 } 0623 } 0624 0625 // ----------------------------------------------------------------------------------------- 0626 0627 BdEngineBackend::BdEngineBackend(const QString& backendName, 0628 DbEngineLocking* const locking) 0629 : d_ptr(new BdEngineBackendPrivate(this)) 0630 { 0631 d_ptr->init(backendName, locking); 0632 } 0633 0634 BdEngineBackend::BdEngineBackend(const QString& backendName, 0635 DbEngineLocking* const locking, 0636 BdEngineBackendPrivate& dd) 0637 : d_ptr(&dd) 0638 { 0639 d_ptr->init(backendName, locking); 0640 } 0641 0642 BdEngineBackend::~BdEngineBackend() 0643 { 0644 Q_D(BdEngineBackend); 0645 close(); 0646 0647 delete d; 0648 } 0649 0650 DbEngineConfigSettings BdEngineBackend::configElement() const 0651 { 0652 Q_D(const BdEngineBackend); 0653 0654 return DbEngineConfig::element(d->parameters.databaseType); 0655 } 0656 0657 DbEngineAction BdEngineBackend::getDBAction(const QString& actionName) const 0658 { 0659 Q_D(const BdEngineBackend); 0660 DbEngineAction action = configElement().sqlStatements.value(actionName); 0661 0662 if (action.name.isNull()) 0663 { 0664 qCWarning(DIGIKAM_DBENGINE_LOG) << "No DB action defined for" << actionName 0665 << "! Implementation missing for this database type (" 0666 << d->parameters.databaseType << ")."; 0667 } 0668 0669 return action; 0670 } 0671 0672 BdEngineBackend::DbType BdEngineBackend::databaseType() const 0673 { 0674 Q_D(const BdEngineBackend); 0675 return d->parameters.isSQLite() ? DbType::SQLite : DbType::MySQL; 0676 } 0677 0678 BdEngineBackend::QueryState BdEngineBackend::execDBAction(const DbEngineAction& action, 0679 QList<QVariant>* const values, 0680 QVariant* const lastInsertId) 0681 { 0682 return execDBAction(action, QMap<QString, QVariant>(), values, lastInsertId); 0683 } 0684 0685 BdEngineBackend::QueryState BdEngineBackend::execDBAction(const QString& action, 0686 QList<QVariant>* const values, 0687 QVariant* const lastInsertId) 0688 { 0689 return execDBAction(getDBAction(action), QMap<QString, QVariant>(), values, lastInsertId); 0690 } 0691 0692 BdEngineBackend::QueryState BdEngineBackend::execDBAction(const QString& action, 0693 const QMap<QString, QVariant>& bindingMap, 0694 QList<QVariant>* const values, QVariant* const lastInsertId) 0695 { 0696 return execDBAction(getDBAction(action), bindingMap, values, lastInsertId); 0697 } 0698 0699 BdEngineBackend::QueryState BdEngineBackend::execDBAction(const DbEngineAction& action, 0700 const QMap<QString, QVariant>& bindingMap, 0701 QList<QVariant>* const values, QVariant* const lastInsertId) 0702 { 0703 Q_D(BdEngineBackend); 0704 0705 BdEngineBackend::QueryState returnResult = BdEngineBackend::QueryState(BdEngineBackend::NoErrors); 0706 QSqlDatabase db = d->databaseForThread(); 0707 0708 if (action.name.isNull()) 0709 { 0710 qCWarning(DIGIKAM_DBENGINE_LOG) << "Attempt to execute null action"; 0711 0712 return BdEngineBackend::QueryState(BdEngineBackend::SQLError); 0713 } 0714 /* 0715 qCDebug(DIGIKAM_DBENGINE_LOG) << "Executing DBAction ["<< action.name <<"]"; 0716 */ 0717 bool wrapInTransaction = (action.mode == QLatin1String("transaction")); 0718 0719 if (wrapInTransaction) 0720 { 0721 beginTransaction(); 0722 } 0723 0724 Q_FOREACH (const DbEngineActionElement& actionElement, action.dbActionElements) 0725 { 0726 BdEngineBackend::QueryState result; 0727 0728 if (actionElement.mode == QLatin1String("query")) 0729 { 0730 result = execSql(actionElement.statement, bindingMap, values, lastInsertId); 0731 } 0732 else if (actionElement.mode == QLatin1String("unprepared")) 0733 { 0734 result = execDirectSqlWithResult(actionElement.statement, values, lastInsertId); 0735 } 0736 else 0737 { 0738 result = execDirectSql(actionElement.statement); 0739 } 0740 0741 if (result != BdEngineBackend::NoErrors) 0742 { 0743 qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while executing DBAction [" 0744 << action.name << "] Statement [" 0745 << actionElement.statement << "]"; 0746 returnResult = result; 0747 0748 /* 0749 if (wrapInTransaction && !db.rollback()) 0750 { 0751 qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while rollback changes of previous DBAction."; 0752 } 0753 */ 0754 0755 break; 0756 } 0757 } 0758 0759 if (wrapInTransaction) 0760 { 0761 commitTransaction(); 0762 } 0763 0764 /* 0765 if ((returnResult == BdEngineBackend::NoErrors && wrapInTransaction) && !db.commit()) 0766 { 0767 qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while committing changes of previous DBAction."; 0768 } 0769 */ 0770 0771 return returnResult; 0772 } 0773 0774 QSqlQuery BdEngineBackend::execDBActionQuery(const QString& action, 0775 const QMap<QString, 0776 QVariant>& bindingMap) 0777 { 0778 return execDBActionQuery(getDBAction(action), bindingMap); 0779 } 0780 0781 QSqlQuery BdEngineBackend::execDBActionQuery(const DbEngineAction& action, const QMap<QString, QVariant>& bindingMap) 0782 { 0783 Q_D(BdEngineBackend); 0784 0785 QSqlDatabase db = d->databaseForThread(); 0786 /* 0787 qCDebug(DIGIKAM_DBENGINE_LOG) << "Executing DBAction ["<< action.name <<"]"; 0788 */ 0789 QSqlQuery result; 0790 0791 Q_FOREACH (const DbEngineActionElement& actionElement, action.dbActionElements) 0792 { 0793 if (actionElement.mode == QLatin1String("query")) 0794 { 0795 result = execQuery(actionElement.statement, bindingMap); 0796 } 0797 else 0798 { 0799 qCDebug(DIGIKAM_DBENGINE_LOG) << "Error, only DBActions with mode 'query' are allowed at this call!"; 0800 } 0801 0802 if (result.lastError().isValid() && !result.lastError().nativeErrorCode().isEmpty()) 0803 { 0804 qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while executing DBAction [" << action.name 0805 << "] Statement [" << actionElement.statement 0806 << "] Errornr. [" << result.lastError() << "]"; 0807 break; 0808 } 0809 } 0810 0811 return result; 0812 } 0813 0814 void BdEngineBackend::setDbEngineErrorHandler(DbEngineErrorHandler* const handler) 0815 { 0816 Q_D(BdEngineBackend); 0817 0818 delete d->errorHandler; 0819 0820 d->errorHandler = handler; 0821 } 0822 0823 bool BdEngineBackend::isCompatible(const DbEngineParameters& parameters) 0824 { 0825 return QSqlDatabase::drivers().contains(parameters.databaseType); 0826 } 0827 0828 bool BdEngineBackend::checkOrSetWALMode() 0829 { 0830 Q_D(BdEngineBackend); 0831 0832 if (!d->parameters.isSQLite()) 0833 { 0834 return false; 0835 } 0836 0837 QList<QVariant> values; 0838 0839 execSql(QString::fromUtf8("PRAGMA journal_mode;"), &values); 0840 0841 if (values.isEmpty()) 0842 { 0843 return false; 0844 } 0845 0846 QSqlDatabase db = d->databaseForThread(); 0847 QString walMode = values.constFirst().toString().toUpper(); 0848 QString dbName = QUrl::fromLocalFile(db.databaseName()).fileName(); 0849 0850 if (walMode == QLatin1String("DELETE")) 0851 { 0852 if (d->parameters.walMode) 0853 { 0854 execSql(QString::fromUtf8("PRAGMA journal_mode=WAL;"), &values); 0855 } 0856 else 0857 { 0858 qCDebug(DIGIKAM_DBENGINE_LOG) << "WAL mode is disabled for" << dbName; 0859 0860 return false; 0861 } 0862 } 0863 else if (walMode == QLatin1String("WAL")) 0864 { 0865 if (!d->parameters.walMode) 0866 { 0867 execSql(QString::fromUtf8("PRAGMA journal_mode=DELETE;"), &values); 0868 } 0869 else 0870 { 0871 qCDebug(DIGIKAM_DBENGINE_LOG) << "WAL mode is enabled for" << dbName; 0872 0873 return true; 0874 } 0875 } 0876 0877 if (values.isEmpty()) 0878 { 0879 return false; 0880 } 0881 0882 walMode = values.constFirst().toString().toUpper(); 0883 bool newMode = (walMode == QLatin1String("WAL")); 0884 0885 qCDebug(DIGIKAM_DBENGINE_LOG) << "WAL mode is set to" << newMode 0886 << "for" << dbName; 0887 0888 return newMode; 0889 } 0890 0891 bool BdEngineBackend::open(const DbEngineParameters& parameters) 0892 { 0893 Q_D(BdEngineBackend); 0894 d->parameters = parameters; 0895 0896 // This will make possibly opened thread dbs reload at next access 0897 0898 d->currentValidity++; 0899 0900 int retries = 0; 0901 0902 Q_FOREVER 0903 { 0904 QSqlDatabase database = d->databaseForThread(); 0905 0906 if (!database.isOpen()) 0907 { 0908 /* 0909 qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while opening the database. Trying again."; 0910 */ 0911 if (connectionErrorHandling(retries++)) 0912 { 0913 continue; 0914 } 0915 else 0916 { 0917 return false; 0918 } 0919 } 0920 else 0921 { 0922 break; 0923 } 0924 } 0925 0926 d->status = Open; 0927 0928 return true; 0929 } 0930 0931 void BdEngineBackend::close() 0932 { 0933 Q_D(BdEngineBackend); 0934 d->closeDatabaseForThread(); 0935 d->status = Unavailable; 0936 } 0937 0938 BdEngineBackend::Status BdEngineBackend::status() const 0939 { 0940 Q_D(const BdEngineBackend); 0941 0942 return d->status; 0943 } 0944 0945 /* 0946 bool BdEngineBackend::execSql(const QString& sql, QStringList* const values) 0947 { 0948 QSqlQuery query = execQuery(sql); 0949 0950 if (!query.isActive()) 0951 { 0952 return false; 0953 } 0954 0955 if (!values) 0956 { 0957 return true; 0958 } 0959 0960 int count = query.record().count(); 0961 0962 while (query.next()) 0963 { 0964 for (int i = 0 ; i < count ; ++i) 0965 { 0966 (*values) << query.value(i).toString(); 0967 } 0968 } 0969 0970 return true; 0971 } 0972 */ 0973 0974 QList<QVariant> BdEngineBackend::readToList(DbEngineSqlQuery& query) 0975 { 0976 QList<QVariant> list; 0977 0978 QSqlRecord record = query.record(); 0979 int count = record.count(); 0980 0981 while (query.next()) 0982 { 0983 for (int i = 0 ; i < count ; ++i) 0984 { 0985 list << query.value(i); 0986 } 0987 } 0988 /* 0989 qCDebug(DIGIKAM_DBENGINE_LOG) << "Setting result value list ["<< list <<"]"; 0990 */ 0991 return list; 0992 } 0993 0994 BdEngineBackend::QueryState BdEngineBackend::handleQueryResult(DbEngineSqlQuery& query, 0995 QList<QVariant>* const values, 0996 QVariant* const lastInsertId) 0997 { 0998 if (!query.isActive()) 0999 { 1000 if (query.lastError().type() == QSqlError::ConnectionError) 1001 { 1002 return BdEngineBackend::QueryState(BdEngineBackend::ConnectionError); 1003 } 1004 else 1005 { 1006 return BdEngineBackend::QueryState(BdEngineBackend::SQLError); 1007 } 1008 } 1009 1010 if (lastInsertId) 1011 { 1012 (*lastInsertId) = query.lastInsertId(); 1013 } 1014 1015 if (values) 1016 { 1017 (*values) = readToList(query); 1018 } 1019 1020 return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); 1021 } 1022 1023 // ------------------------------------------------------------------------------------- 1024 1025 BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, 1026 QList<QVariant>* const values, 1027 QVariant* const lastInsertId) 1028 { 1029 DbEngineSqlQuery query = execQuery(sql); 1030 1031 return handleQueryResult(query, values, lastInsertId); 1032 } 1033 1034 BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, 1035 const QVariant& boundValue1, 1036 QList<QVariant>* const values, 1037 QVariant* const lastInsertId) 1038 { 1039 DbEngineSqlQuery query = execQuery(sql, boundValue1); 1040 1041 return handleQueryResult(query, values, lastInsertId); 1042 } 1043 1044 BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, 1045 const QVariant& boundValue1, 1046 const QVariant& boundValue2, 1047 QList<QVariant>* const values, 1048 QVariant* const lastInsertId) 1049 { 1050 DbEngineSqlQuery query = execQuery(sql, boundValue1, boundValue2); 1051 1052 return handleQueryResult(query, values, lastInsertId); 1053 } 1054 1055 BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, 1056 const QVariant& boundValue1, 1057 const QVariant& boundValue2, 1058 const QVariant& boundValue3, 1059 QList<QVariant>* const values, 1060 QVariant* const lastInsertId) 1061 { 1062 DbEngineSqlQuery query = execQuery(sql, boundValue1, boundValue2, boundValue3); 1063 1064 return handleQueryResult(query, values, lastInsertId); 1065 } 1066 1067 BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, 1068 const QVariant& boundValue1, 1069 const QVariant& boundValue2, 1070 const QVariant& boundValue3, 1071 const QVariant& boundValue4, 1072 QList<QVariant>* const values, 1073 QVariant* const lastInsertId) 1074 { 1075 DbEngineSqlQuery query = execQuery(sql, boundValue1, boundValue2, boundValue3, boundValue4); 1076 1077 return handleQueryResult(query, values, lastInsertId); 1078 } 1079 1080 BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, 1081 const QList<QVariant>& boundValues, 1082 QList<QVariant>* const values, 1083 QVariant* const lastInsertId) 1084 { 1085 DbEngineSqlQuery query = execQuery(sql, boundValues); 1086 1087 return handleQueryResult(query, values, lastInsertId); 1088 } 1089 1090 BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, const QMap<QString, QVariant>& bindingMap, 1091 QList<QVariant>* const values, QVariant* const lastInsertId) 1092 { 1093 DbEngineSqlQuery query = execQuery(sql, bindingMap); 1094 1095 return handleQueryResult(query, values, lastInsertId); 1096 } 1097 1098 // ------------------------------------------------------------------------------------- 1099 1100 BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, 1101 QList<QVariant>* const values, 1102 QVariant* const lastInsertId) 1103 { 1104 exec(preparedQuery); 1105 1106 return handleQueryResult(preparedQuery, values, lastInsertId); 1107 } 1108 1109 BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, 1110 const QVariant& boundValue1, 1111 QList<QVariant>* const values, 1112 QVariant* const lastInsertId) 1113 { 1114 execQuery(preparedQuery, boundValue1); 1115 1116 return handleQueryResult(preparedQuery, values, lastInsertId); 1117 } 1118 1119 BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, 1120 const QVariant& boundValue1, 1121 const QVariant& boundValue2, 1122 QList<QVariant>* const values, 1123 QVariant* const lastInsertId) 1124 { 1125 execQuery(preparedQuery, boundValue1, boundValue2); 1126 1127 return handleQueryResult(preparedQuery, values, lastInsertId); 1128 } 1129 1130 BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, 1131 const QVariant& boundValue1, 1132 const QVariant& boundValue2, 1133 const QVariant& boundValue3, 1134 QList<QVariant>* const values, 1135 QVariant* const lastInsertId) 1136 { 1137 execQuery(preparedQuery, boundValue1, boundValue2, boundValue3); 1138 1139 return handleQueryResult(preparedQuery, values, lastInsertId); 1140 } 1141 1142 BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, 1143 const QVariant& boundValue1, 1144 const QVariant& boundValue2, 1145 const QVariant& boundValue3, 1146 const QVariant& boundValue4, 1147 QList<QVariant>* const values, 1148 QVariant* const lastInsertId) 1149 { 1150 execQuery(preparedQuery, boundValue1, boundValue2, boundValue3, boundValue4); 1151 1152 return handleQueryResult(preparedQuery, values, lastInsertId); 1153 } 1154 1155 BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, 1156 const QList<QVariant>& boundValues, 1157 QList<QVariant>* const values, 1158 QVariant* const lastInsertId) 1159 { 1160 execQuery(preparedQuery, boundValues); 1161 1162 return handleQueryResult(preparedQuery, values, lastInsertId); 1163 } 1164 1165 // ------------------------------------------------------------------------------------- 1166 1167 DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql, const QVariant& boundValue1) 1168 { 1169 DbEngineSqlQuery query = prepareQuery(sql); 1170 /* 1171 qCDebug(DIGIKAM_DBENGINE_LOG) << "Trying to sql ["<< sql <<"] query ["<<query.lastQuery()<<"]"; 1172 */ 1173 execQuery(query, boundValue1); 1174 1175 return query; 1176 } 1177 1178 DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql, 1179 const QVariant& boundValue1, 1180 const QVariant& boundValue2) 1181 { 1182 DbEngineSqlQuery query = prepareQuery(sql); 1183 execQuery(query, boundValue1, boundValue2); 1184 1185 return query; 1186 } 1187 1188 DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql, 1189 const QVariant& boundValue1, 1190 const QVariant& boundValue2, 1191 const QVariant& boundValue3) 1192 { 1193 DbEngineSqlQuery query = prepareQuery(sql); 1194 execQuery(query, boundValue1, boundValue2, boundValue3); 1195 1196 return query; 1197 } 1198 1199 DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql, 1200 const QVariant& boundValue1, 1201 const QVariant& boundValue2, 1202 const QVariant& boundValue3, 1203 const QVariant& boundValue4) 1204 { 1205 DbEngineSqlQuery query = prepareQuery(sql); 1206 execQuery(query, boundValue1, boundValue2, boundValue3, boundValue4); 1207 1208 return query; 1209 } 1210 1211 DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql, 1212 const QList<QVariant>& boundValues) 1213 { 1214 DbEngineSqlQuery query = prepareQuery(sql); 1215 execQuery(query, boundValues); 1216 1217 return query; 1218 } 1219 1220 DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql) 1221 { 1222 DbEngineSqlQuery query = prepareQuery(sql); 1223 /* 1224 qCDebug(DIGIKAM_DBENGINE_LOG)<<"execQuery: Using statement ["<< query.lastQuery() <<"]"; 1225 */ 1226 exec(query); 1227 1228 return query; 1229 } 1230 1231 // ------------------------------------------------------------------------------------- 1232 1233 void BdEngineBackend::execQuery(DbEngineSqlQuery& query, const QVariant& boundValue1) 1234 { 1235 query.bindValue(0, boundValue1); 1236 exec(query); 1237 } 1238 1239 void BdEngineBackend::execQuery(DbEngineSqlQuery& query, 1240 const QVariant& boundValue1, 1241 const QVariant& boundValue2) 1242 { 1243 query.bindValue(0, boundValue1); 1244 query.bindValue(1, boundValue2); 1245 exec(query); 1246 } 1247 1248 void BdEngineBackend::execQuery(DbEngineSqlQuery& query, 1249 const QVariant& boundValue1, 1250 const QVariant& boundValue2, 1251 const QVariant& boundValue3) 1252 { 1253 query.bindValue(0, boundValue1); 1254 query.bindValue(1, boundValue2); 1255 query.bindValue(2, boundValue3); 1256 exec(query); 1257 } 1258 1259 void BdEngineBackend::execQuery(DbEngineSqlQuery& query, 1260 const QVariant& boundValue1, 1261 const QVariant& boundValue2, 1262 const QVariant& boundValue3, 1263 const QVariant& boundValue4) 1264 { 1265 query.bindValue(0, boundValue1); 1266 query.bindValue(1, boundValue2); 1267 query.bindValue(2, boundValue3); 1268 query.bindValue(3, boundValue4); 1269 exec(query); 1270 } 1271 1272 void BdEngineBackend::execQuery(DbEngineSqlQuery& query, 1273 const QList<QVariant>& boundValues) 1274 { 1275 for (int i = 0 ; i < boundValues.size() ; ++i) 1276 { 1277 query.bindValue(i, boundValues.at(i)); 1278 } 1279 1280 exec(query); 1281 } 1282 1283 // ------------------------------------------------------------------------------------- 1284 1285 DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql, const QMap<QString, QVariant>& bindingMap) 1286 { 1287 QString preparedString = sql; 1288 QVariantList valuesToBind; 1289 1290 if (!bindingMap.isEmpty()) 1291 { 1292 /* 1293 qCDebug(DIGIKAM_DBENGINE_LOG) << "Prepare statement [" << preparedString << "] with binding map [" << bindingMap << "]"; 1294 */ 1295 QRegularExpression identifierRegExp(QLatin1String(":[A-Za-z0-9]+")); 1296 int pos = 0; 1297 1298 while ((pos = preparedString.indexOf(identifierRegExp, pos)) != -1) 1299 { 1300 QRegularExpressionMatch regMatch = identifierRegExp.match(preparedString); 1301 QString namedPlaceholder = regMatch.captured(0); 1302 1303 if (!bindingMap.contains(namedPlaceholder)) 1304 { 1305 qCWarning(DIGIKAM_DBENGINE_LOG) << "Missing place holder" << namedPlaceholder 1306 << "in binding map. The following values are defined for this action:" 1307 << bindingMap.keys() <<". This is a setup error!"; 1308 1309 // TODO: What should we do here? How can we cancel that action? 1310 } 1311 1312 QVariant placeHolderValue = bindingMap.value(namedPlaceholder); 1313 QString replaceStr; 1314 1315 if (placeHolderValue.userType() == qMetaTypeId<DbEngineActionType>()) 1316 { 1317 DbEngineActionType actionType = placeHolderValue.value<DbEngineActionType>(); 1318 bool isValue = actionType.isValue(); 1319 QVariant value = actionType.getActionValue(); 1320 1321 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 1322 1323 if (value.typeId() == QVariant::Map) 1324 1325 #else 1326 1327 if (value.type() == QVariant::Map) 1328 1329 #endif 1330 1331 { 1332 QMap<QString, QVariant> placeHolderMap = value.toMap(); 1333 QMap<QString, QVariant>::const_iterator iterator; 1334 1335 for (iterator = placeHolderMap.constBegin(); iterator != placeHolderMap.constEnd(); ++iterator) 1336 { 1337 const QString& key = iterator.key(); 1338 const QVariant& value2 = iterator.value(); 1339 replaceStr.append(key); 1340 replaceStr.append(QLatin1String("= ?")); 1341 valuesToBind.append(value2); 1342 1343 // Add a semicolon to the statement, if we are not on the last entry 1344 1345 if (std::next(iterator, 1) != placeHolderMap.constEnd()) 1346 { 1347 replaceStr.append(QLatin1String(", ")); 1348 } 1349 } 1350 } 1351 1352 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 1353 1354 else if ( value.typeId() == QVariant::List ) 1355 1356 #else 1357 1358 else if ( value.type() == QVariant::List ) 1359 1360 #endif 1361 1362 { 1363 QList<QVariant> placeHolderList = value.toList(); 1364 QList<QVariant>::const_iterator iterator; 1365 1366 for (iterator = placeHolderList.constBegin() ; iterator != placeHolderList.constEnd() ; ++iterator) 1367 { 1368 const QVariant& entry = *iterator; 1369 1370 if (isValue) 1371 { 1372 replaceStr.append(QLatin1String("?")); 1373 valuesToBind.append(entry); 1374 } 1375 else 1376 { 1377 replaceStr.append(entry.value<QString>()); 1378 } 1379 1380 // Add a semicolon to the statement, if we are not on the last entry 1381 1382 if ((iterator+1) != placeHolderList.constEnd()) 1383 { 1384 replaceStr.append(QLatin1String(", ")); 1385 } 1386 } 1387 } 1388 1389 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 1390 1391 else if (value.typeId() == QVariant::StringList ) 1392 1393 #else 1394 1395 else if (value.type() == QVariant::StringList ) 1396 1397 #endif 1398 1399 { 1400 QStringList placeHolderList = value.toStringList(); 1401 QStringList::const_iterator iterator; 1402 1403 for (iterator = placeHolderList.constBegin() ; iterator != placeHolderList.constEnd() ; ++iterator) 1404 { 1405 const QString& entry = *iterator; 1406 1407 if (isValue) 1408 { 1409 replaceStr.append(QLatin1String("?")); 1410 valuesToBind.append(entry); 1411 } 1412 else 1413 { 1414 replaceStr.append(entry); 1415 } 1416 1417 // Add a semicolon to the statement, if we are not on the last entry 1418 1419 if ((iterator+1) != placeHolderList.constEnd()) 1420 { 1421 replaceStr.append(QLatin1String(", ")); 1422 } 1423 } 1424 } 1425 else 1426 { 1427 if (isValue) 1428 { 1429 replaceStr = QLatin1Char('?'); 1430 valuesToBind.append(value); 1431 } 1432 else 1433 { 1434 replaceStr = value.toString(); 1435 } 1436 } 1437 } 1438 else 1439 { 1440 /* 1441 qCDebug(DIGIKAM_DBENGINE_LOG) << "Bind key ["<< namedPlaceholder << "] to value [" << bindingMap[namedPlaceholder] << "]"; 1442 */ 1443 valuesToBind.append(placeHolderValue); 1444 replaceStr = QLatin1Char('?'); 1445 } 1446 1447 preparedString = preparedString.replace(pos, regMatch.capturedLength(), replaceStr); 1448 pos = 0; // reset pos 1449 } 1450 } 1451 /* 1452 qCDebug(DIGIKAM_DBENGINE_LOG) << "Prepared statement [" << preparedString << "] values [" << valuesToBind << "]"; 1453 */ 1454 DbEngineSqlQuery query = prepareQuery(preparedString); 1455 1456 for (int i = 0 ; i < valuesToBind.size() ; ++i) 1457 { 1458 query.bindValue(i, valuesToBind.at(i)); 1459 } 1460 1461 exec(query); 1462 return query; 1463 } 1464 1465 BdEngineBackend::QueryState BdEngineBackend::execUpsertDBAction(const DbEngineAction& action, 1466 const QVariant& id, 1467 const QStringList& fieldNames, 1468 const QList<QVariant>& values) 1469 { 1470 QMap<QString, QVariant> parameters; 1471 QMap<QString, QVariant> fieldValueMap; 1472 1473 for (int i = 0 ; i < fieldNames.size() ; ++i) 1474 { 1475 fieldValueMap.insert(fieldNames.at(i), values.at(i)); 1476 } 1477 1478 DbEngineActionType fieldValueList = DbEngineActionType::value(fieldValueMap); 1479 DbEngineActionType fieldList = DbEngineActionType::fieldEntry(fieldNames); 1480 DbEngineActionType valueList = DbEngineActionType::value(values); 1481 1482 parameters.insert(QLatin1String(":id"), id); 1483 parameters.insert(QLatin1String(":fieldValueList"), QVariant::fromValue(fieldValueList)); 1484 parameters.insert(QLatin1String(":fieldList"), QVariant::fromValue(fieldList)); 1485 parameters.insert(QLatin1String(":valueList"), QVariant::fromValue(valueList)); 1486 1487 return execDBAction(action, parameters); 1488 } 1489 1490 BdEngineBackend::QueryState BdEngineBackend::execUpsertDBAction(const QString& action, 1491 const QVariant& id, 1492 const QStringList& fieldNames, 1493 const QList<QVariant>& values) 1494 { 1495 return execUpsertDBAction(getDBAction(action), id, fieldNames, values); 1496 } 1497 1498 bool BdEngineBackend::connectionErrorHandling(int /*retries*/) 1499 { 1500 Q_D(BdEngineBackend); 1501 1502 if (d->reconnectOnError()) 1503 { 1504 if (d->handleWithErrorHandler(nullptr)) 1505 { 1506 d->closeDatabaseForThread(); 1507 1508 return true; 1509 } 1510 } 1511 1512 return false; 1513 } 1514 1515 bool BdEngineBackend::queryErrorHandling(DbEngineSqlQuery& query, int retries) 1516 { 1517 Q_D(BdEngineBackend); 1518 1519 if (d->isSQLiteLockError(query)) 1520 { 1521 if (d->checkRetrySQLiteLockError(retries)) 1522 { 1523 return true; 1524 } 1525 } 1526 1527 d->debugOutputFailedQuery(query); 1528 1529 /* 1530 * Check if the error is query or database related. 1531 * It seems, that insufficient privileges results only in query errors, 1532 * the database gives an invalid lastError() value. 1533 */ 1534 if (query.lastError().isValid()) 1535 { 1536 d->setDatabaseErrorForThread(query.lastError()); 1537 } 1538 else 1539 { 1540 d->setDatabaseErrorForThread(d->databaseForThread().lastError()); 1541 } 1542 1543 if (d->isConnectionError(query) && d->reconnectOnError()) 1544 { 1545 // after connection errors, it can be required 1546 // to start with a new connection and a fresh, copied query 1547 1548 d->closeDatabaseForThread(); 1549 query = copyQuery(query); 1550 } 1551 1552 if (d->needToHandleWithErrorHandler(query)) 1553 { 1554 if (d->handleWithErrorHandler(&query)) 1555 { 1556 return true; 1557 } 1558 else 1559 { 1560 return false; 1561 } 1562 } 1563 1564 return false; 1565 } 1566 1567 bool BdEngineBackend::transactionErrorHandling(const QSqlError& lastError, int retries) 1568 { 1569 Q_D(BdEngineBackend); 1570 1571 if (d->isSQLiteLockTransactionError(lastError)) 1572 { 1573 if (d->checkRetrySQLiteLockError(retries)) 1574 { 1575 return true; 1576 } 1577 } 1578 1579 d->debugOutputFailedTransaction(lastError); 1580 1581 // no experience with other forms of failure 1582 1583 return false; 1584 } 1585 1586 BdEngineBackend::QueryState BdEngineBackend::execDirectSql(const QString& sql) 1587 { 1588 Q_D(BdEngineBackend); 1589 1590 if (!d->checkOperationStatus()) 1591 { 1592 return BdEngineBackend::QueryState(BdEngineBackend::SQLError); 1593 } 1594 1595 DbEngineSqlQuery query = getQuery(); 1596 int retries = 0; 1597 1598 Q_FOREVER 1599 { 1600 if (query.exec(sql)) 1601 { 1602 break; 1603 } 1604 else 1605 { 1606 if (queryErrorHandling(query, retries++)) 1607 { 1608 continue; 1609 } 1610 else 1611 { 1612 return BdEngineBackend::QueryState(BdEngineBackend::SQLError); 1613 } 1614 } 1615 } 1616 1617 return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); 1618 } 1619 1620 BdEngineBackend::QueryState BdEngineBackend::execDirectSqlWithResult(const QString& sql, 1621 QList<QVariant>* const values, 1622 QVariant* const lastInsertId) 1623 { 1624 Q_D(BdEngineBackend); 1625 1626 if (!d->checkOperationStatus()) 1627 { 1628 return BdEngineBackend::QueryState(BdEngineBackend::SQLError); 1629 } 1630 1631 DbEngineSqlQuery query = getQuery(); 1632 int retries = 0; 1633 1634 Q_FOREVER 1635 { 1636 if (query.exec(sql)) 1637 { 1638 handleQueryResult(query, values, lastInsertId); 1639 break; 1640 } 1641 else 1642 { 1643 if (queryErrorHandling(query, retries++)) 1644 { 1645 continue; 1646 } 1647 else 1648 { 1649 return BdEngineBackend::QueryState(BdEngineBackend::SQLError); 1650 } 1651 } 1652 } 1653 1654 return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); 1655 } 1656 1657 bool BdEngineBackend::exec(DbEngineSqlQuery& query) 1658 { 1659 Q_D(BdEngineBackend); 1660 1661 if (!d->checkOperationStatus()) 1662 { 1663 return false; 1664 } 1665 1666 int retries = 0; 1667 1668 Q_FOREVER 1669 { 1670 /* 1671 qCDebug(DIGIKAM_DBENGINE_LOG) << "Trying to query [" 1672 << query.lastQuery() 1673 << "] values [" 1674 << query.boundValues() 1675 << "]"; 1676 */ 1677 1678 if (query.exec()) // krazy:exclude=crashy 1679 { 1680 break; 1681 } 1682 else 1683 { 1684 if (queryErrorHandling(query, retries++)) 1685 { 1686 continue; 1687 } 1688 else 1689 { 1690 return false; 1691 } 1692 } 1693 } 1694 1695 return true; 1696 } 1697 1698 bool BdEngineBackend::execBatch(DbEngineSqlQuery& query) 1699 { 1700 Q_D(BdEngineBackend); 1701 1702 if (!d->checkOperationStatus()) 1703 { 1704 return false; 1705 } 1706 1707 int retries = 0; 1708 1709 Q_FOREVER 1710 { 1711 if (query.execBatch()) 1712 { 1713 break; 1714 } 1715 else 1716 { 1717 if (queryErrorHandling(query, retries++)) 1718 { 1719 continue; 1720 } 1721 else 1722 { 1723 return false; 1724 } 1725 } 1726 } 1727 1728 return true; 1729 } 1730 1731 DbEngineSqlQuery BdEngineBackend::prepareQuery(const QString& sql) 1732 { 1733 int retries = 0; 1734 1735 Q_FOREVER 1736 { 1737 DbEngineSqlQuery query = getQuery(); 1738 1739 if (query.prepare(sql)) 1740 { 1741 return query; 1742 } 1743 else 1744 { 1745 qCDebug(DIGIKAM_DBENGINE_LOG) << "Prepare failed!"; 1746 1747 if (queryErrorHandling(query, retries++)) 1748 { 1749 continue; 1750 } 1751 else 1752 { 1753 return query; 1754 } 1755 } 1756 } 1757 } 1758 1759 DbEngineSqlQuery BdEngineBackend::copyQuery(const DbEngineSqlQuery& old) 1760 { 1761 DbEngineSqlQuery query = getQuery(); 1762 /* 1763 qCDebug(DIGIKAM_DBENGINE_LOG) << "Last query was [" << old.lastQuery() << "]"; 1764 */ 1765 query.prepare(old.lastQuery()); 1766 query.setForwardOnly(old.isForwardOnly()); 1767 1768 // only for positional binding 1769 1770 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 1771 1772 QList<QVariant> boundValues = old.boundValues(); 1773 1774 #else 1775 1776 QList<QVariant> boundValues = old.boundValues().values(); 1777 1778 #endif 1779 1780 Q_FOREACH (const QVariant& value, boundValues) 1781 { 1782 /* 1783 qCDebug(DIGIKAM_DBENGINE_LOG) << "Bind value to query ["<<value<<"]"; 1784 */ 1785 query.addBindValue(value); 1786 } 1787 1788 return query; 1789 } 1790 1791 DbEngineSqlQuery BdEngineBackend::getQuery() 1792 { 1793 Q_D(BdEngineBackend); 1794 QSqlDatabase db = d->databaseForThread(); 1795 1796 DbEngineSqlQuery query(db); 1797 query.setForwardOnly(true); 1798 1799 return query; 1800 } 1801 1802 BdEngineBackend::QueryState BdEngineBackend::beginTransaction() 1803 { 1804 Q_D(BdEngineBackend); 1805 1806 // Call databaseForThread before touching transaction count - open() will reset the count! 1807 1808 QSqlDatabase db = d->databaseForThread(); 1809 1810 if (d->incrementTransactionCount()) 1811 { 1812 int retries = 0; 1813 1814 Q_FOREVER 1815 { 1816 if (db.transaction()) 1817 { 1818 break; 1819 } 1820 else 1821 { 1822 if (transactionErrorHandling(db.lastError(), retries++)) 1823 { 1824 continue; 1825 } 1826 else 1827 { 1828 d->decrementTransactionCount(); 1829 1830 if (db.lastError().type() == QSqlError::ConnectionError) 1831 { 1832 return BdEngineBackend::QueryState(BdEngineBackend::ConnectionError); 1833 } 1834 else 1835 { 1836 return BdEngineBackend::QueryState(BdEngineBackend::SQLError); 1837 } 1838 } 1839 } 1840 } 1841 1842 d->isInTransaction = true; 1843 } 1844 1845 return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); 1846 } 1847 1848 BdEngineBackend::QueryState BdEngineBackend::commitTransaction() 1849 { 1850 Q_D(BdEngineBackend); 1851 1852 if (d->decrementTransactionCount()) 1853 { 1854 QSqlDatabase db = d->databaseForThread(); 1855 int retries = 0; 1856 1857 Q_FOREVER 1858 { 1859 if (db.commit()) 1860 { 1861 break; 1862 } 1863 else 1864 { 1865 QSqlError lastError = db.lastError(); 1866 1867 if (transactionErrorHandling(lastError, retries++)) 1868 { 1869 continue; 1870 } 1871 else 1872 { 1873 qCDebug(DIGIKAM_DBENGINE_LOG) << "Failed to commit transaction. Starting rollback."; 1874 bool ret = db.rollback(); 1875 1876 // No need to check feedback for database as driver must support transactions. 1877 // Also, in all cases the database error is checked outside this feedback. 1878 1879 Q_UNUSED(ret); 1880 1881 if (lastError.type() == QSqlError::ConnectionError) 1882 { 1883 return BdEngineBackend::QueryState(BdEngineBackend::ConnectionError); 1884 } 1885 else 1886 { 1887 return BdEngineBackend::QueryState(BdEngineBackend::SQLError); 1888 } 1889 } 1890 } 1891 } 1892 1893 d->isInTransaction = false; 1894 d->transactionFinished(); 1895 } 1896 1897 return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); 1898 } 1899 1900 bool BdEngineBackend::isInTransaction() const 1901 { 1902 Q_D(const BdEngineBackend); 1903 1904 return d->isInTransaction; 1905 } 1906 1907 void BdEngineBackend::rollbackTransaction() 1908 { 1909 Q_D(BdEngineBackend); 1910 1911 // we leave that out for transaction counting. It's an exceptional condition. 1912 1913 d->databaseForThread().rollback(); 1914 } 1915 1916 QStringList BdEngineBackend::tables() 1917 { 1918 Q_D(BdEngineBackend); 1919 1920 return d->databaseForThread().tables(); 1921 } 1922 1923 QSqlError BdEngineBackend::lastSQLError() 1924 { 1925 Q_D(BdEngineBackend); 1926 1927 return d->databaseErrorForThread(); 1928 } 1929 1930 QString BdEngineBackend::lastError() 1931 { 1932 Q_D(BdEngineBackend); 1933 1934 return d->databaseForThread().lastError().text(); 1935 } 1936 1937 int BdEngineBackend::maximumBoundValues() const 1938 { 1939 Q_D(const BdEngineBackend); 1940 1941 if (d->parameters.isSQLite()) 1942 { 1943 return 999; // SQLITE_MAX_VARIABLE_NUMBER 1944 } 1945 else 1946 { 1947 return 65535; // MySQL 1948 } 1949 } 1950 1951 void BdEngineBackend::setForeignKeyChecks(bool check) 1952 { 1953 Q_D(BdEngineBackend); 1954 1955 if (d->parameters.isMySQL()) 1956 { 1957 if (check) 1958 { 1959 execSql(QLatin1String("SET FOREIGN_KEY_CHECKS=1;")); 1960 } 1961 else 1962 { 1963 execSql(QLatin1String("SET FOREIGN_KEY_CHECKS=0;")); 1964 } 1965 } 1966 } 1967 1968 } // namespace Digikam 1969 1970 #include "moc_dbenginebackend.cpp"