#ifndef CRUDMODEL_H #define CRUDMODEL_H #include #include "ASyncDBConnection.h" #include "PgClass.h" #include "PgConstraint.h" #include "Pgsql_Connection.h" #include #include #include #include #include class PgConstraint; class OpenDatabase; class ASyncWindow; /** * @brief The CrudModel class * * Features * - order by one or more user selectable columns * - user filter condition * - user limit and offset * - hide columns (will not be retrieved can greatly speed up things when you disable wide columns) * - can use foreign keys to display dropdown * * * Need to keep track of changes from the original. Things to track * - new data being entered for existing row * - new data for new row * - current data of modified row * - current data of new row * - which rows are deleted * We need to be able to: * - determine row count easily * - find specific row from table easily * - support resorting (without requerying) * //- support custom filtering (without requerying) * * Keep data as much in original result * - use redirect list to go from index to actual row in the result * - use an additional map index by original row number containing changed data * - might be beneficial to have vector in sync with original list to signal changes from the result * this could prevent doing a lot of slower searches in the changed row set by only doing this when the direty markes * has been set vector takes 1 bit per value making it very efficient and fast. We also could put a bit in the mapping * vector but this probably would double the size of that list taking 32 times the size of vector. * */ class CrudModel: public QAbstractTableModel { Q_OBJECT public: explicit CrudModel(ASyncWindow *async_win); ~CrudModel(); void setConfig(std::shared_ptr db, const PgClass &table); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; // Basic functionality: int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex &index, int role) const override; virtual Qt::ItemFlags flags(const QModelIndex &) const override; virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override; public slots: virtual bool submit() override; virtual void revert() override; private: using PKeyValues = std::vector; // using RowData = std::vector; // using RowDataPtr = std::unique_ptr; // /** Choosen for a vector of pointers while vector is relatively slow with insertion and removal in the middle // * I hope the small size of the elements will make this not much of an issue while the simple linear and sequential // * nature of a vector will keep memory allocation fairly efficient. // */ // using RowList = std::vector; // std::shared_ptr resultToRowList(std::shared_ptr result); using Value = boost::optional; /** Used to remember the changes that have been made to rows. * * For simplicity it also holds the values for columns that have not been changed. */ class ModifiedRow { public: ModifiedRow(int row, const std::vector &values) : m_row(row), m_values(values) {} const auto& data() const { return m_values; } int row() const { return m_row; } private: int m_row; std::vector m_values; }; using ModifiedRowList = std::map; /** Similar to a modified row but it only stores values for columns that actually have been edited. * */ class PendingRow { public: using ValueMap = std::map; explicit PendingRow(int row) : m_row(row) {} const auto& data() const { return m_values; } int row() const { return m_row; } void setValue(int col, const Value &val) { m_values.insert_or_assign(col, val); } private: int m_row; ValueMap m_values; }; class PendingRowList { public: using Map = std::map; PendingRow& getRow(int row) { auto iter = m_rows.lower_bound(row); if (iter != m_rows.end() && iter->first == row) { return iter->second; } else { return m_rows.insert(iter, {row, PendingRow(row)})->second; } } void setValue(int col, int row, const Value &value) { auto iter = m_rows.find(row); if (iter == m_rows.end()) { iter = m_rows.insert({row, PendingRow(row)}).first; } iter->second.setValue(col, value); } boost::optional getValue(int col, int row) const { auto iter = m_rows.find(row); if (iter != m_rows.end()) { auto &r = iter->second; auto cell = r.data().find(col); if (cell != r.data().end()) return cell->second; } return boost::none; } auto begin() { return m_rows.begin(); } auto end() { return m_rows.end(); } void clear() { m_rows.clear(); } private: Map m_rows; }; ASyncWindow * m_asyncWindow; std::shared_ptr m_database; PgClass m_table; boost::optional m_primaryKey; ASyncDBConnection m_dbConn; bool callLoadData = false; std::shared_ptr m_roData; PendingRowList m_pendingRowList; /** Maintains a list of all modified rows. * * The key values are the indexes of the row before any rows were deleted. */ ModifiedRowList m_modifiedRowList; using RedirectVec = std::vector; /// In sync with the actual table, used to efficiently find the correct row in the result RedirectVec m_redirectVector; void loadData(); void loadIntoModel(std::shared_ptr data); Value getData(const QModelIndex &index) const; Oid getType(int column) const; /// /// \brief columnName /// \param col /// \return /// QString columnName(int col) const; /// /// \brief getModifiedRow searches for the specified row in the modified set /// \param row /// \return Pointer to the modified element or nullptr if there is no modification /// const ModifiedRow* getModifiedRow(int row) const; /// /// \brief getPKeyForRow retrieve the primary key of the specified row /// /// This function should not be called when there is no primarykey. (Editing should be disabled anyway in that case) /// /// \param row Actual result row not intermeddiat model row /// \return /// PKeyValues getPKeyForRow(int row) const; bool savePendingChanges(); void updateRow(const PendingRow &pending_row); private slots: void connectionStateChanged(ASyncDBConnection::State state); // void queryResult(std::shared_ptr result); // void queryError(); // void dataProcessingFutureFinished(); }; #endif // CRUDMODEL_H