Blame view

src/ormtable.h 14.9 KB
5251bf3a   Steven de Ridder   Initial commit. d...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
  /* ****************************************************************************
   * Copyright 2019 Open Systems Development BV                                 *
   *                                                                            *
   * Permission is hereby granted, free of charge, to any person obtaining a    *
   * copy of this software and associated documentation files (the "Software"), *
   * to deal in the Software without restriction, including without limitation  *
   * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
   * and/or sell copies of the Software, and to permit persons to whom the      *
   * Software is furnished to do so, subject to the following conditions:       *
   *                                                                            *
   * The above copyright notice and this permission notice shall be included in *
   * all copies or substantial portions of the Software.                        *
   *                                                                            *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    *
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        *
   * DEALINGS IN THE SOFTWARE.                                                  *
   * ***************************************************************************/
  #ifndef OSDEV_COMPONENTS_ORMTABLE_H
  #define OSDEV_COMPONENTS_ORMTABLE_H
  
  #include <QObject>
  #include <QStringList>
  #include <QSqlRelationalTableModel>
  #include <QSortFilterProxyModel>
  #include <QSqlDatabase>
  #include <QSqlRecord>
  #include <QHash>
  
  #include "dbconnector.h"
  #include "ormreldata.h"
  #include "ormtablerow.h"
  #include "dbrelation.h"
  #include "conversionutils.h"
  #include "timeline.h"
  
  namespace osdev {
  namespace components {
  
  /*!
   * \brief   This class is used as a TransActionsscopeGuard
   *          It takes a database connection at its constructor
   *          and calls the rollBack method in its destructor.
   */
  class OrmTransGuard
  {
  public:
      OrmTransGuard()
          : m_db(nullptr)
      {
      }
  
      explicit OrmTransGuard( DbConnector& _db )
          : m_db( &_db )
      {
      }
  
      ~OrmTransGuard()
      {
          if (nullptr != m_db)
          {
              m_db->transactionRollback();
          }
      }
  
      OrmTransGuard(const OrmTransGuard&) = delete;
      OrmTransGuard& operator=(const OrmTransGuard&) = delete;
  
      OrmTransGuard(OrmTransGuard&& rhs)
          : m_db(std::move(rhs.m_db))
      {
          rhs.m_db = nullptr;
      }
  
      OrmTransGuard& operator=(OrmTransGuard&& rhs)
      {
          if (this == &rhs)
          {
              return *this;
          }
  
          if (nullptr != m_db)
          {
              m_db->transactionRollback();
          }
          m_db = std::move(rhs.m_db);
          rhs.m_db = nullptr;
          return *this;
      }
  
  private:
      DbConnector* m_db;
  };
  
  /*!
   * \brief   The OrmTable represents a database table. Depending
   *          on the EditStrategy, it updates the database by each
   *          inserted record or after all records are inserted.
   * \note    Internally setTable is used. Please do not use setQuery
   *          also because that will invalidate the OrmTable meta data.
   */
  class OrmTable : public QSqlRelationalTableModel
  {
      Q_OBJECT
  
  public:
      /*!
       * \brief Default constructor.
       * \param parent The parent object as a QObject pointer
       * \param db The database layer we're connected through.
       */
      explicit OrmTable(const DbConnector& db, QObject *parent = nullptr );
  
      // Deleted copy- and move constructors
      OrmTable( const OrmTable& ) = delete;
      OrmTable( const OrmTable&& ) = delete;
      OrmTable& operator=( const OrmTable& ) = delete;
      OrmTable& operator=( const OrmTable&& ) = delete;
  
      /// \brief Destructor
      virtual ~OrmTable();
  
      /*!
       * \brief Sets the tablename, this model represents.
       * \param table The name of the table.
       */
      void setTable( const QString& table );
  
      /*!
       * \brief Insert a single QSqlRecord into the model.
       * \param row Inserts the record at position row.
       *            If row is negative, the record will be appended to the end.
       * \param record Record to insert
       * \return true if the record could be inserted, otherwise false.
       */
      bool insertRecord( int row, const QSqlRecord &record );
  
      /*!
       * \brief Insert multiple QSqlRecords into the model.
       * \param row Inserts the records at position row.
       *            If row is negative, the records will be appended to the end.
       *            calls insertRecord internally.
       * \param records Records to insert
       * \return true if the record could be inserted, otherwise false.
       */
      bool insertRecords(int row, const QList<QSqlRecord>& records );
  
      /*!
       * \brief   writes the data given in the dataobject to the table and to the database.
       * \param   dataObject - Contains all relational data.
       */
      void writeData( const QSharedPointer<ORMRelData>& dataObject );
  
      /*!
       * \brief   writes the values to a given record, identified by the fieldname in "KeyField".
       * \param   dataObject - Contains "flat" data
       * \return  True if successful, false if not.
       */
      bool updateRecord( ORMData* dataObject );
  
      /*!
       * \brief   This method is for convenience only. It will retrieve (a set of) records, based on the criteria given.
       * \param   fieldName - The fieldname we like to filter on
       * \param   filterExp - The filter expression
       * \return  A QList of SQLRecords containing the results.
       *          The list will be empty if no records were found or the table is empty.
       */
      QList<OrmTableRow> readData( const QString& fieldName = QString(), const QString& filterExp = QString() );
  
  
      /*!
       * \brief   Returns the number of relations added to this table.
       * \return  The number of relations. 0 if none.
       */
      int numberOfRelations() const { return m_qhRelations.count(); }
  
      /*!
       * \brief setRelatedTable
       * \param tableName The name of the related table.
       * \todo Verify pointer ownership
       * \param pTable Owning pointer to the Table. Ownership is transferred to this instance.
       */
      void setRelatedTable( const QString& tableName, QSortFilterProxyModel* pTable );
  
      /*!
       * \brief Gets the RelatedTable for the specified table name.
       * \param tableName The name of the table for which to get the relation.
       * \return The relation to the table with the specified table name.
       */
      QSortFilterProxyModel* getRelatedTable( const QString& tableName ) const;
  
      /*!
       * \brief Store the relation to another table, stored by its unique name.
       *        ORMTable is owner of the relation pointer.
       * \param relationName - the Name of the relation as known by the database.
       */
      void    saveRelation( const QString& relationName, DbRelation* pRelation );
  
      /*!
       * \brief   Get the relationstructure
       * \param   relationName The name of the relation to get.
       * \return  The pointer to the datastructure. NULL if none exist.
       */
      DbRelation* getRelation( const QString& relationName ) const;
  
      /*!
       * \brief   Get all registered constraintnames by their true name.
       * \return  The list of all relation by their contraint names. If no relations exist, the list will be empty.
       */
      QStringList getRelationNames() const;
  
      /*!
       * \brief   Gets the attached relation based on a given tablename.
       * \param   tableName - The tablename we want the relation on.
       * \return  The relation we're searching for. isValid is true if there is a relation, false if not.
       */
      QList<DbRelation*> getRelationsByTableName( const QString& tableName );
  
      /*!
       * \brief   This method will solve any relations in the structure.
       * \param   dataObject - The ORMRelData object describing the data
       * \return  The ORMData object with all solved references, if there were any.
       *          If the main container can't be retrieved, it will return a nullptr. Check for validity.
       */
      ORMData* solveRelations( const QSharedPointer<ORMRelData>& dataObject );
  
      /*!
       * \brief   Do a "batched" update. The dataobject contains lists which represents different records.
       * \param   dataObject  - The "maintable" object after resolving the relations.
       *                        It is the responsibility of this TableObject to delete this object if successful,
       *                        else it will be rejected and ownership will be transferred to the next subsystem.
       *                        before calling this method, a transaction should be started on the referenced database
       *                        connection. If this method ( and possible following statements like deCoupleRecords )
       *                        the transaction should be committed.
       * \param   sourceId      The source from which the dataObject originates.
       * \return  True if the complete update was successful. False if not and should result in a rejected
       *          transaction. ( See signalRejectedData() ) Rejecting should be done by the calling method.
       */
      bool    batchUpdate( ORMData* dataObject, const QUuid& sourceId );
  
      /*!
       * \brief   Decouple records from their previous value (Normally a foreign key relation).
       *          This method will fail if a field in the database is not-nullable or the number
       *          of fields is larger than 2 (1 searchField and 1 updateable field.
       * \param   dataObject  - The dataobject containing all the information needed to update the record(s).
       *                        It is the responsibility of this TableObject to delete this object if successful,
       *                        else it will be rejected and ownership will be transferred to the next subsystem.
       *                        before calling this method, a transaction should be started on the referenced database
       *                        connection. If this method ( and possible previous statements like batchUpdate() )
       *                        the transaction should be committed.
       * \return  true if successful. false if not.
       */
      bool    deCoupleRecords( ORMData* dataObject );
  
      /*!
       * \brief   Get the name of the field that will track the mutation date / time in uSecs since EPOCH.
       * \return  The name of the field. Empty if no field was configured.
       */
      QString trackField() { return m_recTrackField; }
  
      /*!
       * \brief   Get the name of the field that will track the mutation date / time in uSecs since EPOCH.
       * \param   _recTrackFieldName - The name of the field, coming from the configuration.
       */
      void    setTrackField( const QString& _recTrackFieldName ) { m_recTrackField = _recTrackFieldName; }
  
  
  signals:
      /*!
       * \brief Signal for RejectedData.
       * \param dataContainer The data container for this signal.
       */
      void signalRejectedData( const QSharedPointer<ORMRelData>& dataContainer );
  
  private:
  
      /*!
       * \brief   Translates an ORMData into an QSqlRecord
       * \param   tableObject - The tableObject we like to translate into an sql-record to store.
       * \return  The QSqlRecord object representing an entry. If the creation of the record fails,
       *          the result will be an empty SqlRecord structure.
       */
      QSqlRecord                              buildSqlRecord( ORMData* tableObject );
  
      /*!
       * \brief   Builds a query and filters the table data. Data isn't removed, but a resultset is shown.
       *
       * \param   dataObject - The dataobject of this particular table as extracted from ORMRelData. Based on the
       *                       keyField(s) and values, the filter is set. ( See also resetFilter() )
       * \return  The number of resulting records that match the criteria. If the filter fails, the result will be -1.
       */
      int                                     tableFilter( ORMData* dataObject );
  
      /*!
       * \brief   Checks if the ORMData package is valid against the record it tries to update. Each table should have a
       *          field of type INT which contains the timestamp since epoch. Before updating a record, it should check if the timestamp
       *          of the package is actually newer than the timestamp of that record. If the package is newer ( a.k.a. Package timestamp > timestamp_record )
       *          it is valid to update the record with its values, including the timestamp. This will prevent the situation where records will be overwritten
       *          with "old" data. This method works together with the internal variable 'm_recTrackField'. Before calling this method, we have to check if this
       *          variable isn't empty. If so, we don't keep track of record changes in a timeline and we can update the records anyway.
       *
       * \param   dataObject  - The ORMData object containing a complete transaction.
       *                        Does not have to be resolved by relations yet, this will be done after the validity check.
       *
       * \return  True if the record can be updated, False if the package is older than the actual record.
       */
      bool                                    isPackageValid( ORMData* dataObject );
  
      /*!
       * \brief   resets the filter, allowing the table to show all records. ( See also tableFilter() )
       * \return  The number of records show by the table.
       */
      int                                     resetFilter();
  
      /*!
       * \brief   Commit a database transaction if one is in progress.
       * \note If the driver does not support transactions this method will look at the last driver error and return false if it contains a message.
       * \return  True if commit succeeds or if there was no transaction in progress, false if commit fails.
       */
      bool                                    commit();
  
      QString                                 m_className;        ///< Class name for logging purposes
      QString                                 m_recTrackField;    ///< Name of the field, which keep tracks of changes on each record. If Empty, we don't track.
      DbConnector                             m_db;               ///< Database layer we're connected through. We need the reference to access its helper methods.
      QHash<QString, QSortFilterProxyModel*>  m_qhRelTables;      ///< Filtered view on a related table.
      QHash<QString, DbRelation*>             m_qhRelations;      ///< The actual relation Meta information stored for reference.
      QHash<QString, QVariant::Type>          m_qhFieldTypes;     ///< The internal storage for all fieldtypes by their fieldname.
      QHash<QUuid, QSharedPointer<Timeline>>  m_qhTimelines;      ///< Timelines used for the batch update
  };
  
  } // End namespace components
  } // End namespace osdev
  
  #endif /* OSDEV_COMPONENTS_ORMTABLE_H */