1 #include "abstractdb.h"
2 #include "services/dbmanager.h"
3 #include "common/utils.h"
4 #include "asyncqueryrunner.h"
5 #include "sqlresultsrow.h"
6 #include "common/utils_sql.h"
7 #include "services/config.h"
8 #include "sqlerrorresults.h"
9 #include "sqlerrorcodes.h"
10 #include "services/notifymanager.h"
11 #include "services/sqliteextensionmanager.h"
12 #include "log.h"
13 #include "parser/lexer.h"
14 #include "common/compatibility.h"
15 #include <QDebug>
16 #include <QTime>
17 #include <QWriteLocker>
18 #include <QReadLocker>
19 #include <QThreadPool>
20 #include <QMetaEnum>
21 #include <QtConcurrent/QtConcurrentRun>
22 
23 quint32 AbstractDb::asyncId = 1;
24 
AbstractDb(const QString & name,const QString & path,const QHash<QString,QVariant> & connOptions)25 AbstractDb::AbstractDb(const QString& name, const QString& path, const QHash<QString, QVariant>& connOptions) :
26     name(name), path(path), connOptions(connOptions)
27 {
28 }
29 
~AbstractDb()30 AbstractDb::~AbstractDb()
31 {
32 }
33 
open()34 bool AbstractDb::open()
35 {
36     bool res = isOpen() || openQuiet();
37     if (res)
38         emit connected();
39 
40     return res;
41 }
42 
close()43 bool AbstractDb::close()
44 {
45     bool deny = false;
46     emit aboutToDisconnect(deny);
47     if (deny)
48         return false;
49 
50     bool res = !isOpen() || closeQuiet();
51     if (res)
52         emit disconnected();
53 
54     return res;
55 }
56 
openQuiet()57 bool AbstractDb::openQuiet()
58 {
59     QWriteLocker locker(&dbOperLock);
60     QWriteLocker connectionLocker(&connectionStateLock);
61     return openAndSetup();
62 }
63 
closeQuiet()64 bool AbstractDb::closeQuiet()
65 {
66     QWriteLocker locker(&dbOperLock);
67     QWriteLocker connectionLocker(&connectionStateLock);
68     interruptExecution();
69     bool res = closeInternal();
70     clearAttaches();
71     registeredFunctions.clear();
72     registeredCollations.clear();
73     if (FUNCTIONS) // FUNCTIONS is already null when closing db while closing entire app
74         disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions()));
75 
76     return res;
77 }
78 
openForProbing()79 bool AbstractDb::openForProbing()
80 {
81     QWriteLocker locker(&dbOperLock);
82     QWriteLocker connectionLocker(&connectionStateLock);
83     bool res = openInternal();
84     if (!res)
85         return res;
86 
87     // Implementation specific initialization
88     initAfterOpen();
89     return res;
90 }
91 
registerAllFunctions()92 void AbstractDb::registerAllFunctions()
93 {
94     for (const RegisteredFunction& regFn : registeredFunctions)
95     {
96         if (!deregisterFunction(regFn.name, regFn.argCount))
97             qWarning() << "Failed to deregister custom SQL function:" << regFn.name;
98     }
99 
100     registeredFunctions.clear();
101 
102     RegisteredFunction regFn;
103     for (FunctionManager::ScriptFunction* fnPtr : FUNCTIONS->getScriptFunctionsForDatabase(getName()))
104     {
105         regFn.argCount = fnPtr->undefinedArgs ? -1 : fnPtr->arguments.count();
106         regFn.name = fnPtr->name;
107         regFn.type = fnPtr->type;
108         registerFunction(regFn);
109     }
110 
111     for (FunctionManager::NativeFunction* fnPtr : FUNCTIONS->getAllNativeFunctions())
112     {
113         regFn.argCount = fnPtr->undefinedArgs ? -1 : fnPtr->arguments.count();
114         regFn.name = fnPtr->name;
115         regFn.type = fnPtr->type;
116         registerFunction(regFn);
117     }
118 
119     disconnect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions()));
120     connect(FUNCTIONS, SIGNAL(functionListChanged()), this, SLOT(registerAllFunctions()));
121 }
122 
registerAllCollations()123 void AbstractDb::registerAllCollations()
124 {
125     for (const QString& name : registeredCollations)
126     {
127         if (!deregisterCollation(name))
128             qWarning() << "Failed to deregister custom collation:" << name;
129     }
130 
131     registeredCollations.clear();
132 
133     for (const CollationManager::CollationPtr& collPtr : COLLATIONS->getCollationsForDatabase(getName()))
134         registerCollation(collPtr->name);
135 
136     disconnect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations()));
137     connect(COLLATIONS, SIGNAL(collationListChanged()), this, SLOT(registerAllCollations()));
138 }
139 
loadExtensions()140 void AbstractDb::loadExtensions()
141 {
142     for (const SqliteExtensionManager::ExtensionPtr& extPtr : SQLITE_EXTENSIONS->getExtensionForDatabase(getName()))
143         loadedExtensionCount += loadExtension(extPtr->filePath, extPtr->initFunc) ? 1 : 0;
144 
145     connect(SQLITE_EXTENSIONS, SIGNAL(extensionListChanged()), this, SLOT(reloadExtensions()));
146 }
147 
reloadExtensions()148 void AbstractDb::reloadExtensions()
149 {
150     if (!isOpen())
151         return;
152 
153     bool doOpen = false;
154     if (loadedExtensionCount > 0)
155     {
156         if (!closeQuiet())
157         {
158             qWarning() << "Failed to close database for extension reloading.";
159             return;
160         }
161 
162         doOpen = true;
163         loadedExtensionCount = 0;
164         disconnect(SQLITE_EXTENSIONS, SIGNAL(extensionListChanged()), this, SLOT(reloadExtensions()));
165     }
166 
167     if (doOpen && !openQuiet())
168     {
169         qCritical() << "Failed to re-open database for extension reloading.";
170         return;
171     }
172 
173     loadExtensions();
174 }
175 
isOpen()176 bool AbstractDb::isOpen()
177 {
178     // We use separate mutex for connection state to avoid situations, when some query is being executed,
179     // and we cannot check if database is open, which is not invasive method call.
180     QReadLocker connectionLocker(&connectionStateLock);
181     return isOpenInternal();
182 }
183 
generateUniqueDbName(bool lock)184 QString AbstractDb::generateUniqueDbName(bool lock)
185 {
186     if (lock)
187     {
188         QReadLocker locker(&dbOperLock);
189         return generateUniqueDbNameNoLock();
190     }
191     else
192     {
193         return generateUniqueDbNameNoLock();
194     }
195 }
196 
generateUniqueDbNameNoLock()197 QString AbstractDb::generateUniqueDbNameNoLock()
198 {
199     SqlQueryPtr results = exec("PRAGMA database_list;", Db::Flag::NO_LOCK);
200     if (results->isError())
201     {
202         qWarning() << "Could not get PRAGMA database_list. Falling back to internal db list. Error was:" << results->getErrorText();
203         return generateUniqueName("attached", attachedDbMap.leftValues());
204     }
205 
206     QStringList existingDatabases;
207     for (SqlResultsRowPtr row : results->getAll())
208         existingDatabases << row->value("name").toString();
209 
210     return generateUniqueName("attached", existingDatabases);
211 }
212 
getLockingMode(const QString & query,Flags flags)213 ReadWriteLocker::Mode AbstractDb::getLockingMode(const QString &query, Flags flags)
214 {
215     return ReadWriteLocker::getMode(query, flags.testFlag(Flag::NO_LOCK));
216 }
217 
getName() const218 QString AbstractDb::getName() const
219 {
220     return name;
221 }
222 
getPath() const223 QString AbstractDb::getPath() const
224 {
225     return path;
226 }
227 
getVersion() const228 quint8 AbstractDb::getVersion() const
229 {
230     return version;
231 }
232 
getEncoding()233 QString AbstractDb::getEncoding()
234 {
235     bool doClose = false;
236     if (!isOpen())
237     {
238         if (!openQuiet())
239             return QString();
240 
241         doClose = true;
242     }
243     QString encoding = exec("PRAGMA encoding;")->getSingleCell().toString();
244     if (doClose)
245         closeQuiet();
246 
247     return encoding;
248 }
249 
getConnectionOptions()250 QHash<QString, QVariant>& AbstractDb::getConnectionOptions()
251 {
252     return connOptions;
253 }
254 
setName(const QString & value)255 void AbstractDb::setName(const QString& value)
256 {
257     if (isOpen())
258     {
259         qWarning() << "Tried to change database's name while the database was open.";
260         return;
261     }
262     name = value;
263 }
264 
setPath(const QString & value)265 void AbstractDb::setPath(const QString& value)
266 {
267     if (isOpen())
268     {
269         qWarning() << "Tried to change database's file path while the database was open.";
270         return;
271     }
272     path = value;
273 }
274 
setConnectionOptions(const QHash<QString,QVariant> & value)275 void AbstractDb::setConnectionOptions(const QHash<QString, QVariant>& value)
276 {
277     if (isOpen())
278     {
279         qWarning() << "Tried to change database's connection options while the database was open.";
280         return;
281     }
282     connOptions = value;
283 }
284 
exec(const QString & query,AbstractDb::Flags flags)285 SqlQueryPtr AbstractDb::exec(const QString& query, AbstractDb::Flags flags)
286 {
287     return exec(query, QList<QVariant>(), flags);
288 }
289 
exec(const QString & query,const QVariant & arg)290 SqlQueryPtr AbstractDb::exec(const QString& query, const QVariant& arg)
291 {
292     return exec(query, {arg});
293 }
294 
exec(const QString & query,std::initializer_list<QVariant> argList)295 SqlQueryPtr AbstractDb::exec(const QString& query, std::initializer_list<QVariant> argList)
296 {
297     return exec(query, QList<QVariant>(argList));
298 }
299 
exec(const QString & query,std::initializer_list<std::pair<QString,QVariant>> argMap)300 SqlQueryPtr AbstractDb::exec(const QString &query, std::initializer_list<std::pair<QString, QVariant> > argMap)
301 {
302     return exec(query, QHash<QString,QVariant>(argMap));
303 }
304 
asyncExec(const QString & query,const QList<QVariant> & args,AbstractDb::QueryResultsHandler resultsHandler,AbstractDb::Flags flags)305 void AbstractDb::asyncExec(const QString &query, const QList<QVariant> &args, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags)
306 {
307     quint32 asyncId = asyncExec(query, args, flags);
308     resultHandlers[asyncId] = resultsHandler;
309 }
310 
asyncExec(const QString & query,const QHash<QString,QVariant> & args,AbstractDb::QueryResultsHandler resultsHandler,AbstractDb::Flags flags)311 void AbstractDb::asyncExec(const QString &query, const QHash<QString, QVariant> &args, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags)
312 {
313     quint32 asyncId = asyncExec(query, args, flags);
314     resultHandlers[asyncId] = resultsHandler;
315 }
316 
asyncExec(const QString & query,AbstractDb::QueryResultsHandler resultsHandler,AbstractDb::Flags flags)317 void AbstractDb::asyncExec(const QString &query, AbstractDb::QueryResultsHandler resultsHandler, AbstractDb::Flags flags)
318 {
319     quint32 asyncId = asyncExec(query, flags);
320     resultHandlers[asyncId] = resultsHandler;
321 }
322 
exec(const QString & query,const QList<QVariant> & args,Flags flags)323 SqlQueryPtr AbstractDb::exec(const QString &query, const QList<QVariant>& args, Flags flags)
324 {
325     return execListArg(query, args, flags);
326 }
327 
exec(const QString & query,const QHash<QString,QVariant> & args,AbstractDb::Flags flags)328 SqlQueryPtr AbstractDb::exec(const QString& query, const QHash<QString, QVariant>& args, AbstractDb::Flags flags)
329 {
330     return execHashArg(query, args, flags);
331 }
332 
execHashArg(const QString & query,const QHash<QString,QVariant> & args,Flags flags)333 SqlQueryPtr AbstractDb::execHashArg(const QString& query, const QHash<QString,QVariant>& args, Flags flags)
334 {
335     if (!isOpenInternal())
336         return SqlQueryPtr(new SqlErrorResults(SqlErrorCode::DB_NOT_OPEN, tr("Cannot execute query on closed database.")));
337 
338     QString newQuery = query;
339     SqlQueryPtr queryStmt = prepare(newQuery);
340     queryStmt->setArgs(args);
341     queryStmt->setFlags(flags);
342     queryStmt->execute();
343 
344     if (flags.testFlag(Flag::PRELOAD))
345         queryStmt->preload();
346 
347     return queryStmt;
348 }
349 
execListArg(const QString & query,const QList<QVariant> & args,Flags flags)350 SqlQueryPtr AbstractDb::execListArg(const QString& query, const QList<QVariant>& args, Flags flags)
351 {
352     if (!isOpenInternal())
353         return SqlQueryPtr(new SqlErrorResults(SqlErrorCode::DB_NOT_OPEN, tr("Cannot execute query on closed database.")));
354 
355     QString newQuery = query;
356     SqlQueryPtr queryStmt = prepare(newQuery);
357     queryStmt->setArgs(args);
358     queryStmt->setFlags(flags);
359     queryStmt->execute();
360 
361     if (flags.testFlag(Flag::PRELOAD))
362         queryStmt->preload();
363 
364     return queryStmt;
365 }
366 
openAndSetup()367 bool AbstractDb::openAndSetup()
368 {
369     bool result = openInternal();
370     if (!result)
371         return result;
372 
373     // When this is an internal configuration database
374     if (connOptions.contains(DB_PURE_INIT))
375         return true;
376 
377     // Implementation specific initialization
378     initAfterOpen();
379 
380     // Load extension
381     loadExtensions();
382 
383     // Custom SQL functions
384     registerAllFunctions();
385 
386     // Custom collations
387     registerAllCollations();
388 
389     return result;
390 }
391 
initAfterOpen()392 void AbstractDb::initAfterOpen()
393 {
394 }
395 
checkForDroppedObject(const QString & query)396 void AbstractDb::checkForDroppedObject(const QString& query)
397 {
398     TokenList tokens = Lexer::tokenize(query);
399     tokens.trim(Token::OPERATOR, ";");
400     if (tokens.size() == 0)
401         return;
402 
403     if (tokens[0]->type != Token::KEYWORD || tokens.first()->value.toUpper() != "DROP")
404         return;
405 
406     tokens.removeFirst(); // remove "DROP" from front
407     tokens.trimLeft(); // remove whitespaces and comments from front
408     if (tokens.size() == 0)
409     {
410         qWarning() << "Successful execution of DROP, but after removing DROP from front of the query, nothing has left. Original query:" << query;
411         return;
412     }
413 
414     QString type = tokens.first()->value.toUpper();
415 
416     // Now go to the first ID in the tokens
417     while (tokens.size() > 0 && tokens.first()->type != Token::OTHER)
418         tokens.removeFirst();
419 
420     if (tokens.size() == 0)
421     {
422         qWarning() << "Successful execution of DROP, but after removing DROP and non-ID tokens from front of the query, nothing has left. Original query:" << query;
423         return;
424     }
425 
426     QString database = "main";
427     QString object;
428 
429     if (tokens.size() > 1)
430     {
431         database = tokens.first()->value;
432         object = tokens.last()->value;
433     }
434     else
435         object = tokens.first()->value;
436 
437     object = stripObjName(object);
438 
439     if (type == "TABLE")
440         emit dbObjectDeleted(database, object, DbObjectType::TABLE);
441     else if (type == "INDEX")
442         emit dbObjectDeleted(database, object, DbObjectType::INDEX);
443     else if (type == "TRIGGER")
444         emit dbObjectDeleted(database, object, DbObjectType::TRIGGER);
445     else if (type == "VIEW")
446         emit dbObjectDeleted(database, object, DbObjectType::VIEW);
447     else
448         qWarning() << "Unknown object type dropped:" << type;
449 }
450 
registerCollation(const QString & name)451 bool AbstractDb::registerCollation(const QString& name)
452 {
453     if (registeredCollations.contains(name))
454     {
455         qCritical() << "Collation" << name << "is already registered!"
456                     << "It should already be deregistered while call to register is being made.";
457         return false;
458     }
459 
460     if (registerCollationInternal(name))
461     {
462         registeredCollations << name;
463         return true;
464     }
465 
466     qCritical() << "Could not register collation:" << name;
467     return false;
468 }
469 
deregisterCollation(const QString & name)470 bool AbstractDb::deregisterCollation(const QString& name)
471 {
472     if (!registeredCollations.contains(name))
473     {
474         qCritical() << "Collation" << name << "not registered!"
475                     << "It should already registered while call to deregister is being made.";
476         return false;
477     }
478 
479     if (deregisterCollationInternal(name))
480     {
481         registeredCollations.removeOne(name);
482         return true;
483     }
484     qWarning() << "Could not deregister collation:" << name;
485     return false;
486 }
487 
isCollationRegistered(const QString & name)488 bool AbstractDb::isCollationRegistered(const QString& name)
489 {
490     return registeredCollations.contains(name);
491 }
492 
getAggregateContext(void * memPtr)493 QHash<QString, QVariant> AbstractDb::getAggregateContext(void* memPtr)
494 {
495     if (!memPtr)
496     {
497         qCritical() << "Could not allocate aggregate context.";
498         return QHash<QString, QVariant>();
499     }
500 
501     QHash<QString,QVariant>** aggCtxPtr = reinterpret_cast<QHash<QString,QVariant>**>(memPtr);
502     if (!*aggCtxPtr)
503         *aggCtxPtr = new QHash<QString,QVariant>();
504 
505     return **aggCtxPtr;
506 }
507 
setAggregateContext(void * memPtr,const QHash<QString,QVariant> & aggregateContext)508 void AbstractDb::setAggregateContext(void* memPtr, const QHash<QString, QVariant>& aggregateContext)
509 {
510     if (!memPtr)
511     {
512         qCritical() << "Could not extract aggregate context.";
513         return;
514     }
515 
516     QHash<QString,QVariant>** aggCtxPtr = reinterpret_cast<QHash<QString,QVariant>**>(memPtr);
517     **aggCtxPtr = aggregateContext;
518 }
519 
releaseAggregateContext(void * memPtr)520 void AbstractDb::releaseAggregateContext(void* memPtr)
521 {
522     if (!memPtr)
523     {
524         qCritical() << "Could not release aggregate context.";
525         return;
526     }
527 
528     QHash<QString,QVariant>** aggCtxPtr = reinterpret_cast<QHash<QString,QVariant>**>(memPtr);
529     delete *aggCtxPtr;
530 }
531 
evaluateScalar(void * dataPtr,const QList<QVariant> & argList,bool & ok)532 QVariant AbstractDb::evaluateScalar(void* dataPtr, const QList<QVariant>& argList, bool& ok)
533 {
534     if (!dataPtr)
535         return QVariant();
536 
537     FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr);
538 
539     return FUNCTIONS->evaluateScalar(userData->name, userData->argCount, argList, userData->db, ok);
540 }
541 
evaluateAggregateStep(void * dataPtr,QHash<QString,QVariant> & aggregateContext,QList<QVariant> argList)542 void AbstractDb::evaluateAggregateStep(void* dataPtr, QHash<QString, QVariant>& aggregateContext, QList<QVariant> argList)
543 {
544     if (!dataPtr)
545         return;
546 
547     FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr);
548 
549     QHash<QString,QVariant> storage = aggregateContext["storage"].toHash();
550     if (!aggregateContext.contains("initExecuted"))
551     {
552         FUNCTIONS->evaluateAggregateInitial(userData->name, userData->argCount, userData->db, storage);
553         aggregateContext["initExecuted"] = true;
554     }
555 
556     FUNCTIONS->evaluateAggregateStep(userData->name, userData->argCount, argList, userData->db, storage);
557     aggregateContext["storage"] = storage;
558 }
559 
evaluateAggregateFinal(void * dataPtr,QHash<QString,QVariant> & aggregateContext,bool & ok)560 QVariant AbstractDb::evaluateAggregateFinal(void* dataPtr, QHash<QString, QVariant>& aggregateContext, bool& ok)
561 {
562     if (!dataPtr)
563         return QVariant();
564 
565     FunctionUserData* userData = reinterpret_cast<FunctionUserData*>(dataPtr);
566     QHash<QString,QVariant> storage = aggregateContext["storage"].toHash();
567 
568     return FUNCTIONS->evaluateAggregateFinal(userData->name, userData->argCount, userData->db, ok, storage);
569 }
570 
asyncExec(const QString & query,Flags flags)571 quint32 AbstractDb::asyncExec(const QString &query, Flags flags)
572 {
573     AsyncQueryRunner* runner = new AsyncQueryRunner(query, QList<QVariant>(), flags);
574     return asyncExec(runner);
575 }
576 
asyncExec(const QString & query,const QHash<QString,QVariant> & args,AbstractDb::Flags flags)577 quint32 AbstractDb::asyncExec(const QString& query, const QHash<QString, QVariant>& args, AbstractDb::Flags flags)
578 {
579     AsyncQueryRunner* runner = new AsyncQueryRunner(query, args, flags);
580     return asyncExec(runner);
581 }
582 
asyncExec(const QString & query,const QList<QVariant> & args,AbstractDb::Flags flags)583 quint32 AbstractDb::asyncExec(const QString& query, const QList<QVariant>& args, AbstractDb::Flags flags)
584 {
585     AsyncQueryRunner* runner = new AsyncQueryRunner(query, args, flags);
586     return asyncExec(runner);
587 }
588 
asyncExec(AsyncQueryRunner * runner)589 quint32 AbstractDb::asyncExec(AsyncQueryRunner *runner)
590 {
591     quint32 asyncId = generateAsyncId();
592     runner->setDb(this);
593     runner->setAsyncId(asyncId);
594 
595     connect(runner, SIGNAL(finished(AsyncQueryRunner*)),
596             this, SLOT(asyncQueryFinished(AsyncQueryRunner*)));
597 
598     QThreadPool::globalInstance()->start(runner);
599 
600     return asyncId;
601 }
602 
asyncQueryFinished(AsyncQueryRunner * runner)603 void AbstractDb::asyncQueryFinished(AsyncQueryRunner *runner)
604 {
605     // Extract everything from the runner
606     SqlQueryPtr results = runner->getResults();
607     quint32 asyncId = runner->getAsyncId();
608     delete runner;
609 
610     if (handleResultInternally(asyncId, results))
611         return;
612 
613     emit asyncExecFinished(asyncId, results);
614 
615     if (isReadable() && isWritable())
616         emit idle();
617 }
618 
attach(Db * otherDb,bool silent)619 QString AbstractDb::attach(Db* otherDb, bool silent)
620 {
621     QWriteLocker locker(&dbOperLock);
622     if (!isOpenInternal())
623         return QString();
624 
625     if (attachedDbMap.containsRight(otherDb))
626     {
627         attachCounter[otherDb]++;
628         return attachedDbMap.valueByRight(otherDb);
629     }
630 
631     QString attName = generateUniqueDbName(false);
632     SqlQueryPtr results = exec(getAttachSql(otherDb, attName), Flag::NO_LOCK);
633     if (results->isError())
634     {
635         if (!silent)
636             notifyError(tr("Error attaching database %1: %2").arg(otherDb->getName()).arg(results->getErrorText()));
637         else
638             qDebug() << QString("Error attaching database %1: %2").arg(otherDb->getName()).arg(results->getErrorText());
639 
640         return QString();
641     }
642 
643     attachedDbMap.insert(attName, otherDb);
644 
645     emit attached(otherDb);
646     return attName;
647 }
648 
detach(Db * otherDb)649 void AbstractDb::detach(Db* otherDb)
650 {
651     QWriteLocker locker(&dbOperLock);
652 
653     if (!isOpenInternal())
654         return;
655 
656     detachInternal(otherDb);
657 }
658 
detachInternal(Db * otherDb)659 void AbstractDb::detachInternal(Db* otherDb)
660 {
661     if (!attachedDbMap.containsRight(otherDb))
662         return;
663 
664     if (attachCounter.contains(otherDb))
665     {
666         attachCounter[otherDb]--;
667         return;
668     }
669 
670     QString dbName = attachedDbMap.valueByRight(otherDb);
671     SqlQueryPtr res = exec(QString("DETACH %1;").arg(dbName), Flag::NO_LOCK);
672     if (res->isError())
673     {
674         qCritical() << "Cannot detach" << dbName << " / " << otherDb->getName() << ":" << res->getErrorText();
675         return;
676     }
677     attachedDbMap.removeRight(otherDb);
678     emit detached(otherDb);
679 }
680 
clearAttaches()681 void AbstractDb::clearAttaches()
682 {
683     attachedDbMap.clear();
684     attachCounter.clear();
685 }
686 
detachAll()687 void AbstractDb::detachAll()
688 {
689     QWriteLocker locker(&dbOperLock);
690 
691     if (!isOpenInternal())
692         return;
693 
694     for (Db* db : attachedDbMap.rightValues())
695         detachInternal(db);
696 }
697 
getAttachedDatabases()698 const QHash<Db *, QString> &AbstractDb::getAttachedDatabases()
699 {
700     QReadLocker locker(&dbOperLock);
701     return attachedDbMap.toInvertedQHash();
702 }
703 
getAllAttaches()704 QSet<QString> AbstractDb::getAllAttaches()
705 {
706     QReadLocker locker(&dbOperLock);
707     QSet<QString> attaches = toSet(attachedDbMap.leftValues());
708     // TODO query database for attached databases and unite them here
709     return attaches;
710 }
711 
getUniqueNewObjectName(const QString & attachedDbName)712 QString AbstractDb::getUniqueNewObjectName(const QString &attachedDbName)
713 {
714     QString dbName = getPrefixDb(attachedDbName);
715 
716     QSet<QString> existingNames;
717     SqlQueryPtr results = exec(QString("SELECT name FROM %1.sqlite_master").arg(dbName));
718 
719     for (SqlResultsRowPtr row : results->getAll())
720         existingNames << row->value(0).toString();
721 
722     return randStrNotIn(16, existingNames, false);
723 }
724 
getErrorText()725 QString AbstractDb::getErrorText()
726 {
727     QReadLocker locker(&dbOperLock);
728     return getErrorTextInternal();
729 }
730 
getErrorCode()731 int AbstractDb::getErrorCode()
732 {
733     QReadLocker locker(&dbOperLock);
734     return getErrorCodeInternal();
735 }
736 
initAfterCreated()737 bool AbstractDb::initAfterCreated()
738 {
739     return true;
740 }
741 
setTimeout(int secs)742 void AbstractDb::setTimeout(int secs)
743 {
744     timeout = secs;
745 }
746 
getTimeout() const747 int AbstractDb::getTimeout() const
748 {
749     return timeout;
750 }
751 
isValid() const752 bool AbstractDb::isValid() const
753 {
754     return true;
755 }
756 
getAttachSql(Db * otherDb,const QString & generatedAttachName)757 QString AbstractDb::getAttachSql(Db* otherDb, const QString& generatedAttachName)
758 {
759     return QString("ATTACH '%1' AS %2;").arg(otherDb->getPath(), generatedAttachName);
760 }
761 
generateAsyncId()762 quint32 AbstractDb::generateAsyncId()
763 {
764     if (asyncId > 4000000000)
765         asyncId = 1;
766 
767     return asyncId++;
768 }
769 
begin()770 bool AbstractDb::begin()
771 {
772     QWriteLocker locker(&dbOperLock);
773 
774     if (!isOpenInternal())
775         return false;
776 
777     SqlQueryPtr results = exec("BEGIN;", Flag::NO_LOCK);
778     if (results->isError())
779     {
780         qCritical() << "Error while starting a transaction: " << results->getErrorCode() << results->getErrorText();
781         return false;
782     }
783 
784     return true;
785 }
786 
commit()787 bool AbstractDb::commit()
788 {
789     QWriteLocker locker(&dbOperLock);
790 
791     if (!isOpenInternal())
792         return false;
793 
794     SqlQueryPtr results = exec("COMMIT;", Flag::NO_LOCK);
795     if (results->isError())
796     {
797         qCritical() << "Error while committing a transaction: " << results->getErrorCode() << results->getErrorText();
798         return false;
799     }
800 
801     return true;
802 }
803 
rollback()804 bool AbstractDb::rollback()
805 {
806     QWriteLocker locker(&dbOperLock);
807 
808     if (!isOpenInternal())
809         return false;
810 
811     SqlQueryPtr results = exec("ROLLBACK;", Flag::NO_LOCK);
812     if (results->isError())
813     {
814         qCritical() << "Error while rolling back a transaction: " << results->getErrorCode() << results->getErrorText();
815         return false;
816     }
817 
818     return true;
819 }
820 
interrupt()821 void AbstractDb::interrupt()
822 {
823     // Lock connection state to forbid closing db before interrupt() returns.
824     // This is required by SQLite.
825     QWriteLocker locker(&connectionStateLock);
826     interruptExecution();
827 }
828 
asyncInterrupt()829 void AbstractDb::asyncInterrupt()
830 {
831     QtConcurrent::run(this, &AbstractDb::interrupt);
832 }
833 
isReadable()834 bool AbstractDb::isReadable()
835 {
836     bool res = dbOperLock.tryLockForRead();
837     if (res)
838         dbOperLock.unlock();
839 
840     return res;
841 }
842 
isWritable()843 bool AbstractDb::isWritable()
844 {
845     bool res = dbOperLock.tryLockForWrite();
846     if (res)
847         dbOperLock.unlock();
848 
849     return res;
850 }
851 
guardedAttach(Db * otherDb,bool silent)852 AttachGuard AbstractDb::guardedAttach(Db* otherDb, bool silent)
853 {
854     QString attachName = attach(otherDb, silent);
855     return AttachGuard::create(this, otherDb, attachName);
856 }
857 
handleResultInternally(quint32 asyncId,SqlQueryPtr results)858 bool AbstractDb::handleResultInternally(quint32 asyncId, SqlQueryPtr results)
859 {
860     if (!resultHandlers.contains(asyncId))
861         return false;
862 
863     resultHandlers[asyncId](results);
864     resultHandlers.remove(asyncId);
865 
866     return true;
867 }
868 
registerFunction(const AbstractDb::RegisteredFunction & function)869 void AbstractDb::registerFunction(const AbstractDb::RegisteredFunction& function)
870 {
871     if (registeredFunctions.contains(function))
872         return; // native function was overwritten by script function
873 
874     bool successful = false;
875     switch (function.type)
876     {
877         case FunctionManager::ScriptFunction::SCALAR:
878             successful = registerScalarFunction(function.name, function.argCount);
879             break;
880         case FunctionManager::ScriptFunction::AGGREGATE:
881             successful = registerAggregateFunction(function.name, function.argCount);
882             break;
883     }
884 
885     if (successful)
886         registeredFunctions << function;
887     else
888         qCritical() << "Could not register SQL function:" << function.name << function.argCount << function.type;
889 }
890 
qHash(const AbstractDb::RegisteredFunction & fn)891 int qHash(const AbstractDb::RegisteredFunction& fn)
892 {
893     return qHash(fn.name) ^ fn.argCount ^ fn.type;
894 }
895 
operator ==(const AbstractDb::RegisteredFunction & fn1,const AbstractDb::RegisteredFunction & fn2)896 bool operator==(const AbstractDb::RegisteredFunction& fn1, const AbstractDb::RegisteredFunction& fn2)
897 {
898     return fn1.name == fn2.name && fn1.argCount == fn2.argCount && fn1.type == fn2.type;
899 }
900