diff --git a/src/ExplainTreeModelItem.h b/src/ExplainTreeModelItem.h index 47c4c01..5c28599 100644 --- a/src/ExplainTreeModelItem.h +++ b/src/ExplainTreeModelItem.h @@ -138,6 +138,7 @@ public: float planningTime = 0.f; // Triggers??? float executionTime = 0.f; + float totalRuntime = 0.f; }; diff --git a/src/PgAuthId.cpp b/src/PgAuthId.cpp new file mode 100644 index 0000000..3c873cb --- /dev/null +++ b/src/PgAuthId.cpp @@ -0,0 +1,3 @@ +#include "PgAuthId.h" + +PgAuthId::PgAuthId() = default; diff --git a/src/PgAuthId.h b/src/PgAuthId.h new file mode 100644 index 0000000..bea69e6 --- /dev/null +++ b/src/PgAuthId.h @@ -0,0 +1,32 @@ +#ifndef PGAUTHID_H +#define PGAUTHID_H + +#include +#include +#include + +class PgAuthId { +public: + PgAuthId(); + + Oid oid = InvalidOid; + QString name; + bool super; + bool inherit; + bool createRole; + bool createDB; + bool canlogin; + bool replication; + bool bypassRls; + int connLimit; + QDateTime validUntil; + + bool valid() const { return oid != InvalidOid; } + + bool operator==(Oid _oid) const { return oid == _oid; } + bool operator==(const QString &n) const { return name == n; } + bool operator<(Oid _oid) const { return oid < _oid; } + bool operator<(const PgAuthId &rhs) const { return oid < rhs.oid; } +}; + +#endif // PGAUTHID_H diff --git a/src/PgAuthIdContainer.cpp b/src/PgAuthIdContainer.cpp new file mode 100644 index 0000000..161a723 --- /dev/null +++ b/src/PgAuthIdContainer.cpp @@ -0,0 +1,45 @@ +#include "PgAuthIdContainer.h" +#include "PgsqlConn.h" +#include "PgsqlDatabaseCatalogue.h" + +PgAuthIdContainer::PgAuthIdContainer(PgsqlDatabaseCatalogue *cat) + : PgContainer(cat) +{} + +std::string PgAuthIdContainer::getLoadQuery() const +{ + std::string result = + "SELECT oid, rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, " + " rolcanlogin, rolreplication, rolconnlimit, rolvaliduntil"; + if (m_catalogue->serverVersion() >= 90500) + result += ", rolbypassrls"; + result += "\n" + "FROM pg_authid"; + return result; +} + +void PgAuthIdContainer::load(const Pgsql::Result &res) +{ + const int n_rows = res.rows(); + m_container.clear(); + m_container.reserve(n_rows); + bool with_rls = (m_catalogue->serverVersion() >= 90500); + for (auto row : res) { + PgAuthId v; + v.oid = row.get(0); // InvalidOid; + v.name = row.get(1); + v.super = row.get(2); + v.inherit = row.get(3); + v.createRole = row.get(4); + v.createDB = row.get(5); + v.canlogin = row.get(6); + v.replication = row.get(7); + v.connLimit = row.get(8); + v.validUntil = row.get(9); + v.bypassRls = with_rls ? (bool)row.get(10) : false; +// QDateTime + m_container.push_back(v); + } + std::sort(m_container.begin(), m_container.end()); +} + diff --git a/src/PgAuthIdContainer.h b/src/PgAuthIdContainer.h new file mode 100644 index 0000000..8787933 --- /dev/null +++ b/src/PgAuthIdContainer.h @@ -0,0 +1,26 @@ +#ifndef PGAUTHIDCONTAINER_H +#define PGAUTHIDCONTAINER_H + +#include +#include "PgContainer.h" +#include "PgAuthId.h" + +namespace Pgsql { + + class Result; + +} + + +class PgAuthIdContainer: public PgContainer { +public: + explicit PgAuthIdContainer(PgsqlDatabaseCatalogue *cat); + + std::string getLoadQuery() const; + void load(const Pgsql::Result &res); + +private: +}; + + +#endif // PGAUTHIDCONTAINER_H diff --git a/src/PgContainer.h b/src/PgContainer.h index bb0f058..835b848 100644 --- a/src/PgContainer.h +++ b/src/PgContainer.h @@ -5,11 +5,17 @@ #include #include +class PgsqlDatabaseCatalogue; + template class PgContainer { public: using t_Container = std::vector; ///< Do not assume it will stay a vector only expect bidirectional access + explicit PgContainer(PgsqlDatabaseCatalogue *cat) + : m_catalogue(cat) + {} + typename t_Container::const_iterator begin() const { return m_container.begin(); @@ -54,6 +60,7 @@ public: return m_container.at(idx); } protected: + PgsqlDatabaseCatalogue *m_catalogue; t_Container m_container; private: T m_invalidInstance; diff --git a/src/PgDatabaseContainer.cpp b/src/PgDatabaseContainer.cpp index 5cadd52..8f3d8a2 100644 --- a/src/PgDatabaseContainer.cpp +++ b/src/PgDatabaseContainer.cpp @@ -1,7 +1,8 @@ #include "PgDatabaseContainer.h" #include "PgsqlConn.h" -PgDatabaseContainer::PgDatabaseContainer() +PgDatabaseContainer::PgDatabaseContainer(PgsqlDatabaseCatalogue *cat) + : PgContainer(cat) {} std::string PgDatabaseContainer::getLoadQuery() const @@ -12,7 +13,7 @@ std::string PgDatabaseContainer::getLoadQuery() const void PgDatabaseContainer::load(const Pgsql::Result &res) { - const int n_rows = res.getRows(); + const int n_rows = res.rows(); m_container.clear(); m_container.reserve(n_rows); for (auto row : res) { diff --git a/src/PgDatabaseContainer.h b/src/PgDatabaseContainer.h index 13e82c7..c2b711e 100644 --- a/src/PgDatabaseContainer.h +++ b/src/PgDatabaseContainer.h @@ -14,7 +14,7 @@ namespace Pgsql { class PgDatabaseContainer: public PgContainer { public: - PgDatabaseContainer(); + explicit PgDatabaseContainer(PgsqlDatabaseCatalogue *cat); std::string getLoadQuery() const; void load(const Pgsql::Result &res); diff --git a/src/PgTypeContainer.cpp b/src/PgTypeContainer.cpp index b5d3b82..dba2c91 100644 --- a/src/PgTypeContainer.cpp +++ b/src/PgTypeContainer.cpp @@ -2,7 +2,9 @@ #include "PgsqlConn.h" #include -PgTypeContainer::PgTypeContainer() = default; +PgTypeContainer::PgTypeContainer(PgsqlDatabaseCatalogue *cat) + : PgContainer(cat) +{} //const PgType& PgTypeContainer::getTypeByOid(Oid oid) const @@ -41,7 +43,7 @@ std::string PgTypeContainer::getLoadQuery() void PgTypeContainer::load(const Pgsql::Result &res) { - const int n_rows = res.getRows(); + const int n_rows = res.rows(); m_container.clear(); m_container.reserve(n_rows); for (auto row : res) { diff --git a/src/PgTypeContainer.h b/src/PgTypeContainer.h index 675b0d1..7c5656d 100644 --- a/src/PgTypeContainer.h +++ b/src/PgTypeContainer.h @@ -15,7 +15,7 @@ class PgTypeContainer: public PgContainer { public: // using t_Types = std::vector; ///< Do not assume it will stay a vector only expect bidirectional access - PgTypeContainer(); + explicit PgTypeContainer(PgsqlDatabaseCatalogue *cat); // t_Types::const_iterator begin() const { return m_types.begin(); } // t_Types::const_iterator end() const { return m_types.end(); } diff --git a/src/PgsqlConn.cpp b/src/PgsqlConn.cpp index 66be446..fe764fe 100644 --- a/src/PgsqlConn.cpp +++ b/src/PgsqlConn.cpp @@ -5,205 +5,9 @@ using namespace Pgsql; -namespace { - - void set_stdstring_with_charptr(std::string &s, const char *p) - { - if (p) { - s = p; - } - else { - s.clear(); - } - } - -} // einde unnamed namespace - -ErrorDetails ErrorDetails::createErrorDetailsFromPGresult(const PGresult *result) -{ - - ErrorDetails r; - set_stdstring_with_charptr(r.errorMessage, PQresultErrorMessage(result)); - set_stdstring_with_charptr(r.state, PQresultErrorField(result, PG_DIAG_SQLSTATE)); ///< PG_DIAG_SQLSTATE Error code as listed in https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html - set_stdstring_with_charptr(r.severity, PQresultErrorField(result, PG_DIAG_SEVERITY)); - set_stdstring_with_charptr(r.messagePrimary, PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY)); - set_stdstring_with_charptr(r.messageDetail, PQresultErrorField(result, PG_DIAG_MESSAGE_DETAIL)); - set_stdstring_with_charptr(r.messageHint, PQresultErrorField(result, PG_DIAG_MESSAGE_HINT)); - const char * p = PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION); - r.statementPosition = p != nullptr ? atoi(p) : -1; ///< First character is one, measured in characters not bytes! - p = PQresultErrorField(result, PG_DIAG_INTERNAL_POSITION); - r.internalPosition = p != nullptr ? atoi(p) : -1; - set_stdstring_with_charptr(r.internalQuery, PQresultErrorField(result, PG_DIAG_INTERNAL_QUERY)); - set_stdstring_with_charptr(r.context, PQresultErrorField(result, PG_DIAG_CONTEXT)); - set_stdstring_with_charptr(r.schemaName, PQresultErrorField(result, PG_DIAG_SCHEMA_NAME)); - set_stdstring_with_charptr(r.tableName, PQresultErrorField(result, PG_DIAG_TABLE_NAME)); - set_stdstring_with_charptr(r.columnName, PQresultErrorField(result, PG_DIAG_COLUMN_NAME)); - set_stdstring_with_charptr(r.datatypeName, PQresultErrorField(result, PG_DIAG_DATATYPE_NAME)); - set_stdstring_with_charptr(r.constraintName, PQresultErrorField(result, PG_DIAG_CONSTRAINT_NAME)); - set_stdstring_with_charptr(r.sourceFile, PQresultErrorField(result, PG_DIAG_SOURCE_FILE)); - set_stdstring_with_charptr(r.sourceLine, PQresultErrorField(result, PG_DIAG_SOURCE_LINE)); - set_stdstring_with_charptr(r.sourceFunction, PQresultErrorField(result, PG_DIAG_SOURCE_FUNCTION)); - return r; -} -bool Row::next() -{ - if (m_row < m_result.getRows()) { - ++m_row; - return true; - } - return false; -} - -Value Row::get(int col) const -{ - return m_result.get(col, m_row); -} - -//Value Row::get(const char *colname) const -//{ - -//} - -Result::Result(PGresult *res) - : result(res) -{ - if (res == nullptr) { - throw std::runtime_error("Passing nullptr to Result::Result is not allowed"); - } -} - -Result::~Result() -{ - PQclear(result); -} - - -Result::Result(Result &&rhs) - : result(rhs.result) -{ - rhs.result = nullptr; -} - -Result& Result::operator=(Result &&rhs) -{ - if (result) { - PQclear(result); - } - result = rhs.result; - rhs.result = nullptr; - return *this; -} - -Result::operator bool() const -{ - return result != nullptr; -} - -ExecStatusType Result::resultStatus() -{ - return PQresultStatus(result); -} - -std::string Result::getResStatus() -{ -// return PQresStatus(result); - return ""; -} - -std::string Result::diagSqlState() -{ - std::string s(PQresultErrorField(result, PG_DIAG_SQLSTATE)); - return s; -} - - -ErrorDetails Result::diagDetails() -{ -// ErrorDetails r; -// r.state = PQresultErrorField(result, PG_DIAG_SQLSTATE); ///< PG_DIAG_SQLSTATE Error code as listed in https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html -// r.severity = PQresultErrorField(result, PG_DIAG_SEVERITY); -// r.messagePrimary = PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY); -// r.messageDetail = PQresultErrorField(result, PG_DIAG_MESSAGE_DETAIL); -// r.messageHint = PQresultErrorField(result, PG_DIAG_MESSAGE_HINT); -// r.statementPosition = atoi(PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION)); ///< First character is one, measured in characters not bytes! -// r.internalPosition = atoi(PQresultErrorField(result, PG_DIAG_INTERNAL_POSITION)); -// r.internalQuery = PQresultErrorField(result, PG_DIAG_INTERNAL_QUERY); -// r.context = PQresultErrorField(result, PG_DIAG_CONTEXT); -// r.schemaName = PQresultErrorField(result, PG_DIAG_SCHEMA_NAME); -// r.tableName = PQresultErrorField(result, PG_DIAG_TABLE_NAME); -// r.columnName = PQresultErrorField(result, PG_DIAG_COLUMN_NAME); -// r.datatypeName = PQresultErrorField(result, PG_DIAG_DATATYPE_NAME); -// r.constraintName = PQresultErrorField(result, PG_DIAG_CONSTRAINT_NAME); -// r.sourceFile = PQresultErrorField(result, PG_DIAG_SOURCE_FILE); -// r.sourceLine = PQresultErrorField(result, PG_DIAG_SOURCE_LINE); -// r.sourceFunction = PQresultErrorField(result, PG_DIAG_SOURCE_FUNCTION); - -// return r; - return ErrorDetails::createErrorDetailsFromPGresult(result); -} - -int Result::tuplesAffected() const -{ - int i; - char * res = PQcmdTuples(result); - if (res) { - try { - i = std::stoi(res); - } - catch (std::invalid_argument& ) { - i = -1; - } - catch (std::out_of_range& ) { - i = -1; - } - } - else { - i = -1; - } - return i; -} - -int Result::getRows() const -{ - return PQntuples(result); -} - -int Result::getCols() const -{ - return PQnfields(result); -} - -const char* const Result::getColName(int idx) const -{ - return PQfname(result, idx); -} - -const char * Result::getVal(int col, int row) const -{ - return PQgetvalue(result, row, col); -} - -Value Result::get(int col, int row) const -{ - return Value( - PQgetvalue(result, row, col), - PQftype(result, col) - ); -} - -Oid Result::type(int col) const -{ - return PQftype(result, col); -} - -bool Result::null(int col, int row) const -{ - return PQgetisnull(result, row, col); -} - Canceller::Canceller(PGcancel *c) : m_cancel(c) {} diff --git a/src/PgsqlConn.h b/src/PgsqlConn.h index 0b07ac6..7288762 100644 --- a/src/PgsqlConn.h +++ b/src/PgsqlConn.h @@ -12,6 +12,8 @@ #include +#include "Pgsql_Result.h" + namespace Pgsql { class Connection; @@ -36,213 +38,12 @@ namespace Pgsql { // }; - class ErrorDetails { - public: - static ErrorDetails createErrorDetailsFromPGresult(const PGresult *res); - - std::string errorMessage; - std::string state; ///< PG_DIAG_SQLSTATE Error code as listed in https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html - std::string severity; - std::string messagePrimary; - std::string messageDetail; - std::string messageHint; - int statementPosition; ///< First character is one, measured in characters not bytes! - int internalPosition; - std::string internalQuery; - std::string context; - std::string schemaName; - std::string tableName; - std::string columnName; - std::string datatypeName; - std::string constraintName; - std::string sourceFile; - std::string sourceLine; - std::string sourceFunction; - }; - - /** \brief Class that is returned as value of a cell to facilitate auto conversion. - */ - class Value { - public: - Value(const char *val, Oid typ) - : m_val(val), m_typ(typ) - {} - - QString asQString() const - { - return QString::fromUtf8(m_val); - } - - operator QString() const - { - return QString::fromUtf8(m_val); - } - - operator std::string() const - { - return m_val; - } - - operator short() const - { - return (short)std::atoi(m_val); - } - - operator int() const - { - return std::atoi(m_val); - } - - operator Oid() const - { - return operator int(); - } - - operator __int64() const - { - return strtoull(m_val, nullptr, 10); - } - - operator bool() const - { - return strcmp(m_val, "t") == 0; - } - - private: - const char *m_val; - Oid m_typ; - - }; class Result; - /** \brief A reference to a specific row from a result. - * - * As it is a reference its contents won't be valid after its associated result has - * been destroyed. - */ - class Row { - public: - Row(const Result &result, int row) - : m_result(result) - , m_row(row) - {} - - bool next(); - bool operator==(const Row& rhs) - { - return &m_result == &rhs.m_result - && m_row == rhs.m_row; - } - - Value get(int col) const; - //Value get(const char *colname) const; - //bool get(int col, QString &s); - private: - const Result& m_result; - int m_row; - }; - - /** \brief Non-copyable but movable wrapper for a postgresql result. - * - * This class makes sure the result is removed from memory. It also supplies an iterator for the - * rows from the result facilitating also the use of the cpp for each. - */ - class Result { - public: - class const_iterator { - public: - const_iterator(const Result &r, int rw) - : m_row(r, rw) - {} - - const_iterator operator++() - { - const_iterator t(*this); - m_row.next(); - return t; - } - - const_iterator& operator++(int) - { - m_row.next(); - return *this; - } - - bool operator==(const const_iterator &rhs) - { - return m_row == rhs.m_row; - } - - bool operator!=(const const_iterator &rhs) - { - return !operator==(rhs); - } - - const Row& operator*() - { - return m_row; - } - const Row& operator->() - { - return m_row; - } - - private: - Row m_row; - }; - - Result() = default; - Result(PGresult *result); - ~Result(); - - Result(const Result &rhs) = delete; - Result& operator=(const Result &rhs) = delete; - - Result(Result &&rhs); - Result& operator=(Result &&rhs); - - operator bool() const; - - ExecStatusType resultStatus(); - - std::string getResStatus(); - - /** Use this to retrieve an error code when this is an error result - * - * The possible code are listed in https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html - */ - std::string diagSqlState(); - /** Retrieves all the error fields. */ - ErrorDetails diagDetails(); - - const_iterator begin() const - { - return const_iterator(*this, 0); - } - - const_iterator end() const - { - return const_iterator(*this, getRows()); - } - - int tuplesAffected() const; - int getRows() const; - int getCols() const; - - const char* const getColName(int idx) const; - - const char * getVal(int col, int row) const; - Value get(int col, int row) const; - Oid type(int col) const; - bool null(int col, int row) const; -// iterator begin(); - private: - PGresult *result = nullptr; - }; /** \brief Wrapper for a cancel object from libpq. diff --git a/src/PgsqlDatabaseCatalogue.cpp b/src/PgsqlDatabaseCatalogue.cpp index 9b71293..45a6ae8 100644 --- a/src/PgsqlDatabaseCatalogue.cpp +++ b/src/PgsqlDatabaseCatalogue.cpp @@ -1,8 +1,23 @@ #include "PgsqlDatabaseCatalogue.h" #include "PgTypeContainer.h" #include "PgDatabaseContainer.h" +#include "PgAuthIdContainer.h" #include "PgsqlConn.h" + +QString getRoleNameFromOid(const PgsqlDatabaseCatalogue *cat, Oid oid) +{ + QString name; + const PgAuthIdContainer *auth_ids = cat->authIds(); + if (auth_ids) { + const PgAuthId& auth_id = auth_ids->getByOid(oid); + if (auth_id.valid()) { + name = auth_id.name; + } + } + return name; +} + PgsqlDatabaseCatalogue::PgsqlDatabaseCatalogue() { } @@ -16,28 +31,71 @@ void PgsqlDatabaseCatalogue::loadAll(Pgsql::Connection &conn) { loadTypes(conn); loadDatabases(conn); + loadAuthIds(conn); +} + +void PgsqlDatabaseCatalogue::loadInfo(Pgsql::Connection &conn) +{ + Pgsql::Result r = conn.query("SHOW server_version_num"); + if (r && r.resultStatus() == PGRES_TUPLES_OK) + if (r.rows() == 1) + m_serverVersion = r.get(0, 0); + + r = conn.query("SELECT version()"); + if (r && r.resultStatus() == PGRES_TUPLES_OK) + if (r.rows() == 1) + m_serverVersionString = r.get(0, 0); } void PgsqlDatabaseCatalogue::loadTypes(Pgsql::Connection &conn) { - if (m_types == nullptr) { - m_types = new PgTypeContainer; - } + if (m_types == nullptr) + m_types = new PgTypeContainer(this); std::string q = m_types->getLoadQuery(); Pgsql::Result result = conn.query(q.c_str()); - m_types->load(result); + if (result && result.resultStatus() == PGRES_TUPLES_OK) + m_types->load(result); + else + throw std::runtime_error("Query failed"); + } void PgsqlDatabaseCatalogue::loadDatabases(Pgsql::Connection &conn) { - if (m_databases == nullptr) { - m_databases = new PgDatabaseContainer; - } + if (m_databases == nullptr) + m_databases = new PgDatabaseContainer(this); + std::string q = m_databases->getLoadQuery(); Pgsql::Result result = conn.query(q.c_str()); - m_databases->load(result); + if (result && result.resultStatus() == PGRES_TUPLES_OK) + m_databases->load(result); + else + throw std::runtime_error("Query failed"); +} + +void PgsqlDatabaseCatalogue::loadAuthIds(Pgsql::Connection &conn) +{ + if (m_authIds == nullptr) + m_authIds = new PgAuthIdContainer(this); + + std::string q = m_authIds->getLoadQuery(); + Pgsql::Result result = conn.query(q.c_str()); + if (result && result.resultStatus() == PGRES_TUPLES_OK) + m_authIds->load(result); + else + throw std::runtime_error("Query failed"); +} + +const QString& PgsqlDatabaseCatalogue::serverVersionString() const +{ + return m_serverVersionString; +} + +int PgsqlDatabaseCatalogue::serverVersion() const +{ + return m_serverVersion; } const PgTypeContainer* PgsqlDatabaseCatalogue::types() const @@ -49,3 +107,8 @@ const PgDatabaseContainer *PgsqlDatabaseCatalogue::databases() const { return m_databases; } + +const PgAuthIdContainer *PgsqlDatabaseCatalogue::authIds() const +{ + return m_authIds; +} diff --git a/src/PgsqlDatabaseCatalogue.h b/src/PgsqlDatabaseCatalogue.h index bcf5cd7..0fa8162 100644 --- a/src/PgsqlDatabaseCatalogue.h +++ b/src/PgsqlDatabaseCatalogue.h @@ -1,6 +1,8 @@ #ifndef PGSQLDATABASECATALOGUE_H #define PGSQLDATABASECATALOGUE_H +#include +#include #include namespace Pgsql { @@ -11,6 +13,7 @@ namespace Pgsql { class PgTypeContainer; class PgDatabaseContainer; +class PgAuthIdContainer; class PgsqlDatabaseCatalogue { public: @@ -22,14 +25,25 @@ public: void loadAll(Pgsql::Connection &conn); + void loadInfo(Pgsql::Connection &conn); void loadTypes(Pgsql::Connection &conn); void loadDatabases(Pgsql::Connection &conn); + void loadAuthIds(Pgsql::Connection &conn); + + const QString& serverVersionString() const; + int serverVersion() const; const PgTypeContainer* types() const; const PgDatabaseContainer *databases() const; + const PgAuthIdContainer *authIds() const; private: + QString m_serverVersionString; + int m_serverVersion; PgTypeContainer *m_types = nullptr; PgDatabaseContainer *m_databases = nullptr; + PgAuthIdContainer *m_authIds = nullptr; }; +QString getRoleNameFromOid(const PgsqlDatabaseCatalogue *cat, Oid oid); + #endif // PGSQLDATABASECATALOGUE_H diff --git a/src/Pgsql_Result.cpp b/src/Pgsql_Result.cpp new file mode 100644 index 0000000..800386b --- /dev/null +++ b/src/Pgsql_Result.cpp @@ -0,0 +1,182 @@ +#include "Pgsql_Result.h" + +using namespace Pgsql; + +namespace { + + void set_stdstring_with_charptr(std::string &s, const char *p) + { + if (p) { + s = p; + } + else { + s.clear(); + } + } + +} // einde unnamed namespace + +ErrorDetails ErrorDetails::createErrorDetailsFromPGresult(const PGresult *result) +{ + + ErrorDetails r; + set_stdstring_with_charptr(r.errorMessage, PQresultErrorMessage(result)); + set_stdstring_with_charptr(r.state, PQresultErrorField(result, PG_DIAG_SQLSTATE)); ///< PG_DIAG_SQLSTATE Error code as listed in https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html + set_stdstring_with_charptr(r.severity, PQresultErrorField(result, PG_DIAG_SEVERITY)); + set_stdstring_with_charptr(r.messagePrimary, PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY)); + set_stdstring_with_charptr(r.messageDetail, PQresultErrorField(result, PG_DIAG_MESSAGE_DETAIL)); + set_stdstring_with_charptr(r.messageHint, PQresultErrorField(result, PG_DIAG_MESSAGE_HINT)); + const char * p = PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION); + r.statementPosition = p != nullptr ? atoi(p) : -1; ///< First character is one, measured in characters not bytes! + p = PQresultErrorField(result, PG_DIAG_INTERNAL_POSITION); + r.internalPosition = p != nullptr ? atoi(p) : -1; + set_stdstring_with_charptr(r.internalQuery, PQresultErrorField(result, PG_DIAG_INTERNAL_QUERY)); + set_stdstring_with_charptr(r.context, PQresultErrorField(result, PG_DIAG_CONTEXT)); + set_stdstring_with_charptr(r.schemaName, PQresultErrorField(result, PG_DIAG_SCHEMA_NAME)); + set_stdstring_with_charptr(r.tableName, PQresultErrorField(result, PG_DIAG_TABLE_NAME)); + set_stdstring_with_charptr(r.columnName, PQresultErrorField(result, PG_DIAG_COLUMN_NAME)); + set_stdstring_with_charptr(r.datatypeName, PQresultErrorField(result, PG_DIAG_DATATYPE_NAME)); + set_stdstring_with_charptr(r.constraintName, PQresultErrorField(result, PG_DIAG_CONSTRAINT_NAME)); + set_stdstring_with_charptr(r.sourceFile, PQresultErrorField(result, PG_DIAG_SOURCE_FILE)); + set_stdstring_with_charptr(r.sourceLine, PQresultErrorField(result, PG_DIAG_SOURCE_LINE)); + set_stdstring_with_charptr(r.sourceFunction, PQresultErrorField(result, PG_DIAG_SOURCE_FUNCTION)); + return r; +} + + +Result::Result(PGresult *res) + : result(res) +{ + if (res == nullptr) { + throw std::runtime_error("Passing nullptr to Result::Result is not allowed"); + } +} + +Result::~Result() +{ + PQclear(result); +} + + +Result::Result(Result &&rhs) + : result(rhs.result) +{ + rhs.result = nullptr; +} + +Result& Result::operator=(Result &&rhs) +{ + if (result) { + PQclear(result); + } + result = rhs.result; + rhs.result = nullptr; + return *this; +} + +Result::operator bool() const +{ + return result != nullptr; +} + +ExecStatusType Result::resultStatus() +{ + return PQresultStatus(result); +} + +std::string Result::getResStatus() +{ +// return PQresStatus(result); + return ""; +} + +std::string Result::diagSqlState() +{ + std::string s(PQresultErrorField(result, PG_DIAG_SQLSTATE)); + return s; +} + + +ErrorDetails Result::diagDetails() +{ +// ErrorDetails r; +// r.state = PQresultErrorField(result, PG_DIAG_SQLSTATE); ///< PG_DIAG_SQLSTATE Error code as listed in https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html +// r.severity = PQresultErrorField(result, PG_DIAG_SEVERITY); +// r.messagePrimary = PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY); +// r.messageDetail = PQresultErrorField(result, PG_DIAG_MESSAGE_DETAIL); +// r.messageHint = PQresultErrorField(result, PG_DIAG_MESSAGE_HINT); +// r.statementPosition = atoi(PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION)); ///< First character is one, measured in characters not bytes! +// r.internalPosition = atoi(PQresultErrorField(result, PG_DIAG_INTERNAL_POSITION)); +// r.internalQuery = PQresultErrorField(result, PG_DIAG_INTERNAL_QUERY); +// r.context = PQresultErrorField(result, PG_DIAG_CONTEXT); +// r.schemaName = PQresultErrorField(result, PG_DIAG_SCHEMA_NAME); +// r.tableName = PQresultErrorField(result, PG_DIAG_TABLE_NAME); +// r.columnName = PQresultErrorField(result, PG_DIAG_COLUMN_NAME); +// r.datatypeName = PQresultErrorField(result, PG_DIAG_DATATYPE_NAME); +// r.constraintName = PQresultErrorField(result, PG_DIAG_CONSTRAINT_NAME); +// r.sourceFile = PQresultErrorField(result, PG_DIAG_SOURCE_FILE); +// r.sourceLine = PQresultErrorField(result, PG_DIAG_SOURCE_LINE); +// r.sourceFunction = PQresultErrorField(result, PG_DIAG_SOURCE_FUNCTION); + +// return r; + return ErrorDetails::createErrorDetailsFromPGresult(result); +} + +int Result::tuplesAffected() const +{ + int i; + char * res = PQcmdTuples(result); + if (res) { + try { + i = std::stoi(res); + } + catch (std::invalid_argument& ) { + i = -1; + } + catch (std::out_of_range& ) { + i = -1; + } + } + else { + i = -1; + } + return i; +} + +int Result::rows() const +{ + return PQntuples(result); +} + +int Result::cols() const +{ + return PQnfields(result); +} + +const char* const Result::getColName(int idx) const +{ + return PQfname(result, idx); +} + +const char * Result::val(int col, int row) const +{ + return PQgetvalue(result, row, col); +} + +Value Result::get(int col, int row) const +{ + return Value( + PQgetvalue(result, row, col), + PQftype(result, col) + ); +} + +Oid Result::type(int col) const +{ + return PQftype(result, col); +} + +bool Result::null(int col, int row) const +{ + return PQgetisnull(result, row, col); +} diff --git a/src/Pgsql_Result.h b/src/Pgsql_Result.h new file mode 100644 index 0000000..992e3ec --- /dev/null +++ b/src/Pgsql_Result.h @@ -0,0 +1,135 @@ +#ifndef PGSQL_RESULT_H +#define PGSQL_RESULT_H + +#include "Pgsql_Row.h" + +namespace Pgsql { + + class ErrorDetails { + public: + static ErrorDetails createErrorDetailsFromPGresult(const PGresult *res); + + std::string errorMessage; + std::string state; ///< PG_DIAG_SQLSTATE Error code as listed in https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html + std::string severity; + std::string messagePrimary; + std::string messageDetail; + std::string messageHint; + int statementPosition; ///< First character is one, measured in characters not bytes! + int internalPosition; + std::string internalQuery; + std::string context; + std::string schemaName; + std::string tableName; + std::string columnName; + std::string datatypeName; + std::string constraintName; + std::string sourceFile; + std::string sourceLine; + std::string sourceFunction; + }; + + /** \brief Non-copyable but movable wrapper for a postgresql result. + * + * This class makes sure the result is removed from memory. It also supplies an iterator for the + * rows from the result facilitating also the use of the cpp for each. + */ + class Result { + public: + class const_iterator { + public: + const_iterator(const Result &r, int rw) + : m_row(r, rw) + {} + + const_iterator operator++() + { + const_iterator t(*this); + m_row.next(); + return t; + } + + const_iterator& operator++(int) + { + m_row.next(); + return *this; + } + + bool operator==(const const_iterator &rhs) + { + return m_row == rhs.m_row; + } + + bool operator!=(const const_iterator &rhs) + { + return !operator==(rhs); + } + + const Row& operator*() + { + return m_row; + } + const Row& operator->() + { + return m_row; + } + + private: + Row m_row; + }; + + Result() = default; + Result(PGresult *result); + ~Result(); + + Result(const Result &rhs) = delete; + Result& operator=(const Result &rhs) = delete; + + Result(Result &&rhs); + Result& operator=(Result &&rhs); + + operator bool() const; + + ExecStatusType resultStatus(); + + std::string getResStatus(); + + /** Use this to retrieve an error code when this is an error result + * + * The possible code are listed in https://www.postgresql.org/docs/9.5/static/errcodes-appendix.html + */ + std::string diagSqlState(); + /** Retrieves all the error fields. */ + ErrorDetails diagDetails(); + + const_iterator begin() const + { + return const_iterator(*this, 0); + } + + const_iterator end() const + { + return const_iterator(*this, rows()); + } + + int tuplesAffected() const; + int rows() const; + int cols() const; + + const char* const getColName(int idx) const; + + const char * val(int col, int row) const; + Value get(int col, int row) const; + Oid type(int col) const; + bool null(int col, int row) const; + + + // iterator begin(); + private: + PGresult *result = nullptr; + }; + + +} // end namespace Pgsql + +#endif // PGSQL_RESULT_H diff --git a/src/Pgsql_Row.cpp b/src/Pgsql_Row.cpp new file mode 100644 index 0000000..ed281ac --- /dev/null +++ b/src/Pgsql_Row.cpp @@ -0,0 +1,36 @@ +#include "Pgsql_Row.h" +#include "Pgsql_Result.h" + +using namespace Pgsql; + +Row::Row(const Result &result, int row) + : m_result(result) + , m_row(row) +{} + +bool Row::next() +{ + if (m_row < m_result.rows()) { + ++m_row; + return true; + } + return false; +} + +bool Row::operator==(const Row& rhs) const +{ + return &m_result == &rhs.m_result + && m_row == rhs.m_row; +} + + +Value Row::get(int col) const +{ + return m_result.get(col, m_row); +} + +//Value Row::get(const char *colname) const +//{ + +//} + diff --git a/src/Pgsql_Row.h b/src/Pgsql_Row.h new file mode 100644 index 0000000..f62df72 --- /dev/null +++ b/src/Pgsql_Row.h @@ -0,0 +1,31 @@ +#ifndef PGSQL_ROW_H +#define PGSQL_ROW_H + +#include "Pgsql_Value.h" + +namespace Pgsql { + + class Result; + + /** \brief A reference to a specific row from a result. + * + * As it is a reference its contents won't be valid after its associated result has + * been destroyed. + */ + class Row { + public: + Row(const Result &result, int row); + bool next(); + + bool operator==(const Row& rhs) const; + Value get(int col) const; + //Value get(const char *colname) const; + //bool get(int col, QString &s); + private: + const Result& m_result; + int m_row; + }; + +} // end namespace Pgsql + +#endif // PGSQL_ROW_H diff --git a/src/Pgsql_Value.cpp b/src/Pgsql_Value.cpp new file mode 100644 index 0000000..ff490c5 --- /dev/null +++ b/src/Pgsql_Value.cpp @@ -0,0 +1,56 @@ +#include "Pgsql_Value.h" +#include +#include + +using namespace Pgsql; + +Value::Value(const char *val, Oid typ) + : m_val(val), m_typ(typ) +{} + +QString Value::asQString() const +{ + return QString::fromUtf8(m_val); +} + +Value::operator QString() const +{ + return QString::fromUtf8(m_val); +} + +Value::operator QDateTime() const +{ + return QDateTime::fromString(asQString(), + "yyyy-MM-dd hh:mm:ss"); +} + +Value::operator std::string() const +{ + return m_val; +} + +Value::operator short() const +{ + return (short)std::atoi(m_val); +} + +Value::operator int() const +{ + return std::atoi(m_val); +} + +Value::operator Oid() const +{ + return operator int(); +} + +Value::operator __int64() const +{ + return std::strtoull(m_val, nullptr, 10); +} + +Value::operator bool() const +{ + return std::strcmp(m_val, "t") == 0; +} + diff --git a/src/Pgsql_Value.h b/src/Pgsql_Value.h new file mode 100644 index 0000000..864b623 --- /dev/null +++ b/src/Pgsql_Value.h @@ -0,0 +1,33 @@ +#ifndef PGSQL_VALUE_H +#define PGSQL_VALUE_H + +#include "Pgsql_declare.h" +#include +#include + +namespace Pgsql { + + /** \brief Class that is returned as value of a cell to facilitate auto conversion. + */ + class Value { + public: + Value(const char *val, Oid typ); + QString asQString() const; + operator QString() const; + operator QDateTime() const; + operator std::string() const; + operator short() const; + operator int() const; + operator Oid() const; + operator __int64() const; + operator bool() const; + + private: + const char *m_val; + Oid m_typ; + }; + + +} // end namespace Pgsql + +#endif // PGSQL_VALUE_H diff --git a/src/QueryResultModel.cpp b/src/QueryResultModel.cpp index 3fc45a3..623d2be 100644 --- a/src/QueryResultModel.cpp +++ b/src/QueryResultModel.cpp @@ -11,13 +11,13 @@ QueryResultModel::QueryResultModel(QObject *parent, std::shared_ptrgetRows(); + int r = result->rows(); return r; } int QueryResultModel::columnCount(const QModelIndex &) const { - int r = result->getCols(); + int r = result->cols(); return r; } @@ -34,7 +34,7 @@ QVariant QueryResultModel::data(const QModelIndex &index, int role) const } else { Oid o = result->type(col); - QString s(result->getVal(col, rij)); + QString s(result->val(col, rij)); switch (o) { case oid_bool: s = (s == "t") ? "TRUE" : "FALSE"; @@ -88,7 +88,7 @@ QVariant QueryResultModel::data(const QModelIndex &index, int role) const r = QBrush(Qt::darkGreen); break; case oid_bool: - if (strcmp(result->getVal(col, rij), "t") == 0) { + if (strcmp(result->val(col, rij), "t") == 0) { r = QBrush(Qt::darkGreen); } else { diff --git a/src/QueryTab.cpp b/src/QueryTab.cpp index 46f7cf3..3f9453d 100644 --- a/src/QueryTab.cpp +++ b/src/QueryTab.cpp @@ -212,8 +212,8 @@ void QueryTab::explain(bool analyze) std::thread([this,res]() { std::shared_ptr explain; - if (res->getCols() == 1 && res->getRows() == 1) { - std::string s = res->getVal(0, 0); + if (res->cols() == 1 && res->rows() == 1) { + std::string s = res->val(0, 0); Json::Value root; // will contains the root value after parsing. Json::Reader reader; bool parsingSuccessful = reader.parse(s, root); diff --git a/src/RolesTableModel.cpp b/src/RolesTableModel.cpp new file mode 100644 index 0000000..9744494 --- /dev/null +++ b/src/RolesTableModel.cpp @@ -0,0 +1,123 @@ +#include "RolesTableModel.h" +#include "PgAuthIdContainer.h" + +RolesTableModel::RolesTableModel(QObject *parent) + : QAbstractTableModel(parent) +{ +} + +void RolesTableModel::setRoleList(const PgAuthIdContainer* roles) +{ + beginResetModel(); + m_roles = roles; + endResetModel(); +} + +QVariant RolesTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant v; + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole) { + switch (section) { + case NameCol: + v = tr("Name"); + break; + case SuperCol: + v = tr("Super"); + break; + case InheritCol: + v = tr("Inherit"); + break; + case CreateRoleCol: + v = tr("Create role"); + break; + case CreateDBCol: + v = tr("Create DB"); + break; + case CanLoginCol: + v = tr("Can login"); + break; + case ReplicationCol: + v = tr("Replication"); + break; + case BypassRlsCol: + v = tr("Bypass RLS"); + break; + case ConnlimitCol: + v = tr("Connection limit"); + break; + case ValidUntilCol: + v = tr("Valid until"); + break; + } + } + } + + return v; +} + +int RolesTableModel::rowCount(const QModelIndex &parent) const +{ + int result = 0; + if (m_roles) { + result = m_roles->count(); + } + return result; +} + +int RolesTableModel::columnCount(const QModelIndex &parent) const +{ + int result = 10; +// if (parent.isValid()) +// return 10; + + return result; +} + +QVariant RolesTableModel::data(const QModelIndex &index, int role) const +{ + QVariant v; + //if (!index.isValid()) + if (m_roles) { + const PgAuthId &authid = m_roles->getByIdx(index.row()); + if (role == Qt::DisplayRole) { + switch (index.column()) { + case NameCol: + v = authid.name; + break; + case SuperCol: + // todo lookup role name + v = authid.super; + break; + case InheritCol: + // todo lookup encoding name + v = authid.inherit; + break; + case CreateRoleCol: + v = authid.createRole; + break; + case CreateDBCol: + v = authid.createDB; + break; + case CanLoginCol: + v = authid.canlogin; + break; + case ReplicationCol: + v = authid.replication; + break; + case BypassRlsCol: + v = authid.bypassRls; + break; + case ConnlimitCol: + // todo lookup tablespace name + v = authid.connLimit; + break; + case ValidUntilCol: + v = authid.validUntil; + break; + } + } + } + + return v; +} diff --git a/src/RolesTableModel.h b/src/RolesTableModel.h new file mode 100644 index 0000000..0da4375 --- /dev/null +++ b/src/RolesTableModel.h @@ -0,0 +1,39 @@ +#ifndef ROLESTABLEMODEL_H +#define ROLESTABLEMODEL_H + + +#include + +class PgAuthIdContainer; + +/** Class for displaying the list of roles of a server in a QTableView + * + */ +class RolesTableModel : public QAbstractTableModel { + Q_OBJECT +public: + enum e_Columns : int { NameCol, SuperCol, InheritCol, CreateRoleCol, + CreateDBCol, CanLoginCol, ReplicationCol, + BypassRlsCol, ConnlimitCol, ValidUntilCol }; + + + + explicit RolesTableModel(QObject *parent); + + void setRoleList(const PgAuthIdContainer* roles); + + // Header: + 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; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +private: + const PgAuthIdContainer *m_roles = nullptr; +}; + + +#endif // ROLESTABLEMODEL_H diff --git a/src/ServerWindow.cpp b/src/ServerWindow.cpp index 759adbe..53dcf02 100644 --- a/src/ServerWindow.cpp +++ b/src/ServerWindow.cpp @@ -2,6 +2,7 @@ #include "ui_ServerWindow.h" #include "OpenDatabase.h" #include "DatabasesTableModel.h" +#include "RolesTableModel.h" #include "PgsqlDatabaseCatalogue.h" ServerWindow::ServerWindow(MasterController *master, QWidget *parent) @@ -12,7 +13,10 @@ ServerWindow::ServerWindow(MasterController *master, QWidget *parent) ui->setupUi(this); m_databasesModel = new DatabasesTableModel(this); - ui->tableView->setModel(m_databasesModel); + ui->databasesTableView->setModel(m_databasesModel); + + m_rolesModel = new RolesTableModel(this); + ui->rolesTableView->setModel(m_rolesModel); } ServerWindow::~ServerWindow() @@ -29,6 +33,7 @@ void ServerWindow::setConfig(const ConnectionConfig &config) auto cat = m_database->catalogue(); if (cat) { m_databasesModel->setDatabaseList(cat->databases()); + m_rolesModel->setRoleList(cat->authIds()); } } QString title = "pglab - "; diff --git a/src/ServerWindow.h b/src/ServerWindow.h index 35e6b21..038315b 100644 --- a/src/ServerWindow.h +++ b/src/ServerWindow.h @@ -11,6 +11,7 @@ class ServerWindow; class MasterController; class OpenDatabase; class DatabasesTableModel; +class RolesTableModel; class ServerWindow : public ASyncWindow { Q_OBJECT @@ -22,10 +23,11 @@ public: private: Ui::ServerWindow *ui; - MasterController *m_masterController; + MasterController *m_masterController = nullptr; ConnectionConfig m_config; - OpenDatabase *m_database; - DatabasesTableModel *m_databasesModel; + OpenDatabase *m_database = nullptr; + DatabasesTableModel *m_databasesModel = nullptr; + RolesTableModel *m_rolesModel = nullptr; }; #endif // SERVERWINDOW_H diff --git a/src/ServerWindow.ui b/src/ServerWindow.ui index d9a0adb..42988d9 100644 --- a/src/ServerWindow.ui +++ b/src/ServerWindow.ui @@ -30,7 +30,7 @@ - 0 + 2 @@ -50,7 +50,7 @@ 0 - + true @@ -82,7 +82,7 @@ 0 - + true @@ -114,7 +114,7 @@ 0 - + true diff --git a/src/src.pro b/src/src.pro index 05ef5cc..db4abd5 100644 --- a/src/src.pro +++ b/src/src.pro @@ -59,7 +59,13 @@ SOURCES += main.cpp\ DatabasesTableModel.cpp \ PgDatabase.cpp \ PgDatabaseContainer.cpp \ - Pgsql_Params.cpp + Pgsql_Params.cpp \ + RolesTableModel.cpp \ + PgAuthId.cpp \ + PgAuthIdContainer.cpp \ + Pgsql_Result.cpp \ + Pgsql_Row.cpp \ + Pgsql_Value.cpp HEADERS += \ sqlparser.h \ @@ -105,7 +111,13 @@ HEADERS += \ PgDatabaseContainer.h \ PgContainer.h \ Pgsql_Params.h \ - Pgsql_declare.h + Pgsql_declare.h \ + RolesTableModel.h \ + PgAuthId.h \ + PgAuthIdContainer.h \ + Pgsql_Result.h \ + Pgsql_Row.h \ + Pgsql_Value.h FORMS += mainwindow.ui \ DatabaseWindow.ui \ @@ -119,4 +131,4 @@ FORMS += mainwindow.ui \ RESOURCES += \ resources.qrc -QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01 +#QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01