db.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  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 "db.h"
  19. #include "dbinfo.h"
  20. // Debug Makro
  21. #define DEB(expr) \
  22. qDebug() << "db ::" << __func__ << "\t" << expr
  23. db::db()
  24. {
  25. }
  26. db::db(sql::tableName tn, int row_ID)
  27. {
  28. switch (tn) {
  29. case sql::flights:
  30. table = "flights";
  31. break;
  32. case sql::pilots:
  33. table = "pilots";
  34. break;
  35. case sql::aircraft:
  36. table = "aircraft";
  37. break;
  38. case sql::tails:
  39. table = "tails";
  40. break;
  41. case sql::airports:
  42. table = "airports";
  43. break;
  44. }
  45. row_id = row_ID;
  46. QString statement = "SELECT COUNT(*) FROM " + table + " WHERE _rowid_="+QString::number(row_id);
  47. QSqlQuery q(statement);
  48. q.exec();
  49. q.next();
  50. int rows = q.value(0).toInt();
  51. if(rows==0){
  52. DEB("No entry found for row id: " << row_ID );
  53. }else{
  54. DEB("Retreiving data for row id: " << row_id);
  55. isValid = retreiveData();
  56. }
  57. }
  58. db::db(sql::tableName tn, QMap<QString, QString> newData)
  59. {
  60. switch (tn) {
  61. case sql::flights:
  62. table = "flights";
  63. break;
  64. case sql::pilots:
  65. table = "pilots";
  66. break;
  67. case sql::aircraft:
  68. table = "aircraft";
  69. break;
  70. case sql::tails:
  71. table = "tails";
  72. break;
  73. case sql::airports:
  74. table = "airports";
  75. break;
  76. }
  77. //Do some checks
  78. auto in = dbInfo();
  79. auto columns = in.format.value(table);
  80. QMap<QString,QString>::iterator i;
  81. for (i = newData.begin(); i != newData.end(); ++i){
  82. if(!columns.contains(i.key())){
  83. DEB(newData);
  84. DEB(i.key()<< i.value() << "Not in column list for " << table <<". Removing.");
  85. newData.remove(i.key());
  86. }
  87. }
  88. data = newData;
  89. }
  90. void db::setData(const QMap<QString, QString> &value)
  91. {
  92. data = value;
  93. }
  94. /*!
  95. * \brief db::connect connects to the database via the default connection.
  96. * Can then be accessed globally with QSqlDatabase::database("qt_sql_default_connection")
  97. */
  98. void db::connect()
  99. {
  100. const QString driver("QSQLITE");
  101. if(QSqlDatabase::isDriverAvailable(driver)){
  102. QDir directory("data");
  103. QString databaseLocation = directory.filePath("logbook.db");
  104. QSqlDatabase db = QSqlDatabase::addDatabase(driver);
  105. db.setDatabaseName(databaseLocation);
  106. if(!db.open()){
  107. DEB("DatabaseConnect - ERROR: " << db.lastError().text());
  108. }else{
  109. DEB("Database connection established.");
  110. }
  111. }else{
  112. DEB("DatabaseConnect - ERROR: no driver " << driver << " available");
  113. }
  114. }
  115. /*!
  116. * \brief db::retreiveData retreives data from the database.
  117. * \return
  118. */
  119. bool db::retreiveData()
  120. {
  121. const auto info = dbInfo();
  122. QString statement = "SELECT * FROM " + table + " WHERE _rowid_="+QString::number(row_id);
  123. DEB("Executing SQL...");
  124. DEB(statement);
  125. QSqlQuery q(statement);
  126. q.exec();
  127. q.next();
  128. for(int i=0; i < info.format.value(table).length(); i++){
  129. data.insert(info.format.value(table)[i],q.value(i).toString());
  130. }
  131. QString error = q.lastError().text();
  132. if(error.length() > 2){
  133. DEB("Error: " << q.lastError().text());
  134. return false;
  135. }else{return true;}
  136. }
  137. /*!
  138. * \brief db::exists checks if a certain value exists in the database with a sqlite WHERE statement
  139. * \param table - Name of the table
  140. * \param column - Name of the column
  141. * \param value - The value to be checked
  142. * \return
  143. */
  144. bool db::exists(QString column, QString table, QString checkColumn, QString value, sql::matchType match)
  145. {
  146. bool output = false;
  147. QString statement = "SELECT " + column + " FROM " + table + " WHERE " + checkColumn;
  148. switch (match) {
  149. case sql::exactMatch:
  150. statement += " = '" + value + QLatin1Char('\'');
  151. break;
  152. case sql::partialMatch:
  153. value.append(QLatin1Char('%'));
  154. value.prepend(QLatin1Char('%'));
  155. statement.append(" LIKE '" + value + QLatin1Char('\''));
  156. break;
  157. }
  158. DEB(statement);
  159. QSqlQuery q(statement);
  160. q.exec();
  161. if(!q.first()){
  162. DEB("No result found. Check Query and Error.");
  163. DEB("Error: " << q.lastError().text());
  164. }else{
  165. DEB("Success. Found a result.");
  166. output = true;
  167. if(q.next()){
  168. DEB("More than one result in Database for your query");
  169. }
  170. }
  171. // Debug:
  172. q.first();
  173. q.previous();
  174. while(q.next()){
  175. DEB("Query result: " << q.value(0).toString());
  176. }
  177. // end of Debug
  178. return output;
  179. }
  180. /*!
  181. * \brief singleSelect Returns a single value from the database with a sqlite WHERE statement
  182. * \param table - Name of the table
  183. * \param column - Name of the column
  184. * \param value - Identifier for WHERE statement
  185. * \param match - enum sql::exactMatch or sql::partialMatch
  186. * \return QString
  187. */
  188. QString db::singleSelect(QString column, QString table, QString checkColumn, QString value, sql::matchType match)
  189. {
  190. QString statement = "SELECT " + column + " FROM " + table + " WHERE " + checkColumn;
  191. QString result;
  192. switch (match) {
  193. case sql::exactMatch:
  194. statement += " = '" + value + QLatin1Char('\'');
  195. break;
  196. case sql::partialMatch:
  197. value.append(QLatin1Char('%'));
  198. value.prepend(QLatin1Char('%'));
  199. statement.append(" LIKE '" + value + QLatin1Char('\''));
  200. break;
  201. }
  202. DEB(statement);
  203. QSqlQuery q(statement);
  204. q.exec();
  205. if(!q.first()){
  206. DEB("No result found. Check Query and Error.");
  207. DEB("Error: " << q.lastError().text());
  208. return QString();
  209. }else{
  210. DEB("Success. Found a result.");
  211. result.append(q.value(0).toString());
  212. if(q.next()){
  213. DEB("More than one result in Database for your query");
  214. }
  215. return result;
  216. }
  217. }
  218. /*!
  219. * \brief db::multiSelect Returns multiple values from the database with a sqlite WHERE statement
  220. * \param table - Name of the table
  221. * \param columns - QVector<QString> Names of the columns to be queried
  222. * \param value - Identifier for WHERE statement
  223. * \param checkColumn - column to match value to
  224. * \param match - enum sql::exactMatch or sql::partialMatch
  225. * \return QVector<QString>
  226. */
  227. QVector<QString> db::multiSelect(QVector<QString> columns, QString table, QString checkColumn, QString value, sql::matchType match)
  228. {
  229. QString statement = "SELECT ";
  230. for(const auto& column : columns)
  231. {
  232. statement.append(column);
  233. if(column != columns.last())
  234. {
  235. statement.append(QLatin1String(", "));
  236. }
  237. }
  238. statement.append(" FROM " + table + " WHERE " + checkColumn);
  239. switch (match) {
  240. case sql::exactMatch:
  241. statement += " = '" + value + QLatin1Char('\'');
  242. break;
  243. case sql::partialMatch:
  244. value.append(QLatin1Char('%'));
  245. value.prepend(QLatin1Char('%'));
  246. statement.append(" LIKE '" + value + QLatin1Char('\''));
  247. break;
  248. }
  249. DEB(statement);
  250. QSqlQuery q(statement);
  251. q.exec();
  252. if(!q.first()){
  253. DEB("No result found. Check Query and Error.");
  254. DEB("Error: " << q.lastError().text());
  255. return QVector<QString>();
  256. }else{
  257. q.first();
  258. q.previous();
  259. QVector<QString> result;
  260. while (q.next()) {
  261. for(int i = 0; i < columns.size() ; i++)
  262. {
  263. result.append(q.value(i).toString());
  264. }
  265. }
  266. return result;
  267. }
  268. }
  269. /*!
  270. * \brief db::multiSelect Returns a complete column(s) for a given table. Useful for creating
  271. * lists for QCompleter
  272. * \param column
  273. * \param table
  274. * \return
  275. */
  276. QVector<QString> db::multiSelect(QVector<QString> columns, QString table)
  277. {
  278. QString statement = "SELECT ";
  279. for(const auto& column : columns)
  280. {
  281. statement.append(column);
  282. if(column != columns.last())
  283. {
  284. statement.append(QLatin1String(", "));
  285. }
  286. }
  287. statement.append(" FROM " + table);
  288. DEB(statement);
  289. QSqlQuery q(statement);
  290. q.exec();
  291. if(!q.first()){
  292. DEB("No result found. Check Query and Error.");
  293. DEB("Error: " << q.lastError().text());
  294. return QVector<QString>();
  295. }else{
  296. q.first();
  297. q.previous();
  298. QVector<QString> result;
  299. while (q.next()) {
  300. for(int i = 0; i < columns.size() ; i++)
  301. {
  302. result.append(q.value(i).toString());
  303. }
  304. }
  305. return result;
  306. }
  307. }
  308. /*!
  309. * \brief db::singleUpdate Updates a single value in the database.
  310. * Query format: UPDATE table SET column = value WHERE checkcolumn =/LIKE checkvalue
  311. * \param table Name of the table to be updated
  312. * \param column Name of the column to be updated
  313. * \param checkColumn Name of the column for WHERE statement
  314. * \param value The value to be set
  315. * \param checkvalue The value for the WHERE statement
  316. * \param match enum sql::exactMatch or sql::partialMatch
  317. * \return true on success, otherwise error messages in debug out
  318. */
  319. bool db::singleUpdate(QString table, QString column, QString value, QString checkColumn, QString checkvalue, sql::matchType match)
  320. {
  321. QString statement = "UPDATE " + table;
  322. statement.append(QLatin1String(" SET ") + column + QLatin1String(" = '") + value);
  323. statement.append(QLatin1String("' WHERE "));
  324. switch (match) {
  325. case sql::exactMatch:
  326. statement.append(checkColumn + " = '" + checkvalue + QLatin1Char('\''));
  327. break;
  328. case sql::partialMatch:
  329. value.append(QLatin1Char('%'));
  330. value.prepend(QLatin1Char('%'));
  331. statement.append(checkColumn + " LIKE '" + checkvalue + QLatin1Char('\''));
  332. break;
  333. }
  334. DEB(statement);
  335. QSqlQuery q(statement);
  336. q.exec();
  337. QString error = q.lastError().text();
  338. if(error.length() > 1)
  339. {
  340. DEB("Errors have occured: " << error);
  341. return false;
  342. }else
  343. {
  344. DEB("Success!");
  345. return true;
  346. }
  347. }
  348. /*!
  349. * \brief db::deleteRow Deletes a single row from the database.
  350. * Query format: DELETE FROM table WHERE column =/LIKE value
  351. * \param table - Name of the table
  352. * \param column - Name of the column
  353. * \param value - Identifier for WHERE statement
  354. * \param match - enum sql::exactMatch or sql::partialMatch
  355. * \return true on success, otherwise error messages in debug out
  356. */
  357. bool db::deleteRow(QString table, QString column, QString value, sql::matchType match)
  358. {
  359. QString statement = "DELETE FROM " + table + " WHERE ";
  360. statement.append(column);
  361. switch (match) {
  362. case sql::exactMatch:
  363. statement += " = '" + value + QLatin1Char('\'');
  364. break;
  365. case sql::partialMatch:
  366. value.append(QLatin1Char('%'));
  367. value.prepend(QLatin1Char('%'));
  368. statement.append(" LIKE '" + value + QLatin1Char('\''));
  369. break;
  370. }
  371. DEB(statement);
  372. QSqlQuery q(statement);
  373. q.exec();
  374. QString error = q.lastError().text();
  375. if(error.length() > 1)
  376. {
  377. DEB("Errors have occured: " << error);
  378. return false;
  379. }else
  380. {
  381. DEB("Success!");
  382. return true;
  383. }
  384. }
  385. /*!
  386. * \brief db::customQuery Can be used to send a complex query to the database.
  387. * \param query - the full sql query statement
  388. * \param returnValues - the number of expected return values
  389. * \return QVector<QString> of results
  390. */
  391. QVector<QString> db::customQuery(QString query, int returnValues)
  392. {
  393. QSqlQuery q(query);
  394. DEB(query);
  395. q.exec();
  396. if(!q.first()){
  397. DEB("No result found. Check Query and Error.");
  398. DEB("Error: " << q.lastError().text());
  399. return QVector<QString>();
  400. }else{
  401. q.first();
  402. q.previous();
  403. QVector<QString> result;
  404. while (q.next()) {
  405. for(int i = 0; i < returnValues ; i++)
  406. {
  407. result.append(q.value(i).toString());
  408. }
  409. }
  410. return result;
  411. }
  412. }
  413. /*!
  414. * \brief db::getColumnNames Looks up column names of a given table
  415. * \param table name of the table in the database
  416. QVector<QString> db::getColumnNames(QString table)
  417. {
  418. QSqlDatabase db = QSqlDatabase::database("qt_sql_default_connection");
  419. QVector<QString> columnNames;
  420. QSqlRecord fields = db.record(table);
  421. for(int i = 0; i < fields.count(); i++){
  422. columnNames << fields.field(i).name();
  423. }
  424. return columnNames;
  425. }*/
  426. /*!
  427. * \brief db::update updates the database with the values contained in the object.
  428. * \return True on Success
  429. */
  430. bool db::update()
  431. {
  432. //check prerequisites
  433. if(row_id == 0){
  434. DEB("Invalid Row ID: " << row_id);
  435. return false;
  436. }
  437. if(data.isEmpty()){
  438. DEB("Object Contains no data. Aborting.");
  439. return false;
  440. }
  441. //create query
  442. QString statement = "UPDATE " + table + " SET ";
  443. QMap<QString,QString>::const_iterator i;
  444. for (i = data.constBegin(); i != data.constEnd(); ++i){
  445. if(i.value()!=QString()){
  446. statement += i.key()+QLatin1String("='")+i.value()+QLatin1String("', ");
  447. }else{DEB(i.key() << "is empty. skipping.");}
  448. }
  449. statement.chop(2); // Remove last comma
  450. statement.append(QLatin1String(" WHERE _rowid_=")+QString::number(row_id));
  451. //execute query
  452. QSqlQuery q(statement);
  453. q.exec();
  454. //check result. Upon success, error should be " "
  455. QString error = q.lastError().text();
  456. if(error.length() < 2){
  457. return true;
  458. }else{
  459. DEB("Query Error: " << q.lastError().text());
  460. return false;
  461. }
  462. }
  463. bool db::commit()
  464. {
  465. //check prerequisites
  466. if(row_id != 0){
  467. DEB("Row ID already set. Unable to commit as new, try update() for existing entries: " << row_id);
  468. return false;
  469. }
  470. if(data.isEmpty()){
  471. DEB("Object Contains no data. Aborting.");
  472. return false;
  473. }
  474. QString statement = "INSERT INTO " + table + QLatin1String(" (");
  475. QMap<QString,QString>::iterator i;
  476. for (i = data.begin(); i != data.end(); ++i){
  477. statement += i.key() + QLatin1String(", ");
  478. }
  479. statement.chop(2);
  480. statement += QLatin1String(") VALUES (");
  481. for (i = data.begin(); i != data.end(); ++i){
  482. statement += QLatin1String("'") + i.value() + QLatin1String("', ");
  483. }
  484. statement.chop(2);
  485. statement += QLatin1String(")");
  486. QSqlQuery q(statement);
  487. QString error = q.lastError().text();
  488. if(error.length() < 2){
  489. DEB("Entry successfully committed.");
  490. return true;
  491. }else{
  492. DEB("Unable to commit. Query Error: " << q.lastError().text());
  493. return false;
  494. }
  495. }
  496. //Debug
  497. void db::print()
  498. {
  499. QString v = "Object status:\t\033[38;2;0;255;0;48;2;0;0;0m VALID \033[0m\n";
  500. QString nv = "Object status:\t\033[38;2;255;0;0;48;2;0;0;0m INVALID \033[0m\n";
  501. QTextStream cout(stdout, QIODevice::WriteOnly);
  502. cout << "=========Database Object=========\n";
  503. if(isValid){cout << v;}else{cout << nv;}
  504. cout << "Record from table: " << table << ", row: " << row_id << "\n";
  505. cout << "=================================\n";
  506. QMap<QString,QString>::const_iterator i;
  507. for (i = data.constBegin(); i != data.constEnd(); ++i){
  508. cout << i.key() << ": " << i.value() << "\n";
  509. }
  510. }
  511. QString db::debug()
  512. {
  513. print();
  514. return QString();
  515. }