Kea  1.5.0
pgsql_connection.cc
Go to the documentation of this file.
1 // Copyright (C) 2016-2018 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 
7 #include <config.h>
8 
9 #include <database/db_log.h>
10 #include <pgsql/pgsql_connection.h>
11 
12 // PostgreSQL errors should be tested based on the SQL state code. Each state
13 // code is 5 decimal, ASCII, digits, the first two define the category of
14 // error, the last three are the specific error. PostgreSQL makes the state
15 // code as a char[5]. Macros for each code are defined in PostgreSQL's
16 // server/utils/errcodes.h, although they require a second macro,
17 // MAKE_SQLSTATE for completion. For example, duplicate key error as:
18 //
19 // #define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3','5','0','5')
20 //
21 // PostgreSQL deliberately omits the MAKE_SQLSTATE macro so callers can/must
22 // supply their own. We'll define it as an initialization list:
23 #define MAKE_SQLSTATE(ch1,ch2,ch3,ch4,ch5) {ch1,ch2,ch3,ch4,ch5}
24 // So we can use it like this: const char some_error[] = ERRCODE_xxxx;
25 #define PGSQL_STATECODE_LEN 5
26 #include <utils/errcodes.h>
27 
28 using namespace std;
29 
30 namespace isc {
31 namespace db {
32 
33 // Default connection timeout
34 
36 const int PGSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
37 
38 const char PgSqlConnection::DUPLICATE_KEY[] = ERRCODE_UNIQUE_VIOLATION;
39 
40 PgSqlResult::PgSqlResult(PGresult *result)
41  : result_(result), rows_(0), cols_(0) {
42  if (!result) {
43  // Certain failures, like a loss of connectivity, can return a
44  // null PGresult and we still need to be able to create a PgSqlResult.
45  // We'll set row and col counts to -1 to prevent anyone going off the
46  // rails.
47  rows_ = -1;
48  cols_ = -1;
49  } else {
50  rows_ = PQntuples(result);
51  cols_ = PQnfields(result);
52  }
53 }
54 
55 void
56 PgSqlResult::rowCheck(int row) const {
57  if (row < 0 || row >= rows_) {
58  isc_throw (db::DbOperationError, "row: " << row
59  << ", out of range: 0.." << rows_);
60  }
61 }
62 
64  if (result_) {
65  PQclear(result_);
66  }
67 }
68 
69 void
70 PgSqlResult::colCheck(int col) const {
71  if (col < 0 || col >= cols_) {
72  isc_throw (DbOperationError, "col: " << col
73  << ", out of range: 0.." << cols_);
74  }
75 }
76 
77 void
78 PgSqlResult::rowColCheck(int row, int col) const {
79  rowCheck(row);
80  colCheck(col);
81 }
82 
83 std::string
84 PgSqlResult::getColumnLabel(const int col) const {
85  const char* label = NULL;
86  try {
87  colCheck(col);
88  label = PQfname(result_, col);
89  } catch (...) {
90  std::ostringstream os;
91  os << "Unknown column:" << col;
92  return (os.str());
93  }
94 
95  return (label);
96 }
97 
99  : conn_(conn), committed_(false) {
100  conn_.startTransaction();
101 }
102 
104  // If commit() wasn't explicitly called, rollback.
105  if (!committed_) {
106  conn_.rollback();
107  }
108 }
109 
110 void
112  conn_.commit();
113  committed_ = true;
114 }
115 
117  if (conn_) {
118  // Deallocate the prepared queries.
119  if (PQstatus(conn_) == CONNECTION_OK) {
120  PgSqlResult r(PQexec(conn_, "DEALLOCATE all"));
121  if(PQresultStatus(r) != PGRES_COMMAND_OK) {
122  // Highly unlikely but we'll log it and go on.
124  .arg(PQerrorMessage(conn_));
125  }
126  }
127  }
128 }
129 
130 void
132  // Prepare all statements queries with all known fields datatype
133  PgSqlResult r(PQprepare(conn_, statement.name, statement.text,
134  statement.nbparams, statement.types));
135  if(PQresultStatus(r) != PGRES_COMMAND_OK) {
136  isc_throw(DbOperationError, "unable to prepare PostgreSQL statement: "
137  << statement.text << ", reason: " << PQerrorMessage(conn_));
138  }
139 }
140 
141 void
143  const PgSqlTaggedStatement* end_statement) {
144  // Created the PostgreSQL prepared statements.
145  for (const PgSqlTaggedStatement* tagged_statement = start_statement;
146  tagged_statement != end_statement; ++tagged_statement) {
147  prepareStatement(*tagged_statement);
148  }
149 }
150 
151 void
153  string dbconnparameters;
154  string shost = "localhost";
155  try {
156  shost = getParameter("host");
157  } catch(...) {
158  // No host. Fine, we'll use "localhost"
159  }
160 
161  dbconnparameters += "host = '" + shost + "'" ;
162 
163  string sport;
164  try {
165  sport = getParameter("port");
166  } catch (...) {
167  // No port parameter, we are going to use the default port.
168  sport = "";
169  }
170 
171  if (sport.size() > 0) {
172  unsigned int port = 0;
173 
174  // Port was given, so try to convert it to an integer.
175  try {
176  port = boost::lexical_cast<unsigned int>(sport);
177  } catch (...) {
178  // Port given but could not be converted to an unsigned int.
179  // Just fall back to the default value.
180  port = 0;
181  }
182 
183  // The port is only valid when it is in the 0..65535 range.
184  // Again fall back to the default when the given value is invalid.
185  if (port > numeric_limits<uint16_t>::max()) {
186  port = 0;
187  }
188 
189  // Add it to connection parameters when not default.
190  if (port > 0) {
191  std::ostringstream oss;
192  oss << port;
193  dbconnparameters += " port = " + oss.str();
194  }
195  }
196 
197  string suser;
198  try {
199  suser = getParameter("user");
200  dbconnparameters += " user = '" + suser + "'";
201  } catch(...) {
202  // No user. Fine, we'll use NULL
203  }
204 
205  string spassword;
206  try {
207  spassword = getParameter("password");
208  dbconnparameters += " password = '" + spassword + "'";
209  } catch(...) {
210  // No password. Fine, we'll use NULL
211  }
212 
213  string sname;
214  try {
215  sname = getParameter("name");
216  dbconnparameters += " dbname = '" + sname + "'";
217  } catch(...) {
218  // No database name. Throw a "NoDatabaseName" exception
219  isc_throw(NoDatabaseName, "must specify a name for the database");
220  }
221 
222  unsigned int connect_timeout = PGSQL_DEFAULT_CONNECTION_TIMEOUT;
223  string stimeout;
224  try {
225  stimeout = getParameter("connect-timeout");
226  } catch (...) {
227  // No timeout parameter, we are going to use the default timeout.
228  stimeout = "";
229  }
230 
231  if (stimeout.size() > 0) {
232  // Timeout was given, so try to convert it to an integer.
233 
234  try {
235  connect_timeout = boost::lexical_cast<unsigned int>(stimeout);
236  } catch (...) {
237  // Timeout given but could not be converted to an unsigned int. Set
238  // the connection timeout to an invalid value to trigger throwing
239  // of an exception.
240  connect_timeout = 0;
241  }
242 
243  // The timeout is only valid if greater than zero, as depending on the
244  // database, a zero timeout might signify something like "wait
245  // indefinitely".
246  //
247  // The check below also rejects a value greater than the maximum
248  // integer value. The lexical_cast operation used to obtain a numeric
249  // value from a string can get confused if trying to convert a negative
250  // integer to an unsigned int: instead of throwing an exception, it may
251  // produce a large positive value.
252  if ((connect_timeout == 0) ||
253  (connect_timeout > numeric_limits<int>::max())) {
254  isc_throw(DbInvalidTimeout, "database connection timeout (" <<
255  stimeout << ") must be an integer greater than 0");
256  }
257  }
258 
259  std::ostringstream oss;
260  oss << connect_timeout;
261  dbconnparameters += " connect_timeout = " + oss.str();
262 
263  // Connect to Postgres, saving the low level connection pointer
264  // in the holder object
265  PGconn* new_conn = PQconnectdb(dbconnparameters.c_str());
266  if (!new_conn) {
267  isc_throw(DbOpenError, "could not allocate connection object");
268  }
269 
270  if (PQstatus(new_conn) != CONNECTION_OK) {
271  // If we have a connection object, we have to call finish
272  // to release it, but grab the error message first.
273  std::string error_message = PQerrorMessage(new_conn);
274  PQfinish(new_conn);
275  isc_throw(DbOpenError, error_message);
276  }
277 
278  // We have a valid connection, so let's save it to our holder
279  conn_.setConnection(new_conn);
280 }
281 
282 bool
283 PgSqlConnection::compareError(const PgSqlResult& r, const char* error_state) {
284  const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
285  // PostgreSQL guarantees it will always be 5 characters long
286  return ((sqlstate != NULL) &&
287  (memcmp(sqlstate, error_state, PGSQL_STATECODE_LEN) == 0));
288 }
289 
290 void
292  PgSqlTaggedStatement& statement) const {
293  int s = PQresultStatus(r);
294  if (s != PGRES_COMMAND_OK && s != PGRES_TUPLES_OK) {
295  // We're testing the first two chars of SQLSTATE, as this is the
296  // error class. Note, there is a severity field, but it can be
297  // misleadingly returned as fatal. However, a loss of connectivity
298  // can lead to a NULL sqlstate with a status of PGRES_FATAL_ERROR.
299  const char* sqlstate = PQresultErrorField(r, PG_DIAG_SQLSTATE);
300  if ((sqlstate == NULL) ||
301  ((memcmp(sqlstate, "08", 2) == 0) || // Connection Exception
302  (memcmp(sqlstate, "53", 2) == 0) || // Insufficient resources
303  (memcmp(sqlstate, "54", 2) == 0) || // Program Limit exceeded
304  (memcmp(sqlstate, "57", 2) == 0) || // Operator intervention
305  (memcmp(sqlstate, "58", 2) == 0))) { // System error
307  .arg(statement.name)
308  .arg(PQerrorMessage(conn_))
309  .arg(sqlstate ? sqlstate : "<sqlstate null>");
310 
311  // If there's no lost db callback or it returns false,
312  // then we're not attempting to recover so we're done
313  if (!invokeDbLostCallback()) {
314  exit (-1);
315  }
316 
317  // We still need to throw so caller can error out of the current
318  // processing.
320  "fatal database errror or connectivity lost");
321  }
322 
323  // Apparently it wasn't fatal, so we throw with a helpful message.
324  const char* error_message = PQerrorMessage(conn_);
325  isc_throw(DbOperationError, "Statement exec failed:" << " for: "
326  << statement.name << ", status: " << s
327  << "sqlstate:[ " << (sqlstate ? sqlstate : "<null>")
328  << "], reason: " << error_message);
329  }
330 }
331 
332 void
335  PgSqlResult r(PQexec(conn_, "START TRANSACTION"));
336  if (PQresultStatus(r) != PGRES_COMMAND_OK) {
337  const char* error_message = PQerrorMessage(conn_);
338  isc_throw(DbOperationError, "unable to start transaction"
339  << error_message);
340  }
341 }
342 
343 void
346  PgSqlResult r(PQexec(conn_, "COMMIT"));
347  if (PQresultStatus(r) != PGRES_COMMAND_OK) {
348  const char* error_message = PQerrorMessage(conn_);
349  isc_throw(DbOperationError, "commit failed: " << error_message);
350  }
351 }
352 
353 void
356  PgSqlResult r(PQexec(conn_, "ROLLBACK"));
357  if (PQresultStatus(r) != PGRES_COMMAND_OK) {
358  const char* error_message = PQerrorMessage(conn_);
359  isc_throw(DbOperationError, "rollback failed: " << error_message);
360  }
361 }
362 
363 }; // end of isc::db namespace
364 }; // end of isc namespace
We want to reuse the database backend connection and exchange code for other uses,...
RAII wrapper for PostgreSQL Result sets.
const Oid types[PGSQL_MAX_PARAMETERS_IN_QUERY]
OID types.
void startTransaction()
Start a transaction.
void commit()
Commits transaction.
const int PGSQL_DEFAULT_CONNECTION_TIMEOUT
#define DB_LOG_DEBUG(LEVEL, MESSAGE)
Macros.
Definition: db_log.h:115
bool compareError(const PgSqlResult &r, const char *error_state)
Checks a result set's SQL state against an error state.
bool invokeDbLostCallback() const
Invokes the connection's lost connectivity callback.
void rowCheck(int row) const
Determines if a row index is valid.
std::string getColumnLabel(const int col) const
Fetches the name of the column in a result set.
Exception thrown on failure to open database.
void commit()
Commit Transactions.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
void prepareStatements(const PgSqlTaggedStatement *start_statement, const PgSqlTaggedStatement *end_statement)
Prepare statements.
Exception thrown if name of database is not specified.
void rollback()
Rollback Transactions.
PgSqlTransaction(PgSqlConnection &conn)
Constructor.
std::string getParameter(const std::string &name) const
Returns value of a connection parameter.
Common PgSql Connector Pool.
virtual ~PgSqlConnection()
Destructor.
void prepareStatement(const PgSqlTaggedStatement &statement)
Prepare Single Statement.
const int DB_DBG_TRACE_DETAIL
Database logging levels.
Definition: db_log.h:40
Defines the logger used by the top-level component of kea-dhcp-ddns.
void setConnection(PGconn *connection)
Sets the connection to the value given.
Define a PostgreSQL statement.
void checkStatementError(const PgSqlResult &r, PgSqlTaggedStatement &statement) const
Checks result of the r object.
#define PGSQL_STATECODE_LEN
void rowColCheck(int row, int col) const
Determines if both a row and column index are valid.
const char * text
Text representation of the actual query.
void colCheck(int col) const
Determines if a column index is valid.
#define DB_LOG_ERROR(MESSAGE)
Definition: db_log.h:137
void openDatabase()
Open Database.
const char * name
Short name of the query.
PgSqlHolder conn_
PgSql connection handle.
Exception thrown on failure to execute a database function.
int nbparams
Number of parameters for a given query.