pgsql_connection.cc 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. // Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <config.h>
  7. #include <dhcpsrv/dhcpsrv_log.h>
  8. #include <dhcpsrv/pgsql_connection.h>
  9. // PostgreSQL errors should be tested based on the SQL state code. Each state
  10. // code is 5 decimal, ASCII, digits, the first two define the category of
  11. // error, the last three are the specific error. PostgreSQL makes the state
  12. // code as a char[5]. Macros for each code are defined in PostgreSQL's
  13. // server/utils/errcodes.h, although they require a second macro,
  14. // MAKE_SQLSTATE for completion. For example, duplicate key error as:
  15. //
  16. // #define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3','5','0','5')
  17. //
  18. // PostgreSQL deliberately omits the MAKE_SQLSTATE macro so callers can/must
  19. // supply their own. We'll define it as an initlizer_list:
  20. #define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5) {ch1,ch2,ch3,ch4,ch5}
  21. // So we can use it like this: const char some_error[] = ERRCODE_xxxx;
  22. #define PGSQL_STATECODE_LEN 5
  23. #include <utils/errcodes.h>
  24. using namespace std;
  25. namespace isc {
  26. namespace dhcp {
  27. // Default connection timeout
  28. const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
  29. const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
  30. PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn)
  31. : conn_(conn), committed_(false) {
  32. conn_.startTransaction();
  33. }
  34. PgSqlTransaction::~PgSqlTransaction() {
  35. // Rollback if the PgSqlTransaction::commit wasn't explicitly
  36. // called.
  37. if (!committed_) {
  38. conn_.rollback();
  39. }
  40. }
  41. void
  42. PgSqlTransaction::commit() {
  43. conn_.commit();
  44. committed_ = true;
  45. }
  46. PgSqlConnection::~PgSqlConnection() {
  47. if (conn_) {
  48. // Deallocate the prepared queries.
  49. PgSqlResult r(PQexec(conn_, "DEALLOCATE all"));
  50. if(PQresultStatus(r) != PGRES_COMMAND_OK) {
  51. // Highly unlikely but we'll log it and go on.
  52. LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_DEALLOC_ERROR)
  53. .arg(PQerrorMessage(conn_));
  54. }
  55. }
  56. }
  57. void
  58. PgSqlConnection::prepareStatement(const PgSqlTaggedStatement& statement) {
  59. // Prepare all statements queries with all known fields datatype
  60. PgSqlResult r(PQprepare(conn_, statement.name, statement.text,
  61. statement.nbparams, statement.types));
  62. if(PQresultStatus(r) != PGRES_COMMAND_OK) {
  63. isc_throw(DbOperationError, "unable to prepare PostgreSQL statement: "
  64. << statement.text << ", reason: " << PQerrorMessage(conn_));
  65. }
  66. }
  67. void
  68. PgSqlConnection::openDatabase() {
  69. string dbconnparameters;
  70. string shost = "localhost";
  71. try {
  72. shost = getParameter("host");
  73. } catch(...) {
  74. // No host. Fine, we'll use "localhost"
  75. }
  76. dbconnparameters += "host = '" + shost + "'" ;
  77. string suser;
  78. try {
  79. suser = getParameter("user");
  80. dbconnparameters += " user = '" + suser + "'";
  81. } catch(...) {
  82. // No user. Fine, we'll use NULL
  83. }
  84. string spassword;
  85. try {
  86. spassword = getParameter("password");
  87. dbconnparameters += " password = '" + spassword + "'";
  88. } catch(...) {
  89. // No password. Fine, we'll use NULL
  90. }
  91. string sname;
  92. try {
  93. sname = getParameter("name");
  94. dbconnparameters += " dbname = '" + sname + "'";
  95. } catch(...) {
  96. // No database name. Throw a "NoDatabaseName" exception
  97. isc_throw(NoDatabaseName, "must specify a name for the database");
  98. }
  99. unsigned int connect_timeout = PGSQL_DEFAULT_CONNECTION_TIMEOUT;
  100. string stimeout;
  101. try {
  102. stimeout = getParameter("connect-timeout");
  103. } catch (...) {
  104. // No timeout parameter, we are going to use the default timeout.
  105. stimeout = "";
  106. }
  107. if (stimeout.size() > 0) {
  108. // Timeout was given, so try to convert it to an integer.
  109. try {
  110. connect_timeout = boost::lexical_cast<unsigned int>(stimeout);
  111. } catch (...) {
  112. // Timeout given but could not be converted to an unsigned int. Set
  113. // the connection timeout to an invalid value to trigger throwing
  114. // of an exception.
  115. connect_timeout = 0;
  116. }
  117. // The timeout is only valid if greater than zero, as depending on the
  118. // database, a zero timeout might signify someting like "wait
  119. // indefinitely".
  120. //
  121. // The check below also rejects a value greater than the maximum
  122. // integer value. The lexical_cast operation used to obtain a numeric
  123. // value from a string can get confused if trying to convert a negative
  124. // integer to an unsigned int: instead of throwing an exception, it may
  125. // produce a large positive value.
  126. if ((connect_timeout == 0) ||
  127. (connect_timeout > numeric_limits<int>::max())) {
  128. isc_throw(DbInvalidTimeout, "database connection timeout (" <<
  129. stimeout << ") must be an integer greater than 0");
  130. }
  131. }
  132. std::ostringstream oss;
  133. oss << connect_timeout;
  134. dbconnparameters += " connect_timeout = " + oss.str();
  135. // Connect to Postgres, saving the low level connection pointer
  136. // in the holder object
  137. PGconn* new_conn = PQconnectdb(dbconnparameters.c_str());
  138. if (!new_conn) {
  139. isc_throw(DbOpenError, "could not allocate connection object");
  140. }
  141. if (PQstatus(new_conn) != CONNECTION_OK) {
  142. // If we have a connection object, we have to call finish
  143. // to release it, but grab the error message first.
  144. std::string error_message = PQerrorMessage(new_conn);
  145. PQfinish(new_conn);
  146. isc_throw(DbOpenError, error_message);
  147. }
  148. // We have a valid connection, so let's save it to our holder
  149. conn_.setConnection(new_conn);
  150. }
  151. bool
  152. PgSqlConnection::compareError(const PgSqlResult& r, const char* error_state) {
  153. const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
  154. // PostgreSQL garuantees it will always be 5 characters long
  155. return ((sqlstate != NULL) &&
  156. (memcmp(sqlstate, error_state, PGSQL_STATECODE_LEN) == 0));
  157. }
  158. void
  159. PgSqlConnection::checkStatementError(const PgSqlResult& r,
  160. PgSqlTaggedStatement& statement) const {
  161. int s = PQresultStatus(r);
  162. if (s != PGRES_COMMAND_OK && s != PGRES_TUPLES_OK) {
  163. // We're testing the first two chars of SQLSTATE, as this is the
  164. // error class. Note, there is a severity field, but it can be
  165. // misleadingly returned as fatal.
  166. const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
  167. if ((sqlstate != NULL) &&
  168. ((memcmp(sqlstate, "08", 2) == 0) || // Connection Exception
  169. (memcmp(sqlstate, "53", 2) == 0) || // Insufficient resources
  170. (memcmp(sqlstate, "54", 2) == 0) || // Program Limit exceeded
  171. (memcmp(sqlstate, "57", 2) == 0) || // Operator intervention
  172. (memcmp(sqlstate, "58", 2) == 0))) { // System error
  173. LOG_ERROR(dhcpsrv_logger, DHCPSRV_PGSQL_FATAL_ERROR)
  174. .arg(statement.name)
  175. .arg(PQerrorMessage(conn_))
  176. .arg(sqlstate);
  177. exit (-1);
  178. }
  179. const char* error_message = PQerrorMessage(conn_);
  180. isc_throw(DbOperationError, "Statement exec failed:" << " for: "
  181. << statement.name << ", reason: "
  182. << error_message);
  183. }
  184. }
  185. void
  186. PgSqlConnection::startTransaction() {
  187. LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
  188. DHCPSRV_PGSQL_START_TRANSACTION);
  189. PgSqlResult r(PQexec(conn_, "START TRANSACTION"));
  190. if (PQresultStatus(r) != PGRES_COMMAND_OK) {
  191. const char* error_message = PQerrorMessage(conn_);
  192. isc_throw(DbOperationError, "unable to start transaction"
  193. << error_message);
  194. }
  195. }
  196. void
  197. PgSqlConnection::commit() {
  198. LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_COMMIT);
  199. PgSqlResult r(PQexec(conn_, "COMMIT"));
  200. if (PQresultStatus(r) != PGRES_COMMAND_OK) {
  201. const char* error_message = PQerrorMessage(conn_);
  202. isc_throw(DbOperationError, "commit failed: " << error_message);
  203. }
  204. }
  205. void
  206. PgSqlConnection::rollback() {
  207. LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_PGSQL_ROLLBACK);
  208. PgSqlResult r(PQexec(conn_, "ROLLBACK"));
  209. if (PQresultStatus(r) != PGRES_COMMAND_OK) {
  210. const char* error_message = PQerrorMessage(conn_);
  211. isc_throw(DbOperationError, "rollback failed: " << error_message);
  212. }
  213. }
  214. }; // end of isc::dhcp namespace
  215. }; // end of isc namespace