From 8c20bd6a02b128a1d70b629677b25e69d6784e67 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 8 Apr 2018 09:02:22 +0200 Subject: [PATCH] Added delete support to the CRUD system. --- pglab/CrudModel.cpp | 218 +++++++++++++++++++++++++++-------------- pglab/CrudModel.h | 31 +++++- pglab/CrudTab.cpp | 13 +++ pglab/CrudTab.h | 1 + pglab/CrudTab.ui | 11 +++ pglab/IndexModel.h | 2 + pgsql/Pgsql_Params.cpp | 18 ++-- pgsql/Pgsql_Params.h | 25 ++++- 8 files changed, 230 insertions(+), 89 deletions(-) diff --git a/pglab/CrudModel.cpp b/pglab/CrudModel.cpp index ded9fd6..091bf9a 100644 --- a/pglab/CrudModel.cpp +++ b/pglab/CrudModel.cpp @@ -2,6 +2,7 @@ #include "ASyncWindow.h" #include "OpenDatabase.h" #include "PgDatabaseCatalog.h" +#include "PgAttribute.h" #include "PgAttributeContainer.h" #include "PgConstraintContainer.h" #include "GlobalIoService.h" @@ -42,7 +43,6 @@ void CrudModel::setConfig(std::shared_ptr db, const PgClass &table callLoadData = true; auto dbconfig = m_database->config(); m_dbConn.setupConnection(dbconfig); - } QVariant CrudModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -89,32 +89,39 @@ CrudModel::Value CrudModel::getData(const QModelIndex &index) const { Value value; if (m_roData) { - int row = index.row(); + int grid_row = index.row(); int col = index.column(); + auto row_mapping = m_rowMapping[grid_row]; + const int last_row = rowCount() - 1; //Oid o = m_roData->type(col); // First see if we have buffered editted values that still need saving - boost::optional val = m_pendingRowList.getValue(col, row); - if (!val && row < last_row) { // last_row should never be in modified list, when it is put in modified list a new last row should be created + boost::optional val; + if (row_mapping.pending) { + val = m_pendingRowList.getValue(col, row_mapping.rowKey); + } + if (row_mapping.modified && !val && grid_row < last_row) { // last_row should never be in modified list, when it is put in modified list a new last row should be created // No pending save have a look if we have modified saved data in the modified list - auto find_res = m_modifiedRowList.find(row); - if (find_res != m_modifiedRowList.end()) + auto find_res = m_modifiedRowList.find(row_mapping.rowKey); + if (find_res != m_modifiedRowList.end()) { val = find_res->second.data()[col]; - + } } //Value value; // If we did not have pending or modified data - if (!val && row < m_roData->rows()) { + if (!val && row_mapping.rowKey < m_roData->rows()) { // Then we are going to read the original data. - if (!m_roData->null(col, row)) - value = std::string(m_roData->val(col, row)); + if (!m_roData->null(col, row_mapping.rowKey)) { + value = std::string(m_roData->val(col, row_mapping.rowKey)); + } } else { - if (val) + if (val) { value = *val; + } } } return value; @@ -176,10 +183,20 @@ void CrudModel::loadIntoModel(std::shared_ptr data) { beginResetModel(); m_roData = data; - m_rowCount = data->rows() + 1; + lastRowKey = data->rows(); + m_rowCount = data->rows(); + initRowMapping(); + appendNewRow(); endResetModel(); } +void CrudModel::initRowMapping() +{ + m_rowMapping.resize(m_rowCount); + for (int i = 0; i < m_rowCount; ++i) + m_rowMapping[i] = { i }; +} + void CrudModel::connectionStateChanged(ASyncDBConnection::State state) { switch (state) { @@ -207,7 +224,7 @@ Qt::ItemFlags CrudModel::flags(const QModelIndex &) const { Qt::ItemFlags flags = Qt::ItemIsSelectable + Qt::ItemIsEnabled; if (m_primaryKey) { - flags += Qt::ItemIsEditable; + flags |= Qt::ItemIsEditable; } return flags; } @@ -215,9 +232,12 @@ Qt::ItemFlags CrudModel::flags(const QModelIndex &) const bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole) { - int row = index.row(); + int grid_row = index.row(); int col = index.column(); + auto& row_mapping = m_rowMapping[grid_row]; + row_mapping.pending = true; + Value val; std::string s = value.toString().toUtf8().data(); if (!s.empty()) { @@ -225,7 +245,7 @@ bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int rol s.clear(); val = s; } - m_pendingRowList.setValue(col, row, val); + m_pendingRowList.setValue(col, row_mapping.rowKey, val); emit dataChanged(index, index, QVector() << role); return true; @@ -310,18 +330,56 @@ std::tuple CrudModel::createInsertQuery(const PendingRow QString table_name = genFQTableName(*m_database->catalogue(), m_table); QString buffer; QTextStream q(&buffer); - q << "INSERT INTO " << table_name << " VALUES ($1"; + q << "INSERT INTO " << table_name << "("; + + auto columns = m_database->catalogue()->attributes()->getColumnsForRelation(m_table.oid); + bool first = true; + for (auto e : data) { + int num = e.first + 1; + auto find_res = std::find_if(columns.begin(), columns.end(), + [num] (const auto &elem) -> bool { return num == elem.num; }); + if (find_res != columns.end()) { + if (first) first = false; + else q << ","; + q << find_res->name; + } + } + q << ") VALUES ($1"; for (size_t p = 2; p <= data.size(); ++p) q << ",$" << p; q << ") RETURNING *"; for (auto e : data) { // Add value to paramlist + params.add(e.second, getType(e.first)); } q.flush(); return { buffer, params }; } +std::tuple CrudModel::createDeleteStatement(const PKeyValues &pkey_values) +{ + Pgsql::Params params; + QString table_name = genFQTableName(*m_database->catalogue(), m_table); + QString buffer; + QTextStream q(&buffer); + q << "DELETE FROM " << table_name; + q << "\nWHERE "; + int i = 0, param = 0; + for (auto attnum : m_primaryKey->key) { + int col = attnum - 1; // Assume column ordering matches table, also we assume know special columns like oid are shown + if (i > 0) + q << " AND "; + q << quoteIdent(columnName(col)) << "=$" << ++param; + params.add(pkey_values[i].c_str(), getType(col)); + ++i; + } + + q.flush(); + return { buffer, params }; +} + + std::tuple CrudModel::updateRow(const PendingRow &pending_row) { auto data = pending_row.data(); @@ -329,38 +387,9 @@ std::tuple CrudModel::updateRow(const PendingRow & if (!data.empty()) { auto pkey_values = getPKeyForRow(pending_row.row()); -// QString table_name = genFQTableName(*m_database->catalogue(), m_table); -// QString buffer; -// QTextStream q(&buffer); -// q << "UPDATE " << table_name << " AS d\n SET "; -// int param = 0; -// for (auto e : data) { -// if (param > 0) -// q << ","; -// q << quoteIdent(columnName(e.first)) << "=$" << ++param; - -// // Add value to paramlist -// params.add(e.second, getType(e.first)); -// } - -// q << "\nWHERE "; -// int i = 0; -// for (auto attnum : m_primaryKey->key) { -// int col = attnum - 1; // Assume column ordering matches table, also we assume know special columns like oid are shown -// if (i > 0) -// q << " AND "; -// q << quoteIdent(columnName(col)) << "=$" << ++param; - -// params.add(pkey_values[i].c_str(), getType(col)); -// ++i; -// } -// q << "\nRETURNING *"; -// q.flush(); - QString buffer; Pgsql::Params params; if (pkey_values.empty()){ - // todo insert query std::tie(buffer, params) = createInsertQuery(pending_row); } else { @@ -391,46 +420,31 @@ std::tuple CrudModel::updateRow(const PendingRow & } } -// m_dbConn.send(buffer.toUtf8().data(), params, -// [row_number, this] (Expected> result, qint64) { - -// } ); } return { false, {} }; } bool CrudModel::savePendingChanges() { - // need to start async store of changes - - // modified results are only known after updates/inserts have returned the new values - // (new values can be modified by triggers and such things) - - // do we leave panding changes in the list until async task complete? But then we do not know which - // ones we are storing because user can add new ones. - - // We could also clear the pending list and keep a copy in the async task but then display would revert back - // to original values until save has completed. - - // Moving them to modified list doesn't seem a good idea either because we would have to undo the changes - // when the save fails. - - - -// PendingRowList copy = m_pendingRowList; - // - - while (!m_pendingRowList.m_rows.empty()) { auto iter = m_pendingRowList.m_rows.begin(); auto [ok, modified_row] = updateRow(iter->second); if (ok) { - int row = iter->first; - m_modifiedRowList.insert_or_assign(row, modified_row); + int rowKey = iter->first; + m_modifiedRowList.insert_or_assign(rowKey, modified_row); m_pendingRowList.m_rows.erase(iter); - if (row == m_rowCount - 1) - appendNewRow(); + auto iter = std::find_if(m_rowMapping.begin(), m_rowMapping.end(), + [rowKey](const RowMapping &rhs) -> auto { return rhs.rowKey == rowKey; }); + if (iter != m_rowMapping.end()) { + iter->modified = true; + iter->pending = false; + int row = iter - m_rowMapping.begin(); + if (row == m_rowCount - 1) + appendNewRow(); + + } + } else { @@ -455,6 +469,64 @@ void CrudModel::appendNewRow() { int row = m_rowCount; beginInsertRows(QModelIndex(), row, row); + m_rowMapping.emplace_back(allocNewRowKey()); ++m_rowCount; endInsertRows(); } + +void CrudModel::removeRows() +{ +// determine selection +// remove selected rows + +} + +bool CrudModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (count > 1) return false; + if (m_rowMapping.empty()) return false; + // When removing rows there is no direct mapping anymore between the rows in the grid + // and the rows in m_roData + + // Therefor we need an indirection to keep track of which rows are visible and which + // grid row maps to what data row. Maybe we can also keep track where the current data + // of the row is located original data, pending data or modified data + + // 1. Get PKEY and remove that row from table + + + auto mapping = m_rowMapping[row]; + // Get PKey for row + auto pkey_values = getPKeyForRow(mapping.rowKey); + if (!pkey_values.empty()) { + // Generate DELETE + // QString buffer; + // Pgsql::Params params; + auto [buffer, params] = createDeleteStatement(pkey_values); + // Execute DELETE + Pgsql::Connection db_update_conn; + auto dbconfig = m_database->config(); + bool res = db_update_conn.connect(dbconfig.getKeywords(), dbconfig.getValues(), false); + if (res) { + auto result = db_update_conn.queryParam(buffer, params); + if (result) { + + beginRemoveRows(parent, row, row); + + // 2. remove that row from m_rowMapping + m_rowMapping.erase(m_rowMapping.begin() + row); + + + // 3. if the row is in modified it can be removed from modified + if (mapping.modified) { + m_modifiedRowList.erase(mapping.rowKey); + } + // 4. can it be pending? should be removed if it is. + + endRemoveRows(); + return true; + } + } + } + return false; +} diff --git a/pglab/CrudModel.h b/pglab/CrudModel.h index efb6c8a..8851a16 100644 --- a/pglab/CrudModel.h +++ b/pglab/CrudModel.h @@ -72,6 +72,10 @@ public: virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override; void loadData(); + + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + /// Removes selected rows + void removeRows(); public slots: virtual bool submit() override; virtual void revert() override; @@ -186,6 +190,19 @@ private: Map m_rows; }; + class RowMapping { + public: + bool modified = false; ///< Row has been modified, look there for actual data + bool pending = false; ///< Row has pending changes, look first in pending for current data + + int rowKey = -1; ///< value to use as key/index into the data lists + + RowMapping() = default; + explicit RowMapping(int row_key) + : rowKey(row_key) + {} + }; + using RowMappingVector = std::vector; ASyncWindow * m_asyncWindow; @@ -217,10 +234,14 @@ private: /// The key values are the indexes of the row before any rows were deleted. /// ModifiedRowList m_modifiedRowList; + /// Keeps track of mapping grid rows to data rows + RowMappingVector m_rowMapping; + /// call on initial load to fill in the mappings + void initRowMapping(); - using RedirectVec = std::vector; - /// In sync with the actual table, used to efficiently find the correct row in the result - RedirectVec m_redirectVector; +// using RedirectVec = std::vector; +// /// In sync with the actual table, used to efficiently find the correct row in the result +// RedirectVec m_redirectVector; void loadIntoModel(std::shared_ptr data); @@ -256,9 +277,13 @@ private: std::tuple createUpdateQuery(const PKeyValues &pkey_values, const PendingRow &pending_row); std::tuple createInsertQuery(const PendingRow &pending_row); + std::tuple createDeleteStatement(const PKeyValues &pkey_values); std::tuple updateRow(const PendingRow &pending_row); void appendNewRow(); + + int lastRowKey = 0; + int allocNewRowKey() { return ++lastRowKey; } private slots: void connectionStateChanged(ASyncDBConnection::State state); diff --git a/pglab/CrudTab.cpp b/pglab/CrudTab.cpp index 4c15dcd..ef5d9e0 100644 --- a/pglab/CrudTab.cpp +++ b/pglab/CrudTab.cpp @@ -22,6 +22,7 @@ CrudTab::CrudTab(MainWindow *parent) ui->tableView->setModel(m_crudModel); ui->tableView->setSelectionMode(QAbstractItemView::SingleSelection); + ui->tableView->addAction(ui->actionRemove_rows); //auto selection_model = ui->tableView->selectionModel(); // connect(ui->tableView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, // &CrudTab::tableView_currentRowChanged); @@ -55,3 +56,15 @@ void CrudTab::refresh() { m_crudModel->loadData(); } + +void CrudTab::on_actionRemove_rows_triggered() +{ + // determine selection + +// ui->tableView->currentIndex() +// selectionModel()-> + + //ui->tableView->selectionModel()->selectedRows() + m_crudModel->removeRow(ui->tableView->currentIndex().row()); + //removeRows(); +} diff --git a/pglab/CrudTab.h b/pglab/CrudTab.h index 3256265..768e2a3 100644 --- a/pglab/CrudTab.h +++ b/pglab/CrudTab.h @@ -36,6 +36,7 @@ private: private slots: // void tableView_currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous); + void on_actionRemove_rows_triggered(); }; #endif // CRUDTAB_H diff --git a/pglab/CrudTab.ui b/pglab/CrudTab.ui index a828845..26d29d4 100644 --- a/pglab/CrudTab.ui +++ b/pglab/CrudTab.ui @@ -18,6 +18,17 @@ + + + Remove rows + + + Ctrl+D + + + Qt::WidgetWithChildrenShortcut + + diff --git a/pglab/IndexModel.h b/pglab/IndexModel.h index 5ec5c3a..ec7b4e3 100644 --- a/pglab/IndexModel.h +++ b/pglab/IndexModel.h @@ -31,6 +31,8 @@ public: int columnCount(const QModelIndex &parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex &index, int role) const override; + + PgIndex getIndex(int row) const { return m_indexes[row]; } protected: Oid getType(int column) const; QVariant getData(const QModelIndex &index) const; diff --git a/pgsql/Pgsql_Params.cpp b/pgsql/Pgsql_Params.cpp index fe1eaef..6fe88d2 100644 --- a/pgsql/Pgsql_Params.cpp +++ b/pgsql/Pgsql_Params.cpp @@ -52,25 +52,26 @@ Params::~Params() deleteValues(); } -void Params::addText(const char *data, Oid oid) +Param Params::addText(const char *data, Oid oid) { m_paramTypes.push_back(oid); m_paramValues.push_back(data); m_paramLengths.push_back(data ? strlen(data) + 1 : 0); m_paramFormats.push_back(0); + return Param(*this, m_paramValues.size() - 1); } -void Params::add(const QString &s, Oid oid) +Param Params::add(const QString &s, Oid oid) { auto ba = s.toUtf8(); const int len = ba.size(); char * p = new char[len+1]; std::memcpy(p, ba.data(), len); p[len] = 0; - addText(p, oid); + return addText(p, oid); } -void Params::add(const char *data, Oid oid) +Param Params::add(const char *data, Oid oid) { char * p = nullptr; int len = 0; @@ -80,11 +81,12 @@ void Params::add(const char *data, Oid oid) std::memcpy(p, data, len); p[len] = 0; } + return addText(p, oid); - m_paramTypes.push_back(oid); - m_paramValues.push_back(p); - m_paramLengths.push_back(len); - m_paramFormats.push_back(0); +// m_paramTypes.push_back(oid); +// m_paramValues.push_back(p); +// m_paramLengths.push_back(len); +// m_paramFormats.push_back(0); } //void Params::addBinary(const char *data, int length, Oid oid) diff --git a/pgsql/Pgsql_Params.h b/pgsql/Pgsql_Params.h index 36c0660..4e9fa20 100644 --- a/pgsql/Pgsql_Params.h +++ b/pgsql/Pgsql_Params.h @@ -10,6 +10,21 @@ namespace Pgsql { + class Params; + + class Param { + public: + Param(Params ¶ms, int index) + : params(params) + , index(index) + {} + + int getIndex() const { return index; } + private: + Params ¶ms; + int index; + }; + class Params { public: Params(); @@ -20,11 +35,11 @@ namespace Pgsql { ~Params(); - void add(const QString &s, Oid oid=varchar_oid); - void add(const char *data, Oid oid=varchar_oid); - void add(boost::optional s, Oid oid=varchar_oid) + Param add(const QString &s, Oid oid=varchar_oid); + Param add(const char *data, Oid oid=varchar_oid); + Param add(boost::optional s, Oid oid=varchar_oid) { - add(s ? s->c_str() : nullptr, oid); + return add(s ? s->c_str() : nullptr, oid); } //void addBinary(const char *data, int length, Oid oid); void clear(); @@ -58,7 +73,7 @@ namespace Pgsql { * * The class takes ownership of data and will try to delete[] it. */ - void addText(const char *data, Oid oid=VARCHAROID); + Param addText(const char *data, Oid oid=VARCHAROID); }; } // end namespace Pgsql