diff --git a/pglab/CrudModel.cpp b/pglab/CrudModel.cpp index 0d3c1ed..337782b 100644 --- a/pglab/CrudModel.cpp +++ b/pglab/CrudModel.cpp @@ -66,13 +66,18 @@ QVariant CrudModel::headerData(int section, Qt::Orientation orientation, int rol // Basic functionality: int CrudModel::rowCount(const QModelIndex &/*parent*/) const { +// int row_count = m_roData ? m_roData->rows() : 0; - return m_roData ? m_roData->rows() : 0; +// todo there will be rownumbers that are not in m_roData + +// ++row_count; // one empty new row at the end + return m_rowCount; } int CrudModel::columnCount(const QModelIndex &/*parent*/) const { - return m_roData ? m_roData->cols() : 0; + int col_count = m_roData ? m_roData->cols() : 0; + return col_count; } Oid CrudModel::getType(int column) const @@ -84,30 +89,32 @@ CrudModel::Value CrudModel::getData(const QModelIndex &index) const { Value value; if (m_roData) { - int rij = index.row(); + int row = index.row(); int col = index.column(); + 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, rij); - if (!val) { + 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 // No pending save have a look if we have modified saved data in the modified list - auto find_res = m_modifiedRowList.find(rij); - if (find_res != m_modifiedRowList.end()) { + auto find_res = m_modifiedRowList.find(row); + 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) { + if (!val && row < m_roData->rows()) { // Then we are going to read the original data. - if (!m_roData->null(col, rij)) { - value = std::string(m_roData->val(col, rij)); - } + if (!m_roData->null(col, row)) + value = std::string(m_roData->val(col, row)); } else { - value = *val; + if (val) + value = *val; } } return value; @@ -169,6 +176,7 @@ void CrudModel::loadIntoModel(std::shared_ptr data) { beginResetModel(); m_roData = data; + m_rowCount = data->rows() + 1; endResetModel(); } @@ -205,21 +213,14 @@ bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int rol if (role == Qt::EditRole) { int row = index.row(); int col = index.column(); - //m_pendingRowList.getRow(row); Value val; -// Oid o = m_roData->type(index.column()); -// if (o == Pgsql::bool_oid) { -// val = value.Bool ? "t" : "f"; -// } -// else { - std::string s = value.toString().toUtf8().data(); - if (!s.empty()) { - if (s == "''") - s.clear(); - val = s; - } -// } + std::string s = value.toString().toUtf8().data(); + if (!s.empty()) { + if (s == "''") + s.clear(); + val = s; + } m_pendingRowList.setValue(col, row, val); emit dataChanged(index, index, QVector() << role); @@ -249,7 +250,7 @@ CrudModel::PKeyValues CrudModel::getPKeyForRow(int row) const values.push_back(*(mod_row->data()[col])); } } - else { + else if (row < m_roData->rows()){ 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 values.push_back(m_roData->get(col, row).c_str()); @@ -264,41 +265,104 @@ QString CrudModel::columnName(int col) const return m_roData->getColName(col); } +std::tuple CrudModel::createUpdateQuery(const PKeyValues &pkey_values, const PendingRow &pending_row) +{ + Pgsql::Params params; + auto data = pending_row.data(); + 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(); + return { buffer, params }; +} + +std::tuple CrudModel::createInsertQuery(const PendingRow &pending_row) +{ + Pgsql::Params params; + auto data = pending_row.data(); + QString table_name = genFQTableName(*m_database->catalogue(), m_table); + QString buffer; + QTextStream q(&buffer); + q << "INSERT INTO " << table_name << " 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::updateRow(const PendingRow &pending_row) { auto data = pending_row.data(); - Pgsql::Params params; + if (!data.empty()) { auto pkey_values = getPKeyForRow(pending_row.row()); - QString table_name = genFQTableName(*m_database->catalogue(), m_table); +// 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; - 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)); + Pgsql::Params params; + if (pkey_values.empty()){ + // todo insert query + std::tie(buffer, params) = createInsertQuery(pending_row); + } + else { + std::tie(buffer, params) = createUpdateQuery(pkey_values, pending_row); } - 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(); int row_number = pending_row.row(); Pgsql::Connection db_update_conn; @@ -307,9 +371,6 @@ std::tuple CrudModel::updateRow(const PendingRow & if (res) { auto result = db_update_conn.queryParam(buffer, params); if (result && result.rows() == 1) { - // pending row should be removed - - // and the result should be stored as a modified row std::vector values; auto row = *result.begin(); @@ -360,8 +421,16 @@ bool CrudModel::savePendingChanges() auto iter = m_pendingRowList.m_rows.begin(); auto [ok, modified_row] = updateRow(iter->second); if (ok) { - m_modifiedRowList.insert_or_assign(iter->first, modified_row); + int row = iter->first; + m_modifiedRowList.insert_or_assign(row, modified_row); m_pendingRowList.m_rows.erase(iter); + + if (row == m_rowCount - 1) + appendNewRow(); + + } + else { + return false; } } @@ -377,3 +446,11 @@ void CrudModel::revert() { } + +void CrudModel::appendNewRow() +{ + int row = m_rowCount; + beginInsertRows(QModelIndex(), row, row); + ++m_rowCount; + endInsertRows(); +} diff --git a/pglab/CrudModel.h b/pglab/CrudModel.h index 84d8d03..efb6c8a 100644 --- a/pglab/CrudModel.h +++ b/pglab/CrudModel.h @@ -197,13 +197,25 @@ private: bool callLoadData = false; std::shared_ptr m_roData; + /// \brief Total number of rows to show. + /// + /// The first n rows are the rows in m_roData. + /// While a row is being edited the modifications are in the PendingRowList + /// When the edits have been submitted the new values are put in the ModifiedRowList + /// + /// The last row is for creating new rows. There is no data for this row or it is + /// in the PendingRowList. As soon as the row has been inserted a new last empty row + /// will be created by increasing this variable. And the newly stored row will be in + /// the modified list. + /// + int m_rowCount = 0; PendingRowList m_pendingRowList; - /** Maintains a list of all modified rows. - * - * The key values are the indexes of the row before any rows were deleted. - */ + /// \brief 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; @@ -236,13 +248,17 @@ private: /// 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 + /// \return The values of the primary key column. If this is a new row it will return an empty list /// PKeyValues getPKeyForRow(int row) const; bool savePendingChanges(); + std::tuple createUpdateQuery(const PKeyValues &pkey_values, const PendingRow &pending_row); + std::tuple createInsertQuery(const PendingRow &pending_row); std::tuple updateRow(const PendingRow &pending_row); + + void appendNewRow(); private slots: void connectionStateChanged(ASyncDBConnection::State state);