#include "CrudModel.h" #include "ASyncWindow.h" #include "OpenDatabase.h" #include "PgDatabaseCatalog.h" #include "PgAttributeContainer.h" #include "PgConstraintContainer.h" #include "GlobalIoService.h" #include "SqlFormattingUtils.h" #include "WorkManager.h" #include "Pgsql_oids.h" #include #include #include #include "Pgsql_oids.h" #include "Pgsql_Params.h" #include CrudModel::CrudModel(ASyncWindow *async_window) : m_asyncWindow(async_window) , m_dbConn(*getGlobalAsioIoService()) { qDebug("CrudModel created"); connect(&m_dbConn, &ASyncDBConnection::onStateChanged, this, &CrudModel::connectionStateChanged); } CrudModel::~CrudModel() { m_dbConn.closeConnection(); } /* * Strategy * when ordered by primary key, offset and limit work very quickly so we can get away with not loading * everything. */ void CrudModel::setConfig(std::shared_ptr db, const PgClass &table) { m_database = db; m_table = table; m_primaryKey = db->catalogue()->constraints()->getPrimaryForRelation(table.oid); //cat->attributes()->getColumnsForRelation() callLoadData = true; auto dbconfig = m_database->config(); m_dbConn.setupConnection(dbconfig); } QVariant CrudModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant r; if (role == Qt::DisplayRole) { if (orientation == Qt::Horizontal) { QString s(m_roData->getColName(section)); s += "\n"; s += getTypeDisplayString(*m_database->catalogue(), getType(section)); r = s; } else { r = QString::number(section + 1); } } return r; } // Basic functionality: int CrudModel::rowCount(const QModelIndex &/*parent*/) const { return m_roData ? m_roData->rows() : 0; } int CrudModel::columnCount(const QModelIndex &/*parent*/) const { return m_roData ? m_roData->cols() : 0; } Oid CrudModel::getType(int column) const { return m_roData ? m_roData->type(column) : InvalidOid; } CrudModel::Value CrudModel::getData(const QModelIndex &index) const { Value value; if (m_roData) { int rij = index.row(); int col = index.column(); //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) { // 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()) { val = find_res->second.data()[col]; } } //Value value; // If we did not have pending or modified data if (!val) { // Then we are going to read the original data. if (!m_roData->null(col, rij)) { value = std::string(m_roData->val(col, rij)); } } else { value = *val; } } return value; } QVariant CrudModel::data(const QModelIndex &index, int role) const { QVariant v; if (role == Qt::EditRole) { auto value = getData(index); if (value) { QString s = QString::fromUtf8(value->c_str()); v = s; } } else if (role == Qt::DisplayRole) { auto value = getData(index); if (value) { Oid o = m_roData->type(index.column()); if (o == Pgsql::bool_oid) { v = *value == "t"; //s = (s == "t") ? "TRUE" : "FALSE"; } else { QString s = QString::fromUtf8(value->c_str()); if (s.length() > 256) { s.truncate(256); } v = s; } } } else if (role == Qt::UserRole) { v = getType(index.column()); } return v; } void CrudModel::loadData() { QString table_name = genFQTableName(*m_database->catalogue(), m_table); std::string q = "SELECT * FROM "; q += table_name.toUtf8(); m_dbConn.send(q, [this] (Expected> res, qint64) { if (res.valid()) { auto dbres = res.get(); if (dbres && *dbres) { m_asyncWindow->QueueTask([this, dbres]() { loadIntoModel(dbres); }); } } else { // emit onQueryError(); } }); } void CrudModel::loadIntoModel(std::shared_ptr data) { beginResetModel(); m_roData = data; endResetModel(); } void CrudModel::connectionStateChanged(ASyncDBConnection::State state) { switch (state) { case ASyncDBConnection::State::NotConnected: break; case ASyncDBConnection::State::Connecting: break; case ASyncDBConnection::State::Connected: if (callLoadData) { callLoadData = false; loadData(); } break; case ASyncDBConnection::State::QuerySend: break; case ASyncDBConnection::State::CancelSend: break; case ASyncDBConnection::State::Terminating: break; } } Qt::ItemFlags CrudModel::flags(const QModelIndex &) const { return Qt::ItemIsSelectable + Qt::ItemIsEditable + Qt::ItemIsEnabled; } bool CrudModel::setData(const QModelIndex &index, const QVariant &value, int role) { 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; } // } m_pendingRowList.setValue(col, row, val); emit dataChanged(index, index, QVector() << role); return true; } return false; } const CrudModel::ModifiedRow* CrudModel::getModifiedRow(int row) const { auto iter = m_modifiedRowList.find(row); if (iter == m_modifiedRowList.end()) return nullptr; else return &iter->second; } CrudModel::PKeyValues CrudModel::getPKeyForRow(int row) const { PKeyValues values; values.reserve(m_primaryKey->fkey.size()); auto mod_row = getModifiedRow(row); if (mod_row){ 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(*(mod_row->data()[col])); } } else { 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()); } } return values; } QString CrudModel::columnName(int col) const { return m_roData->getColName(col); } 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 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(); int row_number = pending_row.row(); 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 && 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(); for (auto v : row) { if (v.null()) values.push_back(Value()); else values.push_back(std::string(v.c_str())); } ModifiedRow modified_row(row_number, values); return { true, modified_row }; } } // 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) { m_modifiedRowList.insert_or_assign(iter->first, modified_row); m_pendingRowList.m_rows.erase(iter); } } return true; } bool CrudModel::submit() { return savePendingChanges(); } void CrudModel::revert() { }