pgsql_exchange.cc 9.77 KB
Newer Older
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
// Copyright (C) 2016 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/.

#include <dhcpsrv/pgsql_exchange.h>

#include <boost/lexical_cast.hpp>

#include <iomanip>
#include <sstream>
#include <vector>

namespace isc {
namespace dhcp {

const int PsqlBindArray::TEXT_FMT = 0;
const int PsqlBindArray::BINARY_FMT = 1;
const char* PsqlBindArray::TRUE_STR = "TRUE";
const char* PsqlBindArray::FALSE_STR = "FALSE";

void PsqlBindArray::add(const char* value) {
    values_.push_back(value);
    lengths_.push_back(strlen(value));
    formats_.push_back(TEXT_FMT);
}

void PsqlBindArray::add(const std::string& value) {
    values_.push_back(value.c_str());
    lengths_.push_back(value.size());
    formats_.push_back(TEXT_FMT);
}

void PsqlBindArray::add(const std::vector<uint8_t>& data) {
    values_.push_back(reinterpret_cast<const char*>(&(data[0])));
    lengths_.push_back(data.size());
    formats_.push_back(BINARY_FMT);
}

41
42
43
44
45
46
void PsqlBindArray::add(const uint8_t* data, const size_t len) {
    values_.push_back(reinterpret_cast<const char*>(&(data[0])));
    lengths_.push_back(len);
    formats_.push_back(BINARY_FMT);
}

47
48
49
50
void PsqlBindArray::add(const bool& value)  {
    add(value ? TRUE_STR : FALSE_STR);
}

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
void PsqlBindArray::add(const uint8_t& byte) {
    // We static_cast to an unsigned int, otherwise lexcial_cast may to
    // treat byte as a character, which yields "" for unprintable values 
    bindString(boost::lexical_cast<std::string>
                              (static_cast<unsigned int>(byte)));
}

void PsqlBindArray::add(const isc::asiolink::IOAddress& addr) {
    if (addr.isV4()) {
        bindString(boost::lexical_cast<std::string>
                   (static_cast<uint32_t>(addr)));
    } else {
        bindString(addr.toText());
    }
}

67
68
69
70
71
72
void PsqlBindArray::addNull(const int format) {
    values_.push_back(NULL);
    lengths_.push_back(0);
    formats_.push_back(format);
}

73
74
75
76
77
78
// eventually this should replace add(std::string)
void PsqlBindArray::bindString(const std::string& str) {
    bound_strs_.push_back(StringPtr(new std::string(str)));
    PsqlBindArray::add((bound_strs_.back())->c_str());
}

79
std::string PsqlBindArray::toText() const {
80
81
82
83
84
85
86
87
88
89
90
91
    std::ostringstream stream;
    for (int i = 0; i < values_.size(); ++i) {
        stream << i << " : ";
        if (formats_[i] == TEXT_FMT) {
            stream << "\"" << values_[i] << "\"" << std::endl;
        } else {
            const char *data = values_[i];
            if (lengths_[i] == 0) {
                stream << "empty" << std::endl;
            } else {
                stream << "0x";
                for (int i = 0; i < lengths_[i]; ++i) {
92
93
                    stream << std::setfill('0') << std::setw(2)
                           << std::setbase(16)
94
95
96
97
98
99
100
101
102
103
                           << static_cast<unsigned int>(data[i]);
                }
                stream << std::endl;
            }
        }
    }

    return (stream.str());
}

104
std::string
105
106
107
108
109
110
111
112
113
PgSqlExchange::convertToDatabaseTime(const time_t input_time) {
    struct tm tinfo;
    char buffer[20];
    localtime_r(&input_time, &tinfo);
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo);
    return (std::string(buffer));
}

std::string
114
PgSqlExchange::convertToDatabaseTime(const time_t cltt,
115
                                     const uint32_t valid_lifetime) {
116
    // Calculate expiry time. Store it in the 64-bit value so as we can
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
    // detect overflows.
    int64_t expire_time_64 = static_cast<int64_t>(cltt)
                             + static_cast<int64_t>(valid_lifetime);

    // It has been observed that the PostgreSQL doesn't deal well with the
    // timestamp values beyond the DataSource::MAX_DB_TIME seconds since the
    // beginning of the epoch (around year 2038). The value is often
    // stored in the database but it is invalid when read back (overflow?).
    // Hence, the maximum timestamp value is restricted here.
    if (expire_time_64 > DatabaseConnection::MAX_DB_TIME) {
        isc_throw(isc::BadValue, "Time value is too large: " << expire_time_64);
    }

    return (convertToDatabaseTime(static_cast<time_t>(expire_time_64)));
}

133
time_t
134
135
136
137
138
139
140
141
142
143
144
145
146
PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val) {
    // Convert string time value to time_t
    time_t new_time;
    try  {
        new_time = (boost::lexical_cast<time_t>(db_time_val));
    } catch (const std::exception& ex) {
        isc_throw(BadValue, "Database time value is invalid: " << db_time_val);
    }

    return (new_time);
}

const char*
147
PgSqlExchange::getRawColumnValue(const PgSqlResult& r, const int row,
148
                                 const size_t col) {
149
150
151
    const char* value = PQgetvalue(r, row, col);
    if (!value) {
        isc_throw(DbOperationError, "getRawColumnValue no data for :"
152
                    << getColumnLabel(r, col) << " row:" << row);
153
154
155
156
    }
    return (value);
}

157
158
159
160
161
162
bool 
PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row, 
                            const size_t col) {
    return (PQgetisnull(r, row, col));
}

163
164
void
PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
165
166
167
168
169
170
171
                              const size_t col, std::string& value) {
    value = getRawColumnValue(r, row, col);
}

void
PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
                              const size_t col, bool &value) {
172
173
174
175
176
177
178
    const char* data = getRawColumnValue(r, row, col);
    if (!strlen(data) || *data == 'f') {
        value = false;
    } else if (*data == 't') {
        value = true;
    } else {
        isc_throw(DbOperationError, "Invalid boolean data: " << data
179
                  << " for: " << getColumnLabel(r, col) << " row:" << row
180
181
182
183
                  << " : must be 't' or 'f'");
    }
}

184
185
void
PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row,
186
                              const size_t col, uint8_t &value) {
187
188
189
190
191
192
193
    const char* data = getRawColumnValue(r, row, col);
    try {
        // lexically casting as uint8_t doesn't convert from char
        // so we use uint16_t and implicitly convert.
        value = boost::lexical_cast<uint16_t>(data);
    } catch (const std::exception& ex) {
        isc_throw(DbOperationError, "Invalid uint8_t data: " << data
194
                  << " for: " << getColumnLabel(r, col) << " row:" << row
195
196
197
198
                  << " : " << ex.what());
    }
}

199
200
201
202
203
204
205
206
207
208
209
210
211
isc::asiolink::IOAddress
PgSqlExchange::getIPv6Value(const PgSqlResult& r, const int row,
                            const size_t col) {
    const char* data = getRawColumnValue(r, row, col);
    try {
        return (isc::asiolink::IOAddress(data));
    } catch (const std::exception& ex) {
        isc_throw(DbOperationError, "Cannot convert data: " << data
                  << " for: " << getColumnLabel(r, col) << " row:" << row
                  << " : " << ex.what());
    }
}

212
213
214
215
void
PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
                                const size_t col, uint8_t* buffer,
                                const size_t buffer_size,
216
                                size_t &bytes_converted) {
217
218
219
220
221
222
223
224
225
    // Returns converted bytes in a dynamically allocated buffer, and
    // sets bytes_converted.
    unsigned char* bytes = PQunescapeBytea((const unsigned char*)
                                           (getRawColumnValue(r, row, col)),
                                           &bytes_converted);

    // Unlikely it couldn't allocate it but you never know.
    if (!bytes) {
        isc_throw (DbOperationError, "PQunescapeBytea failed for:"
226
                   << getColumnLabel(r, col) << " row:" << row);
227
228
229
230
231
232
233
234
    }

    // Make sure it's not larger than expected.
    if (bytes_converted > buffer_size) {
        // Free the allocated buffer first!
        PQfreemem(bytes);
        isc_throw (DbOperationError, "Converted data size: "
                   << bytes_converted << " is too large for: "
235
                   << getColumnLabel(r, col) << " row:" << row);
236
237
238
239
240
241
242
243
    }

    // Copy from the allocated buffer to caller's buffer the free up
    // the allocated buffer.
    memcpy(buffer, bytes, bytes_converted);
    PQfreemem(bytes);
}

244
#if 0
245
246
std::string
PgSqlExchange::getColumnLabel(const size_t column) const {
247
    if (column > columns_.size()) {
248
249
250
251
252
        std::ostringstream os;
        os << "Unknown column:" << column;
        return (os.str());
    }

253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
    return (columns_[column]);
}
#endif

std::string
PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
    const char* label = PQfname(r, column);
    if (!label) {
        std::ostringstream os;
        os << "Unknown column:" << column;
        return (os.str());
    }

    return (label);
}

std::string 
270
PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
271
    std::ostringstream stream;
272
    int columns = PQnfields(r);
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
    for (int col = 0; col < columns; ++col) {
        const char* val = getRawColumnValue(r, row, col);
        std::string name = getColumnLabel(r, col);
        int format = PQfformat(r, col); 

        stream << col << "   " << name << " : " ;
        if (format == PsqlBindArray::TEXT_FMT) {
            stream << "\"" << val << "\"" << std::endl;
        } else {
            const char *data = val;
            int length = PQfsize(r, col);
            if (length == 0) {
                stream << "empty" << std::endl;
            } else {
                stream << "0x";
                for (int i = 0; i < length; ++i) {
                    stream << std::setfill('0') << std::setw(2)
                           << std::setbase(16)
                           << static_cast<unsigned int>(data[i]);
                }
                stream << std::endl;
            }
        }
    }

    return (stream.str());
299
300
301
302
}

}; // end of isc::dhcp namespace
}; // end of isc namespace