Db.h 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. #ifndef __DB_H__
  2. #define __DB_H__
  3. #include <QPair>
  4. #include <QMap>
  5. #include <QString>
  6. #include <QSqlQuery>
  7. #include <QSqlError>
  8. #include "src/database/dbinfo.h"
  9. #include "debug.h"
  10. #include "src/experimental/UserInput.h"
  11. namespace experimental {
  12. /// [F] Just to put it somewhere planned DB member functions:
  13. /// static bool init() - maybe call it connect()?
  14. /// static bool disconnect() - close the database connection
  15. /// static bool commit(Entry entry) - upload to database, calls either INSERT or UPDATE
  16. /// bool insert(Entry entry) - (INSERT INTO tablename VALUES...)
  17. /// bool update(Entry entry) - (UPDATE tablename SET...)
  18. /// static bool delete(Entry entry) - (DELETE FROM tableName WHERE ...)
  19. /// static bool exists(Entry entry) - Check if entry exists in DB
  20. ///
  21. /// So a user input would pass the following stages:
  22. /// 1) Create UserInput object from Line Edits
  23. /// 2) process UserInput to obtain Entry object
  24. /// 3) Db operates on entry object (commit, delete, whatever)
  25. ///
  26. /// Idea for processing:
  27. /// static Entry processUserInput(UserInput userInput) - check the user input, perform matching of foreign keys and return an entry object ready for submission
  28. /// ColumnData matchForeignKey (ColName, ColData) - matches a user input to a foreign key, i.e. a registration to a tail_id or a Pilot Name to a pilot_id
  29. /// Entry prepareDataForCommit(Entry entry) - checks data is ready for submission
  30. ///
  31. ///
  32. /// SELF DOCUMENTING CODE
  33. using ColName = QString;
  34. using ColData = QString;
  35. using TableName = QString;
  36. using RowId = int;
  37. using DataPosition = QPair<TableName, RowId>;
  38. using TableData = QMap<ColName, ColData>;
  39. using ColumnData = QPair<ColName, ColData>;
  40. // DEFAULTS
  41. auto const DEFAULT_PILOT_POSITION = DataPosition("pilots", 0);
  42. /*!
  43. * \brief The Entry class encapsulates table metadata(table name, row id)
  44. * and data for new and existing entries in the database to operate on.
  45. */
  46. class Entry {
  47. public:
  48. const DataPosition position;
  49. protected:
  50. TableData table_data;
  51. public:
  52. Entry() = delete;
  53. Entry(DataPosition position_) : position(position_) {}
  54. void populate_data(TableData data) { table_data = data; }
  55. const TableData& data() { return table_data; }
  56. };
  57. // [George]: Either with polymorphism or simple functions the result will be the same.
  58. // if the following syntax is more clear to you we can switch to it.
  59. // ... = new NewPilotDialog(Pilot(selectedPilots.first(), ...);
  60. // the only difference will be that we will subclass Entry to have specialised
  61. // constructor
  62. class PilotEntry : public Entry {
  63. PilotEntry() = delete;
  64. PilotEntry(int row_id) : Entry::Entry(DataPosition("pilots", row_id)) {}
  65. };
  66. Entry newPilotEntry(int row_id) { return Entry(DataPosition("pilots", row_id)); }
  67. static
  68. bool insertPilot(UserInput& uin)
  69. {
  70. DEB("Inserting...");
  71. auto data = uin.all();
  72. auto position = DEFAULT_PILOT_POSITION;
  73. if (data.isEmpty()) {
  74. DEB("Object Contains no data. Aborting.");
  75. return false;
  76. }
  77. QString statement = "INSERT INTO " + position.first + QLatin1String(" (");
  78. for (auto i = data.cbegin(); i != data.cend(); ++i) {
  79. statement += i.key() + QLatin1String(", ");
  80. }
  81. statement.chop(2);
  82. statement += QLatin1String(") VALUES (");
  83. for (auto i = data.cbegin(); i != data.cend(); ++i) {
  84. statement += QLatin1String("'") + i.value() + QLatin1String("', ");
  85. }
  86. statement.chop(2);
  87. statement += QLatin1String(")");
  88. QSqlQuery q(statement);
  89. if (q.lastError().type() == QSqlError::NoError) {
  90. DEB("Entry successfully committed.");
  91. return true;
  92. } else {
  93. DEB("Unable to commit. Query Error: " << q.lastError().text());
  94. return false;
  95. }
  96. }
  97. /*!
  98. * \brief The DB class encapsulates the SQL database by providing fast access
  99. * to hot database data.
  100. */
  101. class DB {
  102. private:
  103. static QStringList tableNames;
  104. static QMap<TableName, QStringList> tableColumns;
  105. public:
  106. /*!
  107. * \brief Initialise DB class and populate columns.
  108. */
  109. static bool init();
  110. /*!
  111. * \brief Create new entry in the databse based on UserInput
  112. */
  113. static
  114. bool insert(UserInput uin)
  115. {
  116. switch (uin.meta_tag)
  117. {
  118. case UserInput::MetaTag::Pilot:
  119. DEB("Inserting NEW Pilot...");
  120. return insertPilot(uin);
  121. case UserInput::MetaTag::Flight:
  122. case UserInput::MetaTag::Aircraft:
  123. default:
  124. DEB("FALLTHROUGH in cases. Missing implementations");
  125. return false;
  126. }
  127. }
  128. static bool remove(UserInput uin) { return false; }
  129. static bool exists(UserInput uin) { return false; }
  130. /*!
  131. * \brief Updates entry in database from existing entry tweaked by the user.
  132. * New entry data is verified before commiting.
  133. */
  134. static bool update(Entry updated_entry)
  135. {
  136. auto position = updated_entry.position;
  137. auto data = updated_entry.data();
  138. QString statement = "UPDATE " + position.first + " SET ";
  139. for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
  140. if (i.key() != QString()) {
  141. statement += i.key() + QLatin1String("='") + i.value() + QLatin1String("', ");
  142. } else {
  143. DEB(i.key() << "is empty key. skipping.");
  144. }
  145. }
  146. statement.chop(2); // Remove last comma
  147. statement.append(QLatin1String(" WHERE _rowid_=") + QString::number(position.second));
  148. DEB("UPDATE QUERY: " << statement);
  149. QSqlQuery q(statement);
  150. //check result. Upon success, error should be " "
  151. if (q.lastError().type() == QSqlError::NoError)
  152. {
  153. DEB("Entry successfully committed.");
  154. return true;
  155. } else {
  156. DEB("Unable to commit. Query Error: " << q.lastError().text());
  157. return false;
  158. }
  159. }
  160. /*!
  161. * \brief Verify entry data is sane for the database.
  162. */
  163. static
  164. Entry prepareDataForCommit(Entry entry)
  165. /// [F] this function does not really verify data, it just removes bad entries,
  166. /// maybe returning the 'cleaned up' entry would be better?
  167. {
  168. TableData data = entry.data();
  169. auto position = entry.position;
  170. auto good_columns = tableColumns.value(entry.position.first);
  171. //Check validity of newData
  172. QStringList badkeys;
  173. for (auto i = data.cbegin(); i != data.cend(); ++i) {
  174. if (!good_columns.contains(i.key())) {
  175. DEB(i.key() << "Not in column list for table " << position.first << ". Discarding.");
  176. badkeys << i.key();
  177. }
  178. }
  179. for (const auto &var : badkeys) {
  180. data.remove(var);
  181. }
  182. entry.populate_data(data);
  183. return entry;
  184. ///[F] maybe this should be the other way around, i.e. update calls verify instead of verify calling update?
  185. }
  186. };
  187. } // namespace experimental
  188. #endif