From e32c82ac6fae3e0b5430793ea33fcb406292dfc8 Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 9 Dec 2018 20:24:11 +0100 Subject: [PATCH] Created Pgsql::Transaction class for handling of transactions. It auto rollsback if no commit has been done. Also moved some code out of the connection files to their own files. --- pgsql/Pgsql_Canceller.cpp | 43 +++++++++ pgsql/Pgsql_Canceller.h | 28 ++++++ pgsql/Pgsql_Col.cpp | 2 - pgsql/Pgsql_Connection.cpp | 43 --------- pgsql/Pgsql_Connection.h | 20 +--- pgsql/Pgsql_ErrorDetails.cpp | 46 +++++++++ pgsql/Pgsql_ErrorDetails.h | 35 +++++++ pgsql/Pgsql_Result.cpp | 40 -------- pgsql/Pgsql_Result.h | 25 +---- pgsql/Pgsql_Transaction.cpp | 178 +++++++++++++++++++++++++++++++++++ pgsql/Pgsql_Transaction.h | 53 +++++++++++ pgsql/pgsql.pro | 11 ++- 12 files changed, 394 insertions(+), 130 deletions(-) create mode 100644 pgsql/Pgsql_Canceller.cpp create mode 100644 pgsql/Pgsql_Canceller.h delete mode 100644 pgsql/Pgsql_Col.cpp create mode 100644 pgsql/Pgsql_ErrorDetails.cpp create mode 100644 pgsql/Pgsql_ErrorDetails.h create mode 100644 pgsql/Pgsql_Transaction.cpp create mode 100644 pgsql/Pgsql_Transaction.h diff --git a/pgsql/Pgsql_Canceller.cpp b/pgsql/Pgsql_Canceller.cpp new file mode 100644 index 0000000..aa93b7d --- /dev/null +++ b/pgsql/Pgsql_Canceller.cpp @@ -0,0 +1,43 @@ +#include "Pgsql_Canceller.h" + +namespace Pgsql { + + Canceller::Canceller(PGcancel *c) + : m_cancel(c) + {} + + Canceller::Canceller(Canceller&& rhs) + : m_cancel(rhs.m_cancel) + { + rhs.m_cancel = nullptr; + } + + Canceller& Canceller::operator=(Canceller&& rhs) + { + if (m_cancel) { + PQfreeCancel(m_cancel); + } + m_cancel = rhs.m_cancel; + rhs.m_cancel = nullptr; + return *this; + } + + Canceller::~Canceller() + { + if (m_cancel) { + PQfreeCancel(m_cancel); + } + } + + bool Canceller::cancel(std::string *error) + { + const int errbuf_size = 256; + char errbuf[errbuf_size]; + bool res = PQcancel(m_cancel, errbuf, errbuf_size); + if (!res && error) { + *error = errbuf; + } + return res; + } + +} diff --git a/pgsql/Pgsql_Canceller.h b/pgsql/Pgsql_Canceller.h new file mode 100644 index 0000000..34fb7de --- /dev/null +++ b/pgsql/Pgsql_Canceller.h @@ -0,0 +1,28 @@ +#ifndef PGSQL_CANCELLER_H +#define PGSQL_CANCELLER_H + +#include +#include + +namespace Pgsql { + + /** \brief Wrapper for a cancel object from libpq. + */ + class Canceller { + public: + Canceller() = default; + Canceller(PGcancel *c); + Canceller(const Canceller&) = delete; + Canceller& operator=(const Canceller&) = delete; + Canceller(Canceller&& rhs); + Canceller& operator=(Canceller&& rhs); + ~Canceller(); + + bool cancel(std::string *error); + private: + PGcancel *m_cancel = nullptr; + }; + +} + +#endif // PGSQL_CANCELLER_H diff --git a/pgsql/Pgsql_Col.cpp b/pgsql/Pgsql_Col.cpp deleted file mode 100644 index 47c64a1..0000000 --- a/pgsql/Pgsql_Col.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "Pgsql_Col.h" - diff --git a/pgsql/Pgsql_Connection.cpp b/pgsql/Pgsql_Connection.cpp index db22689..fef9b64 100644 --- a/pgsql/Pgsql_Connection.cpp +++ b/pgsql/Pgsql_Connection.cpp @@ -7,49 +7,6 @@ using namespace Pgsql; - - - -Canceller::Canceller(PGcancel *c) - : m_cancel(c) -{} - -Canceller::Canceller(Canceller&& rhs) - : m_cancel(rhs.m_cancel) -{ - rhs.m_cancel = nullptr; -} - -Canceller& Canceller::operator=(Canceller&& rhs) -{ - if (m_cancel) { - PQfreeCancel(m_cancel); - } - m_cancel = rhs.m_cancel; - rhs.m_cancel = nullptr; - return *this; -} - -Canceller::~Canceller() -{ - if (m_cancel) { - PQfreeCancel(m_cancel); - } -} - -bool Canceller::cancel(std::string *error) -{ - const int errbuf_size = 256; - char errbuf[errbuf_size]; - bool res = PQcancel(m_cancel, errbuf, errbuf_size); - if (!res && error) { - *error = errbuf; - } - return res; -} - - - Connection::Connection() = default; Connection::~Connection() diff --git a/pgsql/Pgsql_Connection.h b/pgsql/Pgsql_Connection.h index bbe34de..4f564ac 100644 --- a/pgsql/Pgsql_Connection.h +++ b/pgsql/Pgsql_Connection.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -12,6 +11,7 @@ #include +#include "Pgsql_Canceller.h" #include "Pgsql_Result.h" namespace Pgsql { @@ -28,23 +28,6 @@ namespace Pgsql { class Result; class Params; - /** \brief Wrapper for a cancel object from libpq. - */ - class Canceller { - public: - Canceller() = default; - Canceller(PGcancel *c); - Canceller(const Canceller&) = delete; - Canceller& operator=(const Canceller&) = delete; - Canceller(Canceller&& rhs); - Canceller& operator=(Canceller&& rhs); - ~Canceller(); - - bool cancel(std::string *error); - private: - PGcancel *m_cancel = nullptr; - }; - /** \brief Class for connecting to the database. * * The class isolates the programmer from the worst C style parts @@ -87,6 +70,7 @@ namespace Pgsql { int socket(); void close(); + Canceller getCancel(); std::string getErrorMessage() const; diff --git a/pgsql/Pgsql_ErrorDetails.cpp b/pgsql/Pgsql_ErrorDetails.cpp new file mode 100644 index 0000000..302ad3f --- /dev/null +++ b/pgsql/Pgsql_ErrorDetails.cpp @@ -0,0 +1,46 @@ +#include "Pgsql_ErrorDetails.h" + +namespace { + + void set_stdstring_with_charptr(std::string &s, const char *p) + { + if (p) { + s = p; + } + else { + s.clear(); + } + } + +} // einde unnamed namespace + +namespace Pgsql { + + 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; + } + +} diff --git a/pgsql/Pgsql_ErrorDetails.h b/pgsql/Pgsql_ErrorDetails.h new file mode 100644 index 0000000..9a6a0f7 --- /dev/null +++ b/pgsql/Pgsql_ErrorDetails.h @@ -0,0 +1,35 @@ +#ifndef PGSQL_ERRORDETAILS_H +#define PGSQL_ERRORDETAILS_H + +#include +#include + +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; + }; + +} + +#endif // PGSQL_ERRORDETAILS_H diff --git a/pgsql/Pgsql_Result.cpp b/pgsql/Pgsql_Result.cpp index 8ef7bcf..32e7cc3 100644 --- a/pgsql/Pgsql_Result.cpp +++ b/pgsql/Pgsql_Result.cpp @@ -2,46 +2,6 @@ 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) diff --git a/pgsql/Pgsql_Result.h b/pgsql/Pgsql_Result.h index 6945d91..c2a45e4 100644 --- a/pgsql/Pgsql_Result.h +++ b/pgsql/Pgsql_Result.h @@ -2,33 +2,10 @@ #define PGSQL_RESULT_H #include "Pgsql_Row.h" +#include "Pgsql_ErrorDetails.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 diff --git a/pgsql/Pgsql_Transaction.cpp b/pgsql/Pgsql_Transaction.cpp new file mode 100644 index 0000000..89d8cde --- /dev/null +++ b/pgsql/Pgsql_Transaction.cpp @@ -0,0 +1,178 @@ +#include "Pgsql_Transaction.h" + +#include "Pgsql_Connection.h" +#include + +namespace Pgsql { + + Transaction Transaction::startTransaction(Connection &conn) + { + conn.query("BEGIN"); + return Transaction(&conn); + } + + Transaction::~Transaction() + { + if (m_conn && !m_committed && !m_rolledback) { + m_conn->query("ROLLBACK"); + } + } + + void Transaction::rollback() + { + m_conn->query("ROLLBACK"); + m_rolledback = true; + } + + void Transaction::commit() + { + m_conn->query("COMMIT"); + m_committed = true; + } + + Canceller Transaction::getCancel() + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->getCancel(); + } + + std::string Transaction::getErrorMessage() const + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->getErrorMessage(); + } + + Result Transaction::query(const char * command) + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->query(command); + } + + Result Transaction::query(const QString &command) + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->query(command); + } + + Result Transaction::queryParam(const char * command, const Params ¶ms) + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->queryParam(command, params); + } + + Result Transaction::queryParam(const QString &command, const Params ¶ms) + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->queryParam(command, params); + } + + bool Transaction::sendQuery(const char * query) + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->sendQuery(query); + } + + bool Transaction::sendQuery(const std::string &command) + { + return sendQuery(command.c_str()); + } + + bool Transaction::sendQuery(const QString &command) + { + return sendQuery(command.toUtf8().data()); + } + + bool Transaction::sendQueryParams(const char * command, const Params ¶ms) + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->sendQueryParams(command, params); + } + + std::shared_ptr Transaction::getResult() + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->getResult(); + } + + bool Transaction::consumeInput() + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->consumeInput(); + } + + bool Transaction::isBusy() + { + BOOST_ASSERT(m_conn != nullptr); + BOOST_ASSERT(m_committed == false); + BOOST_ASSERT(m_rolledback == false); + + return m_conn->isBusy(); + } + + std::string Transaction::escapeLiteral(const std::string_view &literal) + { + BOOST_ASSERT(m_conn != nullptr); + + return m_conn->escapeLiteral(literal); + } + + QString Transaction::escapeLiteral(const QString &literal) + { + BOOST_ASSERT(m_conn != nullptr); + + return m_conn->escapeLiteral(literal); + } + + std::string Transaction::escapeIdentifier(const std::string_view &ident) + { + BOOST_ASSERT(m_conn != nullptr); + + return m_conn->escapeIdentifier(ident); + } + + QString Transaction::escapeIdentifier(const QString &ident) + { + BOOST_ASSERT(m_conn != nullptr); + + return m_conn->escapeIdentifier(ident); + } + + + QString Transaction::getDBName() const + { + BOOST_ASSERT(m_conn != nullptr); + + return m_conn->getDBName(); + } + +} // end of namespace Pgsql diff --git a/pgsql/Pgsql_Transaction.h b/pgsql/Pgsql_Transaction.h new file mode 100644 index 0000000..dc6ab06 --- /dev/null +++ b/pgsql/Pgsql_Transaction.h @@ -0,0 +1,53 @@ +#ifndef PGSQL_TRANSACTION_H +#define PGSQL_TRANSACTION_H + +#include +#include +#include "Pgsql_Canceller.h" + +namespace Pgsql { + + class Result; + class Params; + class Connection; + + + class Transaction { + public: + static Transaction startTransaction(Connection &conn); + ~Transaction(); + void rollback(); + void commit(); + Canceller getCancel(); + std::string getErrorMessage() const; + Result query(const char * command); + Result query(const QString &command); + Result queryParam(const char * command, const Params ¶ms); + Result queryParam(const QString &command, const Params ¶ms); + bool sendQuery(const char * query); + bool sendQuery(const std::string &command); + bool sendQuery(const QString &command); + bool sendQueryParams(const char * command, const Params ¶ms); + std::shared_ptr getResult(); + bool consumeInput(); + bool isBusy(); + std::string escapeLiteral(const std::string_view &literal); + QString escapeLiteral(const QString &literal); + std::string escapeIdentifier(const std::string_view &ident); + QString escapeIdentifier(const QString &ident); + QString getDBName() const; + + private: + Connection *m_conn = nullptr; + bool m_committed = false; + bool m_rolledback = false; + + Transaction(Connection *conn) + : m_conn(conn) + { + } + }; + +} + +#endif // PGSQL_TRANSACTION_H diff --git a/pgsql/pgsql.pro b/pgsql/pgsql.pro index 6999c72..d603ae6 100644 --- a/pgsql/pgsql.pro +++ b/pgsql/pgsql.pro @@ -35,9 +35,11 @@ SOURCES += Pgsql_Connection.cpp \ Pgsql_Result.cpp \ Pgsql_Row.cpp \ Pgsql_Value.cpp \ - Pgsql_Col.cpp \ ArrayParser.cpp \ - Pgsql_oids.cpp + Pgsql_oids.cpp \ + Pgsql_Transaction.cpp \ + Pgsql_ErrorDetails.cpp \ + Pgsql_Canceller.cpp HEADERS += Pgsql_Connection.h \ Pgsql_Params.h \ @@ -49,7 +51,10 @@ HEADERS += Pgsql_Connection.h \ Pgsql_Col.h \ ArrayParser.h \ Pgsql_oids.h \ - SqlGenerator.h + SqlGenerator.h \ + Pgsql_Transaction.h \ + Pgsql_ErrorDetails.h \ + Pgsql_Canceller.h #FORMS +=