adatabase.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. /*
  2. *openPilotLog - A FOSS Pilot Logbook Application
  3. *Copyright (C) 2020-2021 Felix Turowsky
  4. *
  5. *This program is free software: you can redistribute it and/or modify
  6. *it under the terms of the GNU General Public License as published by
  7. *the Free Software Foundation, either version 3 of the License, or
  8. *(at your option) any later version.
  9. *
  10. *This program is distributed in the hope that it will be useful,
  11. *but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. *GNU General Public License for more details.
  14. *
  15. *You should have received a copy of the GNU General Public License
  16. *along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. #include "adatabase.h"
  19. #include "src/functions/alog.h"
  20. #include "src/classes/astandardpaths.h"
  21. #include "src/opl.h"
  22. #include "src/functions/alog.h"
  23. const int ADatabase::minimumDatabaseRevision = 0;
  24. const QStringList ADatabase::userTableNames = {
  25. QStringLiteral("flights"),
  26. QStringLiteral("pilots"),
  27. QStringLiteral("tails")
  28. };
  29. const QStringList ADatabase::templateTableNames = {
  30. QStringLiteral("aircraft"),
  31. QStringLiteral("airports"),
  32. QStringLiteral("currencies"),
  33. QStringLiteral("changelog")
  34. };
  35. ADatabaseError::ADatabaseError(QString msg_)
  36. : QSqlError::QSqlError(QString(), msg_, QSqlError::UnknownError, QLatin1String("opl"))
  37. {}
  38. ADatabaseError::ADatabaseError(QSqlError err_)
  39. : QSqlError(err_)
  40. {};
  41. QString ADatabaseError::text() const
  42. {
  43. return QLatin1String("Database Error: ") + QSqlError::text();
  44. }
  45. ADatabase* ADatabase::self = nullptr;
  46. ADatabase::ADatabase()
  47. : databaseFile(QFileInfo(AStandardPaths::directory(AStandardPaths::Database).
  48. absoluteFilePath(QStringLiteral("logbook.db"))
  49. )
  50. )
  51. {}
  52. int ADatabase::dbRevision() const
  53. {
  54. return databaseRevision;
  55. }
  56. int ADatabase::checkDbVersion() const
  57. {
  58. QSqlQuery query(QStringLiteral("SELECT COUNT(*) FROM changelog"));
  59. query.next();
  60. return query.value(0).toInt();
  61. }
  62. int ADatabase::getMinimumDatabaseRevision()
  63. {
  64. return minimumDatabaseRevision;
  65. }
  66. QStringList ADatabase::getTemplateTableNames()
  67. {
  68. return templateTableNames;
  69. }
  70. QStringList ADatabase::getUserTableNames()
  71. {
  72. return userTableNames;
  73. }
  74. const ColumnNames_T ADatabase::getTableColumns(TableName_T table_name) const
  75. {
  76. return tableColumns.value(table_name);
  77. }
  78. const TableNames_T ADatabase::getTableNames() const
  79. {
  80. return tableNames;
  81. }
  82. void ADatabase::updateLayout()
  83. {
  84. auto db = ADatabase::database();
  85. tableNames = db.tables();
  86. tableColumns.clear();
  87. for (const auto &table_name : qAsConst(tableNames)) {
  88. ColumnNames_T table_columns;
  89. QSqlRecord fields = db.record(table_name);
  90. for (int i = 0; i < fields.count(); i++) {
  91. table_columns.append(fields.field(i).name());
  92. }
  93. tableColumns.insert(table_name, table_columns);
  94. }
  95. emit dataBaseUpdated();
  96. }
  97. ADatabase* ADatabase::instance()
  98. {
  99. #ifdef __GNUC__
  100. return self ?: self = new ADatabase(); // Cheeky business...
  101. #else
  102. if(!self)
  103. self = new ADatabase();
  104. return self;
  105. #endif
  106. }
  107. const QString ADatabase::sqliteVersion() const
  108. {
  109. QSqlQuery query;
  110. query.prepare(QStringLiteral("SELECT sqlite_version()"));
  111. query.exec();
  112. query.next();
  113. return query.value(0).toString();
  114. }
  115. bool ADatabase::connect()
  116. {
  117. if (!QSqlDatabase::isDriverAvailable(SQLITE_DRIVER)) {
  118. LOG << "Error: No SQLITE Driver availabe.";
  119. return false;
  120. }
  121. QSqlDatabase db = QSqlDatabase::addDatabase(SQLITE_DRIVER);
  122. db.setDatabaseName(databaseFile.absoluteFilePath());
  123. if (!db.open()) {
  124. LOG << QString("Unable to establish database connection.<br>The following error has ocurred:<br><br>%1")
  125. .arg(db.lastError().databaseText());
  126. lastError = db.lastError();
  127. return false;
  128. }
  129. LOG << "Database connection established.";
  130. // Enable foreign key restrictions
  131. QSqlQuery query;
  132. query.prepare(QStringLiteral("PRAGMA foreign_keys = ON;"));
  133. query.exec();
  134. updateLayout();
  135. databaseRevision = checkDbVersion();
  136. return true;
  137. }
  138. void ADatabase::disconnect()
  139. {
  140. auto db = ADatabase::database();
  141. db.close();
  142. LOG << "Database connection closed.";
  143. }
  144. QSqlDatabase ADatabase::database()
  145. {
  146. return QSqlDatabase::database(QStringLiteral("qt_sql_default_connection"));
  147. }
  148. bool ADatabase::commit(AEntry entry)
  149. {
  150. if (exists(entry)) {
  151. return update(entry);
  152. } else {
  153. return insert(entry);
  154. }
  155. }
  156. bool ADatabase::remove(AEntry entry)
  157. {
  158. if (!exists(entry)) {
  159. DEB << "Error: Database entry not found.";
  160. lastError = ADatabaseError(QStringLiteral("Database entry not found."));
  161. return false;
  162. }
  163. QString statement = QLatin1String("DELETE FROM ") + entry.getPosition().tableName
  164. + QLatin1String(" WHERE ROWID=?");
  165. QSqlQuery query;
  166. query.prepare(statement);
  167. query.addBindValue(entry.getPosition().rowId);
  168. if (!query.exec())
  169. {
  170. DEB << "Entry " << entry.getPosition() << " removed.";
  171. emit dataBaseUpdated();
  172. return true;
  173. } else {
  174. DEB << "Unable to delete.";
  175. DEB << "Query: " << statement;
  176. DEB << "Query Error: " << query.lastError().text();
  177. lastError = query.lastError();
  178. return false;
  179. }
  180. }
  181. bool ADatabase::removeMany(QList<DataPosition> data_position_list)
  182. {
  183. int errorCount = 0;
  184. QSqlQuery query;
  185. query.prepare(QStringLiteral("BEGIN EXCLUSIVE TRANSACTION"));
  186. query.exec();
  187. for (const auto& data_position : data_position_list) {
  188. if (!exists(data_position)) {
  189. lastError = ADatabaseError("Database entry not found.");
  190. errorCount++;
  191. }
  192. QString statement = QLatin1String("DELETE FROM ") + data_position.tableName +
  193. QLatin1String(" WHERE ROWID=?");
  194. query.prepare(statement);
  195. query.addBindValue(data_position.rowId);
  196. if (!query.exec())
  197. errorCount++;
  198. }
  199. if (errorCount == 0) {
  200. query.prepare(QStringLiteral("COMMIT"));
  201. if(query.exec()) {
  202. emit dataBaseUpdated();
  203. return true;
  204. } else {
  205. DEB << "Transaction unsuccessful (Interrupted). Error count: "
  206. + QString::number(errorCount);
  207. lastError = query.lastError();
  208. return false;
  209. }
  210. } else {
  211. query.prepare(QStringLiteral("ROLLBACK"));
  212. query.exec();
  213. DEB << "Transaction unsuccessful (no changes have been made). Error count: "
  214. + QString::number(errorCount);
  215. return false;
  216. }
  217. }
  218. bool ADatabase::exists(AEntry entry)
  219. {
  220. if(entry.getPosition().rowId == 0)
  221. return false;
  222. //Check database for row id
  223. QString statement = QLatin1String("SELECT COUNT(*) FROM ") + entry.getPosition().tableName
  224. + QLatin1String(" WHERE ROWID=?");
  225. QSqlQuery query;
  226. query.prepare(statement);
  227. query.addBindValue(entry.getPosition().rowId);
  228. query.setForwardOnly(true);
  229. query.exec();
  230. //this returns either 1 or 0 since row ids are unique
  231. if (!query.isActive()) {
  232. lastError = query.lastError();
  233. DEB << "Query Error: " << query.lastError().text() << statement;
  234. return false;
  235. }
  236. query.next();
  237. int rowId = query.value(0).toInt();
  238. if (rowId) {
  239. return true;
  240. } else {
  241. DEB << "Database entry not found.";
  242. lastError = ADatabaseError(QStringLiteral("Database entry not found."));
  243. return false;
  244. }
  245. }
  246. bool ADatabase::exists(DataPosition data_position)
  247. {
  248. if(data_position.rowId == 0)
  249. return false;
  250. //Check database for row id
  251. QString statement = QLatin1String("SELECT COUNT(*) FROM ") + data_position.tableName
  252. + QLatin1String(" WHERE ROWID=?");
  253. QSqlQuery query;
  254. query.prepare(statement);
  255. query.addBindValue(data_position.rowId);
  256. query.setForwardOnly(true);
  257. query.exec();
  258. //this returns either 1 or 0 since row ids are unique
  259. if (!query.isActive()) {
  260. lastError = query.lastError();
  261. DEB << "Query Error: " << query.lastError().text() << statement;
  262. }
  263. query.next();
  264. int rowId = query.value(0).toInt();
  265. if (rowId) {
  266. return true;
  267. } else {
  268. DEB << "No entry exists at DataPosition: " << data_position.tableName << data_position.rowId;
  269. lastError = ADatabaseError(QStringLiteral("Database entry not found."));
  270. return false;
  271. }
  272. }
  273. bool ADatabase::clear()
  274. {
  275. QSqlQuery q;
  276. for (const auto &table_name : userTableNames) {
  277. q.prepare(QLatin1String("DELETE FROM ") + table_name);
  278. if (!q.exec()) {
  279. DEB << "Error: " << q.lastError().text();
  280. lastError = q.lastError();
  281. return false;
  282. }
  283. }
  284. return true;
  285. }
  286. bool ADatabase::update(AEntry updated_entry)
  287. {
  288. auto data = updated_entry.getData();
  289. QString statement = QLatin1String("UPDATE ") + updated_entry.getPosition().tableName + QLatin1String(" SET ");
  290. for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
  291. statement.append(i.key() + "=?,");
  292. }
  293. statement.chop(1);
  294. statement.append(QLatin1String(" WHERE ROWID=?"));
  295. QSqlQuery query;
  296. query.prepare(statement);
  297. for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
  298. if (i.value() == QVariant(QString()) || i.value() == 0) {
  299. query.addBindValue(QVariant(QVariant::String));
  300. } else {
  301. query.addBindValue(i.value());
  302. }
  303. }
  304. query.addBindValue(updated_entry.getPosition().rowId);
  305. if (query.exec())
  306. {
  307. DEB << "Entry successfully committed.";
  308. emit dataBaseUpdated();
  309. return true;
  310. } else {
  311. DEB << "Unable to commit.";
  312. DEB << "Query: " << statement;
  313. DEB << "Query Error: " << query.lastError().text();
  314. lastError = query.lastError();
  315. return false;
  316. }
  317. }
  318. bool ADatabase::insert(AEntry new_entry)
  319. {
  320. auto data = new_entry.getData();
  321. QString statement = QLatin1String("INSERT INTO ") + new_entry.getPosition().tableName + QLatin1String(" (");
  322. QMap<QString, QVariant>::iterator i;
  323. for (i = data.begin(); i != data.end(); ++i) {
  324. statement.append(i.key() + QLatin1Char(','));
  325. }
  326. statement.chop(1);
  327. statement += QLatin1String(") VALUES (");
  328. for (int i=0; i < data.size(); ++i) {
  329. statement += QLatin1String("?,");
  330. }
  331. statement.chop(1);
  332. statement += QLatin1Char(')');
  333. QSqlQuery query;
  334. query.prepare(statement);
  335. for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
  336. if (i.value() == QVariant(QString()) || i.value() == 0) {
  337. query.addBindValue(QVariant(QVariant::String));
  338. } else {
  339. query.addBindValue(i.value());
  340. }
  341. }
  342. //check result.
  343. if (query.exec())
  344. {
  345. DEB << "Entry successfully committed.";
  346. emit dataBaseUpdated();
  347. return true;
  348. } else {
  349. DEB << "Unable to commit.";
  350. DEB << "Query: " << statement;
  351. DEB << "Query Error: " << query.lastError().text();
  352. lastError = query.lastError();
  353. return false;
  354. }
  355. }
  356. RowData_T ADatabase::getEntryData(DataPosition data_position)
  357. {
  358. // check table exists
  359. if (!getTableNames().contains(data_position.tableName)) {
  360. DEB << data_position.tableName << " not a table in the database. Unable to retreive Entry data.";
  361. return RowData_T();
  362. }
  363. //Check Database for rowId
  364. QString statement = QLatin1String("SELECT COUNT(*) FROM ") + data_position.tableName
  365. + QLatin1String(" WHERE ROWID=?");
  366. QSqlQuery check_query;
  367. check_query.prepare(statement);
  368. check_query.addBindValue(data_position.rowId);
  369. check_query.setForwardOnly(true);
  370. if (!check_query.exec()) {
  371. DEB << "SQL error: " << check_query.lastError().text();
  372. DEB << "Statement: " << statement;
  373. lastError = check_query.lastError();
  374. return RowData_T();
  375. }
  376. check_query.next();
  377. if (check_query.value(0).toInt() == 0) {
  378. DEB << "No Entry found for row id: " << data_position.rowId;
  379. lastError = ADatabaseError("Database entry not found.");
  380. return RowData_T();
  381. }
  382. // Retreive TableData
  383. statement = QLatin1String("SELECT * FROM ") + data_position.tableName
  384. + QLatin1String(" WHERE ROWID=?");
  385. QSqlQuery select_query;
  386. select_query.prepare(statement);
  387. select_query.addBindValue(data_position.rowId);
  388. select_query.setForwardOnly(true);
  389. if (!select_query.exec()) {
  390. DEB << "SQL error: " << select_query.lastError().text();
  391. DEB << "Statement: " << select_query.lastQuery();
  392. lastError = select_query.lastError();
  393. return {};
  394. }
  395. RowData_T entry_data;
  396. if(select_query.next()) { // retreive records
  397. auto r = select_query.record();
  398. for (int i = 0; i < r.count(); i++){
  399. if(!r.value(i).isNull()) {
  400. entry_data.insert(r.fieldName(i), r.value(i));
  401. }
  402. }
  403. }
  404. return entry_data;
  405. }
  406. AEntry ADatabase::getEntry(DataPosition data_position)
  407. {
  408. AEntry entry(data_position);
  409. entry.setData(getEntryData(data_position));
  410. return entry;
  411. }
  412. APilotEntry ADatabase::getPilotEntry(RowId_T row_id)
  413. {
  414. APilotEntry pilot_entry(row_id);
  415. pilot_entry.setData(getEntryData(pilot_entry.getPosition()));
  416. return pilot_entry;
  417. }
  418. ATailEntry ADatabase::getTailEntry(RowId_T row_id)
  419. {
  420. ATailEntry tail_entry(row_id);
  421. tail_entry.setData(getEntryData(tail_entry.getPosition()));
  422. return tail_entry;
  423. }
  424. AAircraftEntry ADatabase::getAircraftEntry(RowId_T row_id)
  425. {
  426. AAircraftEntry aircraft_entry(row_id);
  427. aircraft_entry.setData(getEntryData(aircraft_entry.getPosition()));
  428. return aircraft_entry;
  429. }
  430. AFlightEntry ADatabase::getFlightEntry(RowId_T row_id)
  431. {
  432. AFlightEntry flight_entry(row_id);
  433. flight_entry.setData(getEntryData(flight_entry.getPosition()));
  434. return flight_entry;
  435. }
  436. ACurrencyEntry ADatabase::getCurrencyEntry(ACurrencyEntry::CurrencyName currency_name)
  437. {
  438. ACurrencyEntry currency_entry(currency_name);
  439. currency_entry.setData(getEntryData(currency_entry.getPosition()));
  440. return currency_entry;
  441. }
  442. const QStringList ADatabase::getCompletionList(ADatabaseTarget target)
  443. {
  444. QString statement;
  445. switch (target) {
  446. case ADatabaseTarget::pilots:
  447. statement.append(QStringLiteral("SELECT lastname||', '||firstname FROM pilots"));
  448. break;
  449. case ADatabaseTarget::aircraft:
  450. statement.append(QStringLiteral("SELECT make||' '||model FROM aircraft WHERE model IS NOT NULL "
  451. "UNION "
  452. "SELECT make||' '||model||'-'||variant FROM aircraft WHERE variant IS NOT NULL"));
  453. break;
  454. case ADatabaseTarget::airport_identifier_all:
  455. statement.append(QStringLiteral("SELECT icao FROM airports UNION SELECT iata FROM airports"));
  456. break;
  457. case ADatabaseTarget::registrations:
  458. statement.append(QStringLiteral("SELECT registration FROM tails"));
  459. break;
  460. case ADatabaseTarget::companies:
  461. statement.append(QStringLiteral("SELECT company FROM pilots"));
  462. break;
  463. default:
  464. DEB << "Not a valid completer target for this function.";
  465. return QStringList();
  466. }
  467. QSqlQuery query;
  468. query.prepare(statement);
  469. query.setForwardOnly(true);
  470. query.exec();
  471. if(!query.isActive()) {
  472. lastError = query.lastError();
  473. return QStringList();
  474. }
  475. QStringList completer_list;
  476. while (query.next())
  477. completer_list.append(query.value(0).toString());
  478. completer_list.sort();
  479. completer_list.removeAll(QString());
  480. completer_list.removeDuplicates();
  481. return completer_list;
  482. }
  483. const
  484. QMap<QString, RowId_T> ADatabase::getIdMap(ADatabaseTarget target)
  485. {
  486. QString statement;
  487. switch (target) {
  488. case ADatabaseTarget::pilots:
  489. statement.append(QStringLiteral("SELECT ROWID, lastname||', '||firstname FROM pilots"));
  490. break;
  491. case ADatabaseTarget::aircraft:
  492. statement.append(QStringLiteral("SELECT ROWID, make||' '||model FROM aircraft WHERE model IS NOT NULL "
  493. "UNION "
  494. "SELECT ROWID, make||' '||model||'-'||variant FROM aircraft WHERE variant IS NOT NULL"));
  495. break;
  496. case ADatabaseTarget::airport_identifier_icao:
  497. statement.append(QStringLiteral("SELECT ROWID, icao FROM airports"));
  498. break;
  499. case ADatabaseTarget::airport_identifier_iata:
  500. statement.append(QStringLiteral("SELECT ROWID, iata FROM airports WHERE iata NOT NULL"));
  501. break;
  502. case ADatabaseTarget::airport_names:
  503. statement.append(QStringLiteral("SELECT ROWID, name FROM airports"));
  504. break;
  505. case ADatabaseTarget::tails:
  506. statement.append(QStringLiteral("SELECT ROWID, registration FROM tails"));
  507. break;
  508. default:
  509. DEB << "Not a valid completer target for this function.";
  510. return {};
  511. }
  512. auto query = QSqlQuery(statement);
  513. if (!query.isActive()) {
  514. DEB << "No result found. Check Query and Error.";
  515. DEB << "Query: " << statement;
  516. DEB << "Error: " << query.lastError().text();
  517. lastError = query.lastError();
  518. return {};
  519. }
  520. auto id_map = QMap<QString, RowId_T>();
  521. while (query.next()) {
  522. id_map.insert(query.value(1).toString(), query.value(0).toInt());
  523. }
  524. return id_map;
  525. }
  526. int ADatabase::getLastEntry(ADatabaseTable table)
  527. {
  528. QString statement = QLatin1String("SELECT MAX(ROWID) FROM ");
  529. switch (table) {
  530. case ADatabaseTable::pilots:
  531. statement.append(Opl::Db::TABLE_PILOTS);
  532. break;
  533. case ADatabaseTable::aircraft:
  534. statement.append(Opl::Db::TABLE_AIRCRAFT);
  535. break;
  536. case ADatabaseTable::tails:
  537. statement.append(Opl::Db::TABLE_TAILS);
  538. break;
  539. default:
  540. DEB << "Not a valid completer target for this function.";
  541. return 0;
  542. }
  543. auto query = QSqlQuery(statement);
  544. if (query.first()) {
  545. return query.value(0).toInt();
  546. } else {
  547. lastError = ADatabaseError(QStringLiteral("Database entry not found."));
  548. DEB << "No entry found.";
  549. return 0;
  550. }
  551. }
  552. QList<RowId_T> ADatabase::getForeignKeyConstraints(RowId_T foreign_row_id, ADatabaseTable target)
  553. {
  554. QString statement = QLatin1String("SELECT ROWID FROM flights WHERE ");
  555. switch (target) {
  556. case ADatabaseTable::pilots:
  557. statement.append(QLatin1String("pic=?"));
  558. break;
  559. case ADatabaseTable::tails:
  560. statement.append(QLatin1String("acft=?"));
  561. break;
  562. default:
  563. DEB << "Not a valid target for this function.";
  564. return QList<int>();
  565. break;
  566. }
  567. QSqlQuery query;
  568. query.prepare(statement);
  569. query.addBindValue(foreign_row_id);
  570. query.exec();
  571. if (!query.isActive()) {
  572. lastError = query.lastError();
  573. DEB << "Error";
  574. DEB << statement;
  575. DEB << query.lastError().text();
  576. return QList<int>();
  577. }
  578. QList<int> row_ids;
  579. while (query.next()) {
  580. row_ids.append(query.value(0).toInt());
  581. }
  582. return row_ids;
  583. }
  584. APilotEntry ADatabase::resolveForeignPilot(RowId_T foreign_key)
  585. {
  586. return aDB->getPilotEntry(foreign_key);
  587. }
  588. ATailEntry ADatabase::resolveForeignTail(RowId_T foreign_key)
  589. {
  590. return aDB->getTailEntry(foreign_key);
  591. }
  592. QVector<QVariant> ADatabase::customQuery(QString statement, int return_values)
  593. {
  594. QSqlQuery query(statement);
  595. if(!query.exec()) {
  596. lastError = query.lastError();
  597. DEB << "Query Error: " << lastError.text();
  598. return {};
  599. }
  600. if (!query.first()) {
  601. DEB << "No result found. Check Query and Error.";
  602. DEB << "Error: " << query.lastError().text();
  603. DEB << "Statement: " << statement;
  604. lastError = ADatabaseError("No result found.");
  605. return QVector<QVariant>();
  606. } else {
  607. query.first();
  608. query.previous();
  609. QVector<QVariant> result;
  610. while (query.next()) {
  611. for (int i = 0; i < return_values ; i++) {
  612. result.append(query.value(i));
  613. }
  614. }
  615. lastError = QString();
  616. return result;
  617. }
  618. }
  619. QMap<ADatabaseSummaryKey, QString> ADatabase::databaseSummary(const QString &db_path)
  620. {
  621. const QString connection_name = QStringLiteral("summary_connection");
  622. QMap<ADatabaseSummaryKey, QString> return_values;
  623. { // scope for a temporary database connection, ensures proper cleanup when removeDatabase() is called.
  624. //DEB << "Adding temporary connection to database:" << db_path;
  625. QSqlDatabase temp_database = QSqlDatabase::addDatabase(SQLITE_DRIVER, connection_name); // Don't use default connection
  626. temp_database.setDatabaseName(db_path);
  627. if (!temp_database.open())
  628. return {};
  629. QSqlQuery query(temp_database); // Query object using the temporary connection
  630. ADatabaseSummaryKey key; // Used among the queries for verbosity... and sanity
  631. const QVector<QPair<ADatabaseSummaryKey, QString>> key_table_pairs = {
  632. {ADatabaseSummaryKey::total_flights, QStringLiteral("flights")},
  633. {ADatabaseSummaryKey::total_tails, QStringLiteral("tails")},
  634. {ADatabaseSummaryKey::total_pilots, QStringLiteral("pilots")}
  635. };
  636. // retreive amount of flights, tails and pilots
  637. for (const auto & pair : key_table_pairs) {
  638. query.prepare(QLatin1String("SELECT COUNT (*) FROM ") + pair.second);
  639. query.exec();
  640. key = pair.first;
  641. if (query.first()){
  642. return_values[key] = query.value(0).toString();
  643. }
  644. else{
  645. return_values[key] = QString();
  646. }
  647. }
  648. // retreive date of last flight
  649. query.prepare(QStringLiteral("SELECT MAX(doft) FROM flights"));
  650. query.exec();
  651. key = ADatabaseSummaryKey::last_flight;
  652. if (query.first()){
  653. return_values[key] = query.value(0).toString();
  654. }
  655. else {
  656. return_values[key] = QString();
  657. }
  658. // retreive total flight time as a string "hh:mm"
  659. query.prepare(QStringLiteral("SELECT "
  660. "printf(\"%02d\",CAST(SUM(tblk) AS INT)/60)"
  661. "||':'||"
  662. "printf(\"%02d\",CAST(SUM(tblk) AS INT)%60) FROM flights"));
  663. key = ADatabaseSummaryKey::total_time;
  664. query.exec();
  665. if (query.first()){
  666. return_values[key] = query.value(0).toString();
  667. }
  668. else {
  669. return_values[key] = QString();
  670. }
  671. }
  672. QSqlDatabase::removeDatabase(connection_name); // cleanly removes temp connection without leaks since query+db are out of scope
  673. return return_values;
  674. }
  675. const QString ADatabase::databaseSummaryString(const QString &db_path)
  676. {
  677. auto summary_map = databaseSummary(db_path);
  678. QString out = QLatin1String("<table>");
  679. out.append(tr("<tr><td>Total Time: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::total_time]));
  680. out.append(tr("<tr><td>Last Flight: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::last_flight]));
  681. out.append(tr("<tr><td>Number of flights: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::total_flights]));
  682. out.append(tr("<tr><td>Number of aircraft: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::total_tails]));
  683. out.append(tr("<tr><td>Number of Pilots: </td><td>%1</td>").arg(summary_map[ADatabaseSummaryKey::total_pilots]));
  684. out.append("</table>");
  685. return out;
  686. }
  687. /*!
  688. * \brief ADatabase::createBackup copies the currently used database to an external backup location provided by the user
  689. * \param dest_file This is the full path and filename of where the backup will be created, e.g. 'home/Sully/myBackups/backupFromOpl.db'
  690. */
  691. bool ADatabase::createBackup(const QString& dest_file)
  692. {
  693. LOG << "Backing up current database to: " << dest_file;
  694. ADatabase::disconnect();
  695. QFile db_file(databaseFile.absoluteFilePath());
  696. DEB << "File to Overwrite:" << db_file;
  697. if (!db_file.copy(dest_file)) {
  698. LOG << "Unable to backup old database:" << db_file.errorString();
  699. return false;
  700. }
  701. LOG << "Backed up old database as:" << dest_file;
  702. ADatabase::connect();
  703. emit connectionReset();
  704. return true;
  705. }
  706. QVector<RowData_T> ADatabase::getTable(ADatabaseTable table_name)
  707. {
  708. auto query_str = QStringLiteral("SELECT * FROM ");
  709. switch (table_name) {
  710. case ADatabaseTable::pilots:
  711. query_str.append(Opl::Db::TABLE_PILOTS);
  712. break;
  713. case ADatabaseTable::tails:
  714. query_str.append(Opl::Db::TABLE_TAILS);
  715. break;
  716. case ADatabaseTable::flights:
  717. query_str.append(Opl::Db::TABLE_FLIGHTS);
  718. break;
  719. case ADatabaseTable::currencies:
  720. query_str.append(Opl::Db::TABLE_CURRENCIES);
  721. default:
  722. break;
  723. }
  724. QSqlQuery q;
  725. q.prepare(query_str);
  726. q.setForwardOnly(true);
  727. if (!q.exec()) {
  728. LOG << "SQL error: " << q.lastError().text();
  729. LOG << "Statement: " << query_str;
  730. lastError = q.lastError();
  731. return {};
  732. }
  733. QVector<RowData_T> entry_data;
  734. while(q.next()) { // iterate through records
  735. auto r = q.record();
  736. //DEB << r;
  737. RowData_T row;
  738. for (int i = 0; i < r.count(); i++){
  739. if(!r.value(i).isNull()) {
  740. row.insert(r.fieldName(i), r.value(i));
  741. }
  742. }
  743. entry_data.append(row);
  744. }
  745. return entry_data;
  746. }
  747. /*!
  748. * \brief ADatabase::restoreBackup restores the database from a given backup file and replaces the currently active database.
  749. * \param backup_file This is the full path and filename of the backup, e.g. 'home/Sully/myBackups/backupFromOpl.db'
  750. */
  751. bool ADatabase::restoreBackup(const QString& backup_file)
  752. {
  753. LOG << "Restoring backup from file:" << backup_file;
  754. QString default_loc = databaseFile.absoluteFilePath();
  755. ADatabase::disconnect();
  756. QFile backup(backup_file);
  757. QFile current_db(default_loc);
  758. if (!current_db.rename(default_loc + QLatin1String(".tmp"))) { // move previously used db out of the way
  759. LOG << current_db.errorString() << "Unable to remove current db file";
  760. return false;
  761. }
  762. if (!backup.copy(default_loc))
  763. {
  764. LOG << backup.errorString() << "Could not copy" << backup << "to" << databaseFile;
  765. // try to restore previously used db
  766. current_db.rename(default_loc);
  767. return false;
  768. }
  769. // backup has been restored, clean up the previously moved file
  770. current_db.remove();
  771. LOG << "Backup successfully restored!";
  772. ADatabase::connect();
  773. emit connectionReset();
  774. return true;
  775. }