1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
|
// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef PGSQL_EXCHANGE_H
#define PGSQL_EXCHANGE_H
#include <asiolink/io_address.h>
#include <pgsql/pgsql_connection.h>
#include <boost/lexical_cast.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <stdint.h>
#include <vector>
#include <iostream>
namespace isc {
namespace db {
/// @brief Structure used to bind C++ input values to dynamic SQL parameters
/// The structure contains three vectors which store the input values,
/// data lengths, and formats. These vectors are passed directly into the
/// PostgreSQL execute call.
///
/// Note that the data values are stored as pointers. These pointers need to
/// be valid for the duration of the PostgreSQL statement execution. In other
/// words populating them with pointers to values that go out of scope before
/// statement is executed is a bad idea.
///
/// Other than vectors or buffers of binary data, all other values are currently
/// converted to their string representation prior to sending them to PostgreSQL.
/// All of the add() method variants which accept a non-string value internally
/// create the conversion string which is then retained in the bind array to ensure
/// scope.
///
/// @brief smart pointer to const std::strings used by PsqlBindArray to ensure scope
/// of strings supplying exchange values
typedef boost::shared_ptr<const std::string> ConstStringPtr;
struct PsqlBindArray {
/// @brief Vector of pointers to the data values.
std::vector<const char *> values_;
/// @brief Vector of data lengths for each value.
std::vector<int> lengths_;
/// @brief Vector of "format" for each value. A value of 0 means the
/// value is text, 1 means the value is binary.
std::vector<int> formats_;
/// @brief Format value for text data.
static const int TEXT_FMT;
/// @brief Format value for binary data.
static const int BINARY_FMT;
/// @brief Constant string passed to DB for boolean true values.
static const char* TRUE_STR;
/// @brief Constant string passed to DB for boolean false values.
static const char* FALSE_STR;
/// @brief Fetches the number of entries in the array.
/// @return Returns size_t containing the number of entries.
size_t size() const {
return (values_.size());
}
/// @brief Indicates it the array is empty.
/// @return Returns true if there are no entries in the array, false
/// otherwise.
bool empty() const {
return (values_.empty());
}
/// @brief Adds a char array to bind array based
///
/// Adds a TEXT_FMT value to the end of the bind array, using the given
/// char* as the data source. The value is expected to be NULL
/// terminated. The caller is responsible for ensuring that value
/// remains in scope until the bind array has been discarded.
///
/// @param value char array containing the null-terminated text to add.
/// @throw DbOperationError if value is NULL.
void add(const char* value);
/// @brief Adds an string value to the bind array
///
/// Adds a TEXT formatted value to the end of the bind array using the
/// given string as the data source. The caller is responsible for
/// ensuring that string parameter remains in scope until the bind
/// array has been discarded.
///
/// @param value std::string containing the value to add.
void add(const std::string& value);
/// @brief Adds a vector of binary data to the bind array.
///
/// Adds a BINARY_FMT value to the end of the bind array using the
/// given vector as the data source. NOTE this does not replicate
/// the vector, so it must remain in scope until the bind array
/// is destroyed.
///
/// @param data vector of binary bytes.
void add(const std::vector<uint8_t>& data);
/// @brief Adds a buffer of binary data to the bind array.
///
/// Adds a BINARY_FMT value to the end of the bind array using the
/// given vector as the data source. NOTE this does not replicate
/// the buffer, so it must remain in scope until the bind array
/// is destroyed.
///
/// @param data buffer of binary data.
/// @param len number of bytes of data in buffer
/// @throw DbOperationError if data is NULL.
void add(const uint8_t* data, const size_t len);
/// @brief Adds a boolean value to the bind array.
///
/// Converts the given boolean value to its corresponding to PostgreSQL
/// string value and adds it as a TEXT_FMT value to the bind array.
/// This creates an internally scoped string.
///
/// @param value the boolean value to add.
void add(const bool& value);
/// @brief Adds a uint8_t value to the bind array.
///
/// Converts the given uint8_t value to its corresponding numeric string
/// literal and adds it as a TEXT_FMT value to the bind array.
/// This creates an internally scoped string.
///
/// @param byte the one byte value to add.
void add(const uint8_t& byte);
/// @brief Adds a the given IOAddress value to the bind array.
///
/// Converts the IOAddress, based on its protocol family, to the
/// corresponding string literal and adds it as a TEXT_FMT value to
/// the bind array.
/// This creates an internally scoped string.
///
/// @param addr IP address value to add.
void add(const isc::asiolink::IOAddress& addr);
/// @brief Adds a the given value to the bind array.
///
/// Converts the given value to its corresponding string literal
/// boost::lexical_cast and adds it as a TEXT_FMT value to the bind array.
/// This is intended primarily for numeric types.
/// This creates an internally scoped string.
///
/// @param value data value to add.
template<typename T>
void add(const T& value) {
addTempString(boost::lexical_cast<std::string>(value));
}
/// @brief Binds a the given string to the bind array.
///
/// Prior to add the given string the vector of exchange values,
/// it duplicated as a ConstStringPtr and saved internally. This guarantees
/// the string remains in scope until the PsqlBindArray is destroyed,
/// without the caller maintaining the string values.
///
/// @param str string value to add.
void addTempString(const std::string& str);
/// @brief Adds a NULL value to the bind array
///
/// This should be used whenever a the value for a parameter specified
/// in the SQL statement should be NULL.
void addNull(const int format = PsqlBindArray::TEXT_FMT);
//std::vector<const std::string> getBoundStrs() {
std::vector<ConstStringPtr> getBoundStrs() {
return (bound_strs_);
}
/// @brief Dumps the contents of the array to a string.
/// @return std::string containing the dump
std::string toText() const;
private:
/// @brief vector of strings which supplied the values
std::vector<ConstStringPtr> bound_strs_;
};
/// @brief Defines a smart pointer to PsqlBindArray
typedef boost::shared_ptr<PsqlBindArray> PsqlBindArrayPtr;
/// @brief Base class for marshalling data to and from PostgreSQL.
///
/// Provides the common functionality to set up binding information between
/// application objects in the program and their representation in the
/// database, and for retrieving column values from rows of a result set.
class PgSqlExchange {
public:
/// @brief Constructor
PgSqlExchange(const size_t num_columns = 0) : columns_(num_columns) {}
/// @brief Destructor
virtual ~PgSqlExchange(){}
/// @brief Converts time_t value to a text representation in local time.
///
/// @param input_time A time_t value representing time.
/// @return std::string containing stringified time.
static std::string convertToDatabaseTime(const time_t input_time);
/// @brief Converts lease expiration time to a text representation in
/// local time.
///
/// The expiration time is calculated as a sum of the cltt (client last
/// transmit time) and the valid lifetime.
///
/// The format of the output string is "%Y-%m-%d %H:%M:%S". Database
/// table columns using this value should be typed as TIMESTAMP WITH
/// TIME ZONE. For such columns PostgreSQL assumes input strings without
/// timezones should be treated as in local time and are converted to UTC
/// when stored. Likewise, these columns are automatically adjusted
/// upon retrieval unless fetched via "extract(epoch from <column>))".
///
/// Unless we start using binary input, timestamp columns must be input as
/// date/time strings.
///
/// @param cltt Client last transmit time
/// @param valid_lifetime Valid lifetime
///
/// @return std::string containing the stringified time
/// @throw isc::BadValue if the sum of the calculated expiration time is
/// greater than the value of @c DataSource::MAX_DB_TIME.
static std::string convertToDatabaseTime(const time_t cltt,
const uint32_t valid_lifetime);
/// @brief Converts time stamp from the database to a time_t
///
/// We're fetching timestamps as an integer string of seconds since the
/// epoch. This method converts such a string int a time_t.
///
/// @param db_time_val timestamp to be converted. This value
/// is expected to be the number of seconds since the epoch
/// expressed as base-10 integer string.
/// @return Converted timestamp as time_t value.
static time_t convertFromDatabaseTime(const std::string& db_time_val);
/// @brief Gets a pointer to the raw column value in a result set row
///
/// Given a result set, row, and column return a const char* pointer to
/// the data value in the result set. The pointer is valid as long as
/// the result set has not been freed. It may point to text or binary
/// data depending on how query was structured. You should not attempt
/// to free this pointer.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return a const char* pointer to the column's raw data
/// @throw DbOperationError if the value cannot be fetched.
static const char* getRawColumnValue(const PgSqlResult& r, const int row,
const size_t col);
/// @brief Fetches the name of the column in a result set
///
/// Returns the column name of the column from the result set.
/// If the column index is out of range it will return the
/// string "Unknown column:<index>". Note this is NOT from the
/// list of columns defined in the exchange.
///
/// @param r the result set containing the query results
/// @param col index of the column name to fetch
///
/// @return string containing the name of the column
static std::string getColumnLabel(const PgSqlResult& r, const size_t col);
/// @brief Fetches text column value as a string
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the string value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
static void getColumnValue(const PgSqlResult& r, const int row,
const size_t col, std::string& value);
/// @brief Fetches boolean text ('t' or 'f') as a bool.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
static void getColumnValue(const PgSqlResult& r, const int row,
const size_t col, bool &value);
/// @brief Fetches an integer text column as a uint8_t.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
static void getColumnValue(const PgSqlResult& r, const int row,
const size_t col, uint8_t &value);
/// @brief Converts a column in a row in a result set into IPv6 address.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return isc::asiolink::IOAddress containing the IPv6 address.
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
static isc::asiolink::IOAddress getIPv6Value(const PgSqlResult& r,
const int row,
const size_t col);
/// @brief Returns true if a column within a row is null
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
///
/// @return True if the column values in the row is NULL, false otherwise.
static bool isColumnNull(const PgSqlResult& r, const int row,
const size_t col);
/// @brief Fetches a text column as the given value type
///
/// Uses boost::lexicalcast to convert the text column value into
/// a value of type T.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] value parameter to receive the converted value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
template<typename T>
static void getColumnValue(const PgSqlResult& r, const int row,
const size_t col, T& value) {
const char* data = getRawColumnValue(r, row, col);
try {
value = boost::lexical_cast<T>(data);
} catch (const std::exception& ex) {
isc_throw(db::DbOperationError, "Invalid data:[" << data
<< "] for row: " << row << " col: " << col << ","
<< getColumnLabel(r, col) << " : " << ex.what());
}
}
/// @brief Converts a column in a row in a result set to a binary bytes
///
/// Method is used to convert columns stored as BYTEA into a buffer of
/// binary bytes, (uint8_t). It uses PQunescapeBytea to do the conversion.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
/// @param col the column number within the row
/// @param[out] buffer pre-allocated buffer to which the converted bytes
/// will be stored.
/// @param buffer_size size of the output buffer
/// @param[out] bytes_converted number of bytes converted
/// value
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
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);
/// @brief Diagnostic tool which dumps the Result row contents as a string
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
///
/// @return A string depiction of the row contents.
static std::string dumpRow(const PgSqlResult& r, int row);
protected:
/// @brief Stores text labels for columns, currently only used for
/// logging and errors.
std::vector<std::string>columns_;
};
}; // end of isc::db namespace
}; // end of isc namespace
#endif // PGSQL_EXCHANGE_H
|