adatabasesetup.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. *openPilot Log - A FOSS Pilot Logbook Application
  3. *Copyright (C) 2020 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 "adatabasesetup.h"
  19. #include "src/database/adatabase.h"
  20. #include "src/testing/adebug.h"
  21. #include "src/functions/areadcsv.h"
  22. // Statements for creation of database tables, Revision 15
  23. const QString createTablePilots = "CREATE TABLE pilots ( "
  24. " pilot_id INTEGER NOT NULL, "
  25. " lastname TEXT NOT NULL, "
  26. " firstname TEXT, "
  27. " alias TEXT, "
  28. " company TEXT, "
  29. " employeeid TEXT, "
  30. " phone TEXT, "
  31. " email TEXT, "
  32. " PRIMARY KEY(pilot_id AUTOINCREMENT)"
  33. ")";
  34. const QString createTableTails = "CREATE TABLE tails ("
  35. " tail_id INTEGER NOT NULL,"
  36. " registration TEXT NOT NULL,"
  37. " company TEXT,"
  38. " make TEXT,"
  39. " model TEXT,"
  40. " variant TEXT,"
  41. " multipilot INTEGER,"
  42. " multiengine INTEGER,"
  43. " engineType INTEGER,"
  44. " weightClass INTEGER,"
  45. " PRIMARY KEY(tail_id AUTOINCREMENT)"
  46. ")";
  47. const QString createTableFlights = "CREATE TABLE flights ("
  48. " flight_id INTEGER NOT NULL, "
  49. " doft NUMERIC NOT NULL, "
  50. " dept TEXT NOT NULL, "
  51. " dest TEXT NOT NULL, "
  52. " tofb INTEGER NOT NULL, "
  53. " tonb INTEGER NOT NULL, "
  54. " pic INTEGER NOT NULL, "
  55. " acft INTEGER NOT NULL, "
  56. " tblk INTEGER NOT NULL, "
  57. " tSPSE INTEGER, "
  58. " tSPME INTEGER, "
  59. " tMP INTEGER, "
  60. " tNIGHT INTEGER, "
  61. " tIFR INTEGER, "
  62. " tPIC INTEGER, "
  63. " tPICUS INTEGER, "
  64. " tSIC INTEGER, "
  65. " tDUAL INTEGER, "
  66. " tFI INTEGER, "
  67. " tSIM INTEGER, "
  68. " pilotFlying INTEGER, "
  69. " toDay INTEGER, "
  70. " toNight INTEGER, "
  71. " ldgDay INTEGER, "
  72. " ldgNight INTEGER, "
  73. " autoland INTEGER, "
  74. " secondPilot INTEGER, "
  75. " thirdPilot INTEGER, "
  76. " approachType TEXT, "
  77. " flightNumber TEXT, "
  78. " remarks TEXT, "
  79. " FOREIGN KEY(pic) REFERENCES pilots(pilot_id) ON DELETE RESTRICT, "
  80. " FOREIGN KEY(acft) REFERENCES tails(tail_id) ON DELETE RESTRICT, "
  81. " PRIMARY KEY(flight_id AUTOINCREMENT) "
  82. ")";
  83. const QString createTableAirports = "CREATE TABLE airports ( "
  84. " airport_id INTEGER NOT NULL, "
  85. " icao TEXT NOT NULL, "
  86. " iata TEXT, "
  87. " name TEXT, "
  88. " lat REAL, "
  89. " long REAL, "
  90. " country TEXT, "
  91. " alt INTEGER, "
  92. " utcoffset INTEGER, "
  93. " tzolson TEXT, "
  94. " PRIMARY KEY(airport_id AUTOINCREMENT) "
  95. ")";
  96. const QString createTableAircraft = "CREATE TABLE aircraft ("
  97. " aircraft_id INTEGER NOT NULL,"
  98. " make TEXT,"
  99. " model TEXT,"
  100. " variant TEXT,"
  101. " name TEXT,"
  102. " iata TEXT,"
  103. " icao TEXT,"
  104. " multipilot INTEGER,"
  105. " multiengine INTEGER,"
  106. " engineType INTEGER,"
  107. " weightClass INTEGER,"
  108. " PRIMARY KEY(aircraft_id AUTOINCREMENT)"
  109. ")";
  110. const QString createTableChangelog = "CREATE TABLE changelog ( "
  111. " revision INTEGER NOT NULL, "
  112. " comment TEXT, "
  113. " date NUMERIC, "
  114. " PRIMARY KEY(revision) "
  115. ")";
  116. // Statements for creation of views in the database
  117. const QString createViewDefault = "CREATE VIEW viewDefault AS "
  118. " SELECT flight_id, doft as 'Date', "
  119. " dept AS 'Dept', "
  120. " printf('%02d',(tofb/60))||':'||printf('%02d',(tofb%60)) AS 'Time', "
  121. " dest AS 'Dest', printf('%02d',(tonb/60))||':'||printf('%02d',(tonb%60)) AS 'Time ', "
  122. " printf('%02d',(tblk/60))||':'||printf('%02d',(tblk%60)) AS 'Total', "
  123. " CASE "
  124. " WHEN pilot_id = 1 THEN alias "
  125. " ELSE lastname||', '||substr(firstname, 1, 1)||'.' "
  126. " END "
  127. " AS 'Name PIC', "
  128. " make||' '||model||'-'||variant AS 'Type', "
  129. " registration AS 'Registration', "
  130. " FlightNumber AS 'Flight #', "
  131. " remarks AS 'Remarks'"
  132. " FROM flights "
  133. " INNER JOIN pilots on flights.pic = pilots.pilot_id "
  134. " INNER JOIN tails on flights.acft = tails.tail_id "
  135. " ORDER BY date DESC ";
  136. const QString createViewEASA = "CREATE VIEW viewEASA AS "
  137. " SELECT "
  138. " flight_id, doft as 'Date', "
  139. " dept AS 'Dept', "
  140. " printf('%02d',(tofb/60))||':'||printf('%02d',(tofb%60)) AS 'Time', "
  141. " dest AS 'Dest', printf('%02d',(tonb/60))||':'||printf('%02d',(tonb%60)) AS 'Time ', "
  142. " make||' '||model||'-'||variant AS 'Type', "
  143. " registration AS 'Registration', "
  144. " (SELECT printf('%02d',(tSPSE/60))||':'||printf('%02d',(tSPSE%60)) WHERE tSPSE IS NOT NULL) AS 'SP SE', "
  145. " (SELECT printf('%02d',(tSPME/60))||':'||printf('%02d',(tSPME%60)) WHERE tSPME IS NOT NULL) AS 'SP ME', "
  146. " (SELECT printf('%02d',(tMP/60))||':'||printf('%02d',(tMP%60)) WHERE tMP IS NOT NULL) AS 'MP', "
  147. " printf('%02d',(tblk/60))||':'||printf('%02d',(tblk%60)) AS 'Total', "
  148. " CASE "
  149. " WHEN pilot_id = 1 THEN alias "
  150. " ELSE lastname||', '||substr(firstname, 1, 1)||'.' "
  151. " END "
  152. " AS 'Name PIC', "
  153. " ldgDay AS 'L/D', "
  154. " ldgNight AS 'L/N', "
  155. " (SELECT printf('%02d',(tNight/60))||':'||printf('%02d',(tNight%60)) WHERE tNight IS NOT NULL) AS 'Night', "
  156. " (SELECT printf('%02d',(tIFR/60))||':'||printf('%02d',(tIFR%60)) WHERE tIFR IS NOT NULL) AS 'IFR', "
  157. " (SELECT printf('%02d',(tPIC/60))||':'||printf('%02d',(tPIC%60)) WHERE tPIC IS NOT NULL) AS 'PIC', "
  158. " (SELECT printf('%02d',(tSIC/60))||':'||printf('%02d',(tSIC%60)) WHERE tSIC IS NOT NULL) AS 'SIC', "
  159. " (SELECT printf('%02d',(tDual/60))||':'||printf('%02d',(tDual%60)) WHERE tDual IS NOT NULL) AS 'Dual', "
  160. " (SELECT printf('%02d',(tFI/60))||':'||printf('%02d',(tFI%60)) WHERE tFI IS NOT NULL) AS 'FI', "
  161. " remarks AS 'Remarks' "
  162. " FROM flights "
  163. " INNER JOIN pilots on flights.pic = pilots.pilot_id "
  164. " INNER JOIN tails on flights.acft = tails.tail_id "
  165. " ORDER BY date DESC";
  166. const QString createViewTails = "CREATE VIEW viewTails AS "
  167. " SELECT "
  168. " tail_id AS 'ID', "
  169. " registration AS 'Registration', "
  170. " make||' '||model AS 'Type', "
  171. " company AS 'Company' "
  172. " FROM tails WHERE model IS NOT NULL AND variant IS NULL "
  173. " UNION "
  174. " SELECT "
  175. " tail_id AS 'ID', "
  176. " registration AS 'Registration', "
  177. " make||' '||model||'-'||variant AS 'Type', "
  178. " company AS 'Company' "
  179. " FROM tails WHERE variant IS NOT NULL";
  180. const QString createViewPilots = "CREATE VIEW viewPilots AS "
  181. " SELECT "
  182. " pilot_id AS 'ID', "
  183. " lastname AS 'Last Name', "
  184. " firstname AS 'First Name', "
  185. " company AS 'Company' "
  186. " FROM pilots";
  187. const QString createViewQCompleter = "CREATE VIEW viewQCompleter AS "
  188. " SELECT airport_id, icao, iata, tail_id, registration, pilot_id, "
  189. " lastname||', '||firstname AS 'pilot_name', alias "
  190. " FROM airports "
  191. " LEFT JOIN tails ON airports.airport_id = tails.tail_id "
  192. " LEFT JOIN pilots ON airports.airport_id = pilots.pilot_id";
  193. const QString createViewTotals = "CREATE VIEW viewTotals AS "
  194. " SELECT "
  195. " printf(\"%02d\",CAST(SUM(tblk) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tblk) AS INT)%60) AS \"TOTAL\", "
  196. " printf(\"%02d\",CAST(SUM(tSPSE) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tSPSE) AS INT)%60) AS \"SP SE\", "
  197. " printf(\"%02d\",CAST(SUM(tSPME) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tSPME) AS INT)%60) AS \"SP ME\", "
  198. " printf(\"%02d\",CAST(SUM(tNIGHT) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tNIGHT) AS INT)%60) AS \"NIGHT\", "
  199. " printf(\"%02d\",CAST(SUM(tIFR) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tIFR) AS INT)%60) AS \"IFR\", "
  200. " printf(\"%02d\",CAST(SUM(tPIC) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tPIC) AS INT)%60) AS \"PIC\", "
  201. " printf(\"%02d\",CAST(SUM(tPICUS) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tPICUS) AS INT)%60) AS \"PICUS\", "
  202. " printf(\"%02d\",CAST(SUM(tSIC) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tSIC) AS INT)%60) AS \"SIC\", "
  203. " printf(\"%02d\",CAST(SUM(tDual) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tDual) AS INT)%60) AS \"DUAL\", "
  204. " printf(\"%02d\",CAST(SUM(tFI) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tFI) AS INT)%60) AS \"INSTRUCTOR\", "
  205. " printf(\"%02d\",CAST(SUM(tSIM) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tSIM) AS INT)%60) AS \"SIMULATOR\", "
  206. " printf(\"%02d\",CAST(SUM(tMP) AS INT)/60)||\":\"||printf(\"%02d\",CAST(SUM(tMP) AS INT)%60) AS \"MultPilot\", "
  207. " CAST(SUM(toDay) AS INT) AS \"TO Day\", CAST(SUM(toNight) AS INT) AS \"TO Night\", "
  208. " CAST(SUM(ldgDay) AS INT) AS \"LDG Day\", CAST(SUM(ldgNight) AS INT) AS \"LDG Night\" "
  209. " FROM flights";
  210. /*const QString createViewPilotsTailsMap = "CREATE VIEW viewPilotsTailsMap AS "
  211. "SELECT "
  212. "pilot_id, lastname, firstname, tail_id, registration "
  213. "FROM pilots "
  214. "LEFT JOIN tails "
  215. "ON pilots.pilot_id = tails.tail_id";*/
  216. const QStringList tables = {
  217. createTablePilots,
  218. createTableTails,
  219. createTableFlights,
  220. createTableAircraft,
  221. createTableAirports,
  222. createTableChangelog
  223. };
  224. const QStringList views = {
  225. createViewDefault,
  226. createViewEASA,
  227. createViewTails,
  228. createViewPilots,
  229. createViewTotals,
  230. createViewQCompleter,
  231. };
  232. const QStringList userTables = {
  233. "flights",
  234. "pilots",
  235. "tails"
  236. };
  237. const QStringList templateTables= {
  238. "aircraft",
  239. "airports",
  240. "changelog"
  241. };
  242. bool ADataBaseSetup::createDatabase()
  243. {
  244. DEB("Creating tables...");
  245. if (!createSchemata(tables)) {
  246. DEB("Creating tables has failed.");
  247. return false;
  248. }
  249. DEB("Creating views...");
  250. if (!createSchemata(views)) {
  251. DEB("Creating views failed.");
  252. return false;
  253. }
  254. // call connect again to (re-)populate tableNames and columnNames
  255. aDB()->connect();
  256. DEB("Populating tables...");
  257. if (!importDefaultData()) {
  258. DEB("Populating tables failed.");
  259. return false;
  260. }
  261. DEB("Database successfully created!");
  262. return true;
  263. }
  264. bool ADataBaseSetup::importDefaultData()
  265. {
  266. QSqlQuery query;
  267. // reset template tables
  268. for (const auto& table : templateTables) {
  269. //clear tables
  270. query.prepare("DELETE FROM " + table);
  271. if (!query.exec()) {
  272. DEB("Error: " << query.lastError().text());
  273. }
  274. //fill with data from csv
  275. if (!commitData(aReadCsv("data/templates/" + table + ".csv"), table)) {
  276. DEB("Error importing data.");
  277. return false;
  278. }
  279. }
  280. return true;
  281. };
  282. /*!
  283. * \brief DbSetup::resetToDefault Empties all user-generated content in the database.
  284. * \return true on success
  285. */
  286. bool ADataBaseSetup::resetToDefault()
  287. {
  288. QSqlQuery query;
  289. // clear user tables
  290. for (const auto& table : userTables) {
  291. query.prepare("DELETE FROM " + table);
  292. if (!query.exec()) {
  293. DEB("Error: " << query.lastError().text());
  294. }
  295. }
  296. return true;
  297. }
  298. /*!
  299. * \brief dbSetup::debug prints Database Layout
  300. */
  301. void ADataBaseSetup::debug()
  302. {
  303. DEB("Database tables and views: ");
  304. QSqlQuery query;
  305. const QVector<QString> types = { "table", "view" };
  306. for (const auto& var : types){
  307. query.prepare("SELECT name FROM sqlite_master WHERE type=" + var);
  308. query.exec();
  309. while (query.next()) {
  310. QString table = query.value(0).toString();
  311. QSqlQuery entries("SELECT COUNT(*) FROM " + table);
  312. entries.next();
  313. DEB("Element " << query.value(0).toString()) << "with"
  314. << entries.value(0).toString() << "rows";
  315. }
  316. }
  317. }
  318. /*!
  319. * \brief dbSetup::createTables Create the required tables for the database
  320. * \return true on success
  321. */
  322. bool ADataBaseSetup::createSchemata(const QStringList &statements)
  323. {
  324. QSqlQuery query;
  325. QStringList errors;
  326. for (const auto& statement : statements) {
  327. query.prepare(statement);
  328. query.exec();
  329. if(!query.isActive()) {
  330. errors << statement.section(QLatin1Char(' '),2,2) + " ERROR - " + query.lastError().text();
  331. DEB("Query: " << query.lastQuery());
  332. } else {
  333. DEB("Schema added: " << statement.section(QLatin1Char(' '),2,2));
  334. }
  335. }
  336. if (!errors.isEmpty()) {
  337. DEB("The following errors have ocurred: ");
  338. for (const auto& error : errors) {
  339. DEB(error);
  340. }
  341. return false;
  342. } else {
  343. DEB("All schemas added successfully");
  344. return true;
  345. }
  346. }
  347. /*!
  348. * \brief DbSetup::commitData inserts the data parsed from a csv file into the
  349. * database. The first line of the csv file has to contain the column names
  350. * of the corresponding table in the database.
  351. * \param fromCSV input as parsed from CSV::read()
  352. * \param tableName as in the database
  353. * \return
  354. */
  355. bool ADataBaseSetup::commitData(QVector<QStringList> fromCSV, const QString &tableName)
  356. {
  357. DEB("Table names: " << aDB()->getTableNames());
  358. DEB("Importing Data to" << tableName);
  359. if (!aDB()->getTableNames().contains(tableName)){
  360. DEB(tableName << "is not a table in the database. Aborting.");
  361. DEB("Please check input data.");
  362. return false;
  363. }
  364. // create insert statement
  365. QString statement = "INSERT INTO " + tableName + " (";
  366. QString placeholder = ") VALUES (";
  367. for (auto& csvColumn : fromCSV) {
  368. if(aDB()->getTableColumns().value(tableName).contains(csvColumn.first())) {
  369. statement += csvColumn.first() + ',';
  370. csvColumn.removeFirst();
  371. placeholder.append("?,");
  372. } else {
  373. DEB(csvColumn.first() << "is not a column of " << tableName << "Aborting.");
  374. DEB("Please check input data.");
  375. return false;
  376. }
  377. }
  378. statement.chop(1);
  379. placeholder.chop(1);
  380. placeholder.append(')');
  381. statement.append(placeholder);
  382. /*
  383. * Using exclusive transaction and the loop below is MUCH faster than
  384. * passing the QStringLists to QSqlQuery::addBindValue and using QSqlQuery::execBatch()
  385. */
  386. QSqlQuery query;
  387. query.exec("BEGIN EXCLUSIVE TRANSACTION;");
  388. for (int i = 0; i < fromCSV.first().length(); i++){
  389. query.prepare(statement);
  390. for(int j = 0; j < fromCSV.length(); j++) {
  391. fromCSV[j][i] == QString("") ? // make sure NULL is committed for empty values
  392. query.addBindValue(QVariant(QVariant::String))
  393. : query.addBindValue(fromCSV[j][i]);
  394. //query.addBindValue(fromCSV[j][i]);
  395. }
  396. query.exec();
  397. }
  398. query.exec("COMMIT;"); //commit transaction
  399. if (query.lastError().text().length() > 3) {
  400. DEB("Error:" << query.lastError().text());
  401. return false;
  402. } else {
  403. qDebug() << tableName << "Database successfully updated!";
  404. return true;
  405. }
  406. }