Kea  1.9.9-git
pgsql_exchange.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 <pgsql/pgsql_exchange.h>
10 #include <pgsql/pgsql_connection.h>
11 
12 #include <boost/lexical_cast.hpp>
13 
14 #include <iomanip>
15 #include <sstream>
16 #include <vector>
17 
18 namespace isc {
19 namespace db {
20 
21 const int PsqlBindArray::TEXT_FMT = 0;
22 const int PsqlBindArray::BINARY_FMT = 1;
23 const char* PsqlBindArray::TRUE_STR = "TRUE";
24 const char* PsqlBindArray::FALSE_STR = "FALSE";
25 
26 void PsqlBindArray::add(const char* value) {
27  if (!value) {
28  isc_throw(BadValue, "PsqlBindArray::add - char* value cannot be NULL");
29  }
30 
31  values_.push_back(value);
32  lengths_.push_back(strlen(value));
33  formats_.push_back(TEXT_FMT);
34 }
35 
36 void PsqlBindArray::add(const std::string& value) {
37  values_.push_back(value.c_str());
38  lengths_.push_back(value.size());
39  formats_.push_back(TEXT_FMT);
40 }
41 
42 void PsqlBindArray::add(const std::vector<uint8_t>& data) {
43  values_.push_back(reinterpret_cast<const char*>(&(data[0])));
44  lengths_.push_back(data.size());
45  formats_.push_back(BINARY_FMT);
46 }
47 
48 void PsqlBindArray::add(const uint8_t* data, const size_t len) {
49  if (!data) {
50  isc_throw(BadValue, "PsqlBindArray::add - uint8_t data cannot be NULL");
51  }
52 
53  values_.push_back(reinterpret_cast<const char*>(&(data[0])));
54  lengths_.push_back(len);
55  formats_.push_back(BINARY_FMT);
56 }
57 
58 void PsqlBindArray::add(const bool& value) {
59  add(value ? TRUE_STR : FALSE_STR);
60 }
61 
62 void PsqlBindArray::add(const uint8_t& byte) {
63  // We static_cast to an unsigned int, otherwise lexical_cast may to
64  // treat byte as a character, which yields "" for unprintable values
65  addTempString(boost::lexical_cast<std::string>
66  (static_cast<unsigned int>(byte)));
67 }
68 
70  if (addr.isV4()) {
71  addTempString(boost::lexical_cast<std::string>
72  (addr.toUint32()));
73  } else {
74  addTempString(addr.toText());
75  }
76 }
77 
78 void PsqlBindArray::addNull(const int format) {
79  values_.push_back(NULL);
80  lengths_.push_back(0);
81  formats_.push_back(format);
82 }
83 
89 void PsqlBindArray::addTempString(const std::string& str) {
90  bound_strs_.push_back(ConstStringPtr(new std::string(str)));
91  PsqlBindArray::add((bound_strs_.back())->c_str());
92 }
93 
94 std::string PsqlBindArray::toText() const {
95  std::ostringstream stream;
96  for (int i = 0; i < values_.size(); ++i) {
97  stream << i << " : ";
98  if (formats_[i] == TEXT_FMT) {
99  stream << "\"" << values_[i] << "\"" << std::endl;
100  } else {
101  const char *data = values_[i];
102  if (lengths_[i] == 0) {
103  stream << "empty" << std::endl;
104  } else {
105  stream << "0x";
106  for (int x = 0; x < lengths_[i]; ++x) {
107  stream << std::setfill('0') << std::setw(2)
108  << std::setbase(16)
109  << static_cast<unsigned int>(data[x]);
110  }
111  stream << std::endl;
112  stream << std::setbase(10);
113  }
114  }
115  }
116 
117  return (stream.str());
118 }
119 
120 std::string
121 PgSqlExchange::convertToDatabaseTime(const time_t input_time) {
122  struct tm tinfo;
123  char buffer[20];
124  localtime_r(&input_time, &tinfo);
125  strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
126  return (std::string(buffer));
127 }
128 
129 std::string
131  const uint32_t valid_lifetime) {
132  // Calculate expiry time. Store it in the 64-bit value so as we can
133  // detect overflows.
134  int64_t expire_time_64 = static_cast<int64_t>(cltt)
135  + static_cast<int64_t>(valid_lifetime);
136 
137  // It has been observed that the PostgreSQL doesn't deal well with the
138  // timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
139  // beginning of the epoch (around year 2038). The value is often
140  // stored in the database but it is invalid when read back (overflow?).
141  // Hence, the maximum timestamp value is restricted here.
142  if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
143  isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
144  }
145 
146  return (convertToDatabaseTime(static_cast<time_t>(expire_time_64)));
147 }
148 
149 time_t
150 PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val) {
151  // Convert string time value to time_t
152  time_t new_time;
153  try {
154  new_time = (boost::lexical_cast<time_t>(db_time_val));
155  } catch (const std::exception& ex) {
156  isc_throw(BadValue, "Database time value is invalid: " << db_time_val);
157  }
158 
159  return (new_time);
160 }
161 
162 const char*
164  const size_t col) {
165  r.rowColCheck(row,col);
166  const char* value = PQgetvalue(r, row, col);
167  if (!value) {
168  isc_throw(DbOperationError, "getRawColumnValue no data for :"
169  << getColumnLabel(r, col) << " row:" << row);
170  }
171  return (value);
172 }
173 
174 bool
175 PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row,
176  const size_t col) {
177  r.rowColCheck(row,col);
178  return (PQgetisnull(r, row, col));
179 }
180 
181 void
183  const size_t col, std::string& value) {
184  value = getRawColumnValue(r, row, col);
185 }
186 
187 void
189  const size_t col, bool &value) {
190  const char* data = getRawColumnValue(r, row, col);
191  if (!strlen(data) || *data == 'f') {
192  value = false;
193  } else if (*data == 't') {
194  value = true;
195  } else {
196  isc_throw(DbOperationError, "Invalid boolean data: " << data
197  << " for: " << getColumnLabel(r, col) << " row:" << row
198  << " : must be 't' or 'f'");
199  }
200 }
201 
202 void
204  const size_t col, uint8_t &value) {
205  const char* data = getRawColumnValue(r, row, col);
206  try {
207  // lexically casting as uint8_t doesn't convert from char
208  // so we use uint16_t and implicitly convert.
209  value = boost::lexical_cast<uint16_t>(data);
210  } catch (const std::exception& ex) {
211  isc_throw(DbOperationError, "Invalid uint8_t data: " << data
212  << " for: " << getColumnLabel(r, col) << " row:" << row
213  << " : " << ex.what());
214  }
215 }
216 
218 PgSqlExchange::getIPv6Value(const PgSqlResult& r, const int row,
219  const size_t col) {
220  const char* data = getRawColumnValue(r, row, col);
221  try {
222  return (isc::asiolink::IOAddress(data));
223  } catch (const std::exception& ex) {
224  isc_throw(DbOperationError, "Cannot convert data: " << data
225  << " for: " << getColumnLabel(r, col) << " row:" << row
226  << " : " << ex.what());
227  }
228 }
229 
230 void
232  const size_t col, uint8_t* buffer,
233  const size_t buffer_size,
234  size_t &bytes_converted) {
235  // Returns converted bytes in a dynamically allocated buffer, and
236  // sets bytes_converted.
237  unsigned char* bytes = PQunescapeBytea((const unsigned char*)
238  (getRawColumnValue(r, row, col)),
239  &bytes_converted);
240 
241  // Unlikely it couldn't allocate it but you never know.
242  if (!bytes) {
243  isc_throw (DbOperationError, "PQunescapeBytea failed for:"
244  << getColumnLabel(r, col) << " row:" << row);
245  }
246 
247  // Make sure it's not larger than expected.
248  if (bytes_converted > buffer_size) {
249  // Free the allocated buffer first!
250  PQfreemem(bytes);
251  isc_throw (DbOperationError, "Converted data size: "
252  << bytes_converted << " is too large for: "
253  << getColumnLabel(r, col) << " row:" << row);
254  }
255 
256  // Copy from the allocated buffer to caller's buffer the free up
257  // the allocated buffer.
258  memcpy(buffer, bytes, bytes_converted);
259  PQfreemem(bytes);
260 }
261 
262 std::string
263 PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
264  return (r.getColumnLabel(column));
265 }
266 
267 std::string
269  r.rowCheck(row);
270  std::ostringstream stream;
271  int columns = r.getCols();
272  for (int col = 0; col < columns; ++col) {
273  const char* val = getRawColumnValue(r, row, col);
274  std::string name = r.getColumnLabel(col);
275  int format = PQfformat(r, col);
276 
277  stream << col << " " << name << " : " ;
278  if (format == PsqlBindArray::TEXT_FMT) {
279  stream << "\"" << val << "\"" << std::endl;
280  } else {
281  const char *data = val;
282  int length = PQfsize(r, col);
283  if (length == 0) {
284  stream << "empty" << std::endl;
285  } else {
286  stream << "0x";
287  for (int i = 0; i < length; ++i) {
288  stream << std::setfill('0') << std::setw(2)
289  << std::setbase(16)
290  << static_cast<unsigned int>(data[i]);
291  }
292  stream << std::endl;
293  }
294  }
295  }
296 
297  return (stream.str());
298 }
299 
300 }; // end of isc::db namespace
301 }; // end of isc namespace
static time_t convertFromDatabaseTime(const std::string &db_time_val)
Converts time stamp from the database to a time_t.
RAII wrapper for PostgreSQL Result sets.
static std::string convertToDatabaseTime(const time_t input_time)
Converts time_t value to a text representation in local time.
std::vector< int > formats_
Vector of "format" for each value.
static const int BINARY_FMT
Format value for binary data.
static std::string dumpRow(const PgSqlResult &r, int row)
Diagnostic tool which dumps the Result row contents as a string.
std::string toText() const
Dumps the contents of the array to a string.
static const char * FALSE_STR
Constant string passed to DB for boolean false values.
std::vector< int > lengths_
Vector of data lengths for each value.
void rowColCheck(int row, int col) const
Determines if both a row and column index are valid.
void rowCheck(int row) const
Determines if a row index is valid.
static isc::asiolink::IOAddress getIPv6Value(const PgSqlResult &r, const int row, const size_t col)
Converts a column in a row in a result set into IPv6 address.
static const time_t MAX_DB_TIME
Defines maximum value for time that can be reliably stored.
std::vector< const char * > values_
Vector of pointers to the data values.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
void addTempString(const std::string &str)
Binds the given string to the bind array.
void add(const char *value)
Adds a char array to bind array based.
static const int TEXT_FMT
Format value for text data.
static void getColumnValue(const PgSqlResult &r, const int row, const size_t col, std::string &value)
Fetches text column value as a string.
static bool isColumnNull(const PgSqlResult &r, const int row, const size_t col)
Returns true if a column within a row is null.
static std::string getColumnLabel(const PgSqlResult &r, const size_t col)
Fetches the name of the column in a result set.
void addNull(const int format=PsqlBindArray::TEXT_FMT)
Adds a NULL value to the bind array.
static void convertFromBytea(const PgSqlResult &r, const int row, const size_t col, uint8_t *buffer, const size_t buffer_size, size_t &bytes_converted)
Converts a column in a row in a result set to a binary bytes.
boost::shared_ptr< const std::string > ConstStringPtr
Structure used to bind C++ input values to dynamic SQL parameters The structure contains three vector...
Defines the logger used by the top-level component of kea-dhcp-ddns.
int getCols() const
Returns the number of columns in the result set.
std::string getColumnLabel(const int col) const
Fetches the name of the column in a result set.
static const char * getRawColumnValue(const PgSqlResult &r, const int row, const size_t col)
Gets a pointer to the raw column value in a result set row.
Exception thrown on failure to execute a database function.
static const char * TRUE_STR
Constant string passed to DB for boolean true values.
std::string format(const std::string &format, const std::vector< std::string > &args)
Apply Formatting.
Definition: strutil.cc:157