adatabase.cpp 27 KB

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