adatabasesetup.cpp 16 KB

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