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