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