#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; m_dbConn.setupConnection(m_database->config()); } 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) { // int row = index.row(); // if (m_pendingChanges.needsToSave(row)) { //// if (savePendingChanges()) //// startEditingRow(); // } 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()); 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) { if (value) v = *value == "t"; //s = (s == "t") ? "TRUE" : "FALSE"; else v = "null"; } else { QString s = QString::fromUtf8(value->c_str()); if (s.length() > 256) { s.truncate(256); } v = s; } } else { v = "null"; } } 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) { // WorkManager::getWorkManager()->addWork( // [dbres, this] () -> void { // std::shared_ptr rl = resultToRowList(dbres); // m_asyncWindow->QueueTask([this, rl]() { loadIntoModel(rl); }); // }); 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); } void CrudModel::updateRow(const PendingRow &pending_row) { auto pkey_values = getPKeyForRow(pending_row.row()); QString buffer; QTextStream q(&buffer); q << "UPDATE "; q << genFQTableName(*m_database->catalogue(), m_table); q << " AS d\n SET "; auto data = pending_row.data(); Pgsql::Params params; if (!data.empty()) { int param = 0; for (auto e : data) { if (param > 0) q << ","; q << quoteIdent(columnName(e.first)) << "=$" << ++param; if (e.second) params.add(e.second->c_str(), getType(e.first)); else params.add(nullptr, 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 params.add(pkey_values[i].c_str(), getType(col)); if (i > 0) q << " AND "; q << quoteIdent(columnName(col)) << "=$" << ++param; ++i; } q << "\nRETURNING *"; q.flush(); m_dbConn.send(buffer.toUtf8().data(), params, [this] (Expected> result, qint64) { } ); } } 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; // for (const auto& row : m_pendingRowList) { // int row_number = row.first; // if (row_number >= 0) { //// update // } updateRow(row.second); } m_pendingRowList.clear(); return true; } bool CrudModel::submit() { return savePendingChanges(); } void CrudModel::revert() { }