/* **************************************************************************** * 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 #include #include #include #include #include #include #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& 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& 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 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 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& 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& 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 m_qhRelTables; ///< Filtered view on a related table. QHash m_qhRelations; ///< The actual relation Meta information stored for reference. QHash m_qhFieldTypes; ///< The internal storage for all fieldtypes by their fieldname. QHash> m_qhTimelines; ///< Timelines used for the batch update }; } // End namespace components } // End namespace osdev #endif /* OSDEV_COMPONENTS_ORMTABLE_H */