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"