adatabasesetup.cpp 17 KB

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