diff options
Diffstat (limited to 'src/lib/pgsql/pgsql_exchange.cc')
-rw-r--r-- | src/lib/pgsql/pgsql_exchange.cc | 767 |
1 files changed, 767 insertions, 0 deletions
diff --git a/src/lib/pgsql/pgsql_exchange.cc b/src/lib/pgsql/pgsql_exchange.cc new file mode 100644 index 0000000..d203714 --- /dev/null +++ b/src/lib/pgsql/pgsql_exchange.cc @@ -0,0 +1,767 @@ +// Copyright (C) 2016-2022 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 <config.h> + +#include <pgsql/pgsql_exchange.h> +#include <exceptions/exceptions.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/lexical_cast.hpp> + +#include <iomanip> +#include <sstream> +#include <vector> + +using namespace isc::util; +using namespace isc::data; +using namespace boost::posix_time; + +namespace isc { +namespace db { + +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) { + if (!value) { + isc_throw(BadValue, "PsqlBindArray::add - char* value cannot be NULL"); + } + + 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::insert(const char* value, size_t index) { + if (index && index >= values_.size()) { + isc_throw(OutOfRange, "PsqlBindArray::insert - index: " << index + << ", is larger than the array size: " << values_.size()); + } + + values_.insert(values_.begin() + index, value); + lengths_.insert(lengths_.begin() + index, strlen(value)); + formats_.insert(formats_.begin() + index, TEXT_FMT); +} + +void PsqlBindArray::insert(const std::string& value, size_t index) { + if (index && index >= values_.size()) { + isc_throw(OutOfRange, "PsqlBindArray::insert - index: " << index + << ", is larger than the array size: " << values_.size()); + } + + bound_strs_.push_back(ConstStringPtr(new std::string(value))); + + values_.insert(values_.begin() + index, bound_strs_.back()->c_str()); + lengths_.insert(lengths_.begin() + index, value.size()); + formats_.insert(formats_.begin() + index, TEXT_FMT); +} + +void PsqlBindArray::popBack() { + if (values_.size() == 0) { + isc_throw(OutOfRange, "PsqlBindArray::pop_back - array empty"); + } + + values_.erase(values_.end() - 1); + lengths_.erase(lengths_.end() - 1); + formats_.erase(formats_.end() - 1); +} + +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); +} + +void PsqlBindArray::addTempBinary(const std::vector<uint8_t>& data) { + bound_strs_.push_back(ConstStringPtr(new std::string( + reinterpret_cast<const char*>(data.data()), data.size()))); + + values_.push_back(reinterpret_cast<const char*>(bound_strs_.back()->data())); + lengths_.push_back(data.size()); + formats_.push_back(BINARY_FMT); +} + +void PsqlBindArray::add(const uint8_t* data, const size_t len) { + if (!data) { + isc_throw(BadValue, "PsqlBindArray::add - uint8_t data cannot be NULL"); + } + + values_.push_back(reinterpret_cast<const char*>(&(data[0]))); + lengths_.push_back(len); + formats_.push_back(BINARY_FMT); +} + +void PsqlBindArray::addTempBuffer(const uint8_t* data, const size_t len) { + if (!data) { + isc_throw(BadValue, "PsqlBindArray::addTempBuffer - uint8_t data cannot be NULL"); + } + + bound_strs_.push_back(ConstStringPtr(new std::string( + reinterpret_cast<const char*>(data), len))); + + values_.push_back(bound_strs_.back()->data()); + lengths_.push_back(len); + formats_.push_back(BINARY_FMT); +} + +void PsqlBindArray::add(const bool& value) { + add(value ? TRUE_STR : FALSE_STR); +} + +void PsqlBindArray::add(const uint8_t& byte) { + // We static_cast to an unsigned int, otherwise lexical_cast may to + // treat byte as a character, which yields "" for unprintable values + addTempString(boost::lexical_cast<std::string> + (static_cast<unsigned int>(byte))); +} + +void PsqlBindArray::add(const isc::asiolink::IOAddress& addr) { + if (addr.isV4()) { + addTempString(boost::lexical_cast<std::string> + (addr.toUint32())); + } else { + addTempString(addr.toText()); + } +} + +void PsqlBindArray::addNull(const int format) { + values_.push_back(NULL); + lengths_.push_back(0); + formats_.push_back(format); +} + +void +PsqlBindArray::add(const Triplet<uint32_t>& triplet) { + if (triplet.unspecified()) { + addNull(); + } else { + add<uint32_t>(triplet.get()); + } +} + +void +PsqlBindArray::addMin(const Triplet<uint32_t>& triplet) { + if (triplet.unspecified() || (triplet.getMin() == triplet.get())) { + addNull(); + } else { + add<uint32_t>(triplet.getMin()); + } +} + +void +PsqlBindArray::addMax(const Triplet<uint32_t>& triplet) { + if (triplet.unspecified() || (triplet.getMax() == triplet.get())) { + addNull(); + } else { + add<uint32_t>(triplet.getMax()); + } +} + +/// @todo Eventually this could replace add(std::string&)? This would mean +/// all bound strings would be internally copies rather than perhaps belonging +/// to the originating object such as Host::hostname_. One the one hand it +/// would make all strings handled one-way only, on the other hand it would +/// mean duplicating strings where it isn't strictly necessary. +void PsqlBindArray::addTempString(const std::string& str) { + bound_strs_.push_back(ConstStringPtr(new std::string(str))); + + PsqlBindArray::add((bound_strs_.back())->c_str()); +} + +void +PsqlBindArray::addOptional(const util::Optional<std::string>& value) { + if (value.unspecified()) { + addNull(); + } else { + addTempString(value); + } +} + +void +PsqlBindArray::addInet4(const isc::asiolink::IOAddress& value) { + if (!value.isV4()) { + isc_throw(BadValue, "unable to add address to PsqlBindAray '" + << value.toText() << "' is not an IPv4 address"); + } + + // inet columns are inserted as string addresses. + addTempString(value.toText()); +} + +void +PsqlBindArray::addOptionalInet4(const util::Optional<isc::asiolink::IOAddress>& value) { + // If the value is unspecified it doesn't matter what the value is. + if (value.unspecified()) { + addNull(); + } else { + addInet4(value); + } +} + +void +PsqlBindArray::addInet6(const isc::asiolink::IOAddress& value) { + if (!value.isV6()) { + isc_throw(BadValue, "unable to add address to PsqlBindAray '" + << value.toText() << "' is not an IPv6 address"); + } + + // inet columns are inserted as string addresses. + addTempString(value.toText()); +} + +void +PsqlBindArray::addOptionalInet6(const util::Optional<isc::asiolink::IOAddress>& value) { + // If the value is unspecified it doesn't matter what the value is. + if (value.unspecified()) { + addNull(); + } else { + addInet6(value); + } +} + +void +PsqlBindArray::addTimestamp(const boost::posix_time::ptime& timestamp) { + // Convert the ptime to time_t, then use the existing conversion + // function to make db time. + // + // Sadly boost::posix_time::to_time_t() was not added until 1.58, + // so do it ourselves. + ptime epoch(boost::gregorian::date(1970, 1, 1)); + if (timestamp < epoch) { + isc_throw(isc::BadValue, "Time value is before the epoch"); + } + ptime max_db_time = boost::posix_time::from_time_t(DatabaseConnection::MAX_DB_TIME); + time_duration::sec_type since_epoch = (timestamp - epoch).total_seconds(); + time_t input_time(since_epoch); + if (timestamp > max_db_time) { + isc_throw(isc::BadValue, "Time value is too large: " << + (input_time < 0 ? + static_cast<int64_t>(static_cast<uint32_t>(input_time)) : + input_time)); + } + + // Converts to timestamp to local date/time string. + addTempString(PgSqlExchange::convertLocalToDatabaseTime(input_time)); +} + +void +PsqlBindArray::addTimestamp() { + time_t now; + time(&now); + addTempString(PgSqlExchange::convertLocalToDatabaseTime(now)); +} + +void +PsqlBindArray::add(const ElementPtr& value) { + if (!value) { + addNull(); + return; + } + + std::ostringstream ss; + value->toJSON(ss); + addTempString(ss.str()); +} + +void +PsqlBindArray::add(const ConstElementPtr& value) { + if (!value) { + addNull(); + return; + } + + std::ostringstream ss; + value->toJSON(ss); + addTempString(ss.str()); +} + +std::string +PsqlBindArray::toText() const { + std::ostringstream stream; + + if (values_.size() == 0) { + return ("bindarray is empty"); + } + + for (int i = 0; i < values_.size(); ++i) { + stream << i << " : "; + + if (lengths_[i] == 0) { + stream << "empty" << std::endl; + continue; + } + + if (formats_[i] == TEXT_FMT) { + stream << "\"" << values_[i] << "\"" << std::endl; + } else { + const char *data = values_[i]; + stream << "0x"; + for (int x = 0; x < lengths_[i]; ++x) { + stream << std::setfill('0') << std::setw(2) + << std::setbase(16) + << static_cast<unsigned int>(data[x]); + } + + stream << std::endl << std::setbase(10); + } + } + + return (stream.str()); +} + +bool +PsqlBindArray::amNull(size_t index) const { + if (values_.size() < index + 1) { + isc_throw(OutOfRange, "The index " << index << " is larger than the " + " array size " << values_.size()); + } + + // We assume lengths_.size() always equals values_.size(). If not, the + // at() operator will throw. + return ( (values_.at(index) == NULL) && (lengths_.at(index) == 0) ); +} + +std::string +PgSqlExchange::convertToDatabaseTime(const time_t input_time) { + struct tm tinfo; + char buffer[20]; + + localtime_r(&input_time, &tinfo); + + // PostgreSQL will assume the value is already in local time since we + // do not specify timezone in the string. + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo); + return (std::string(buffer)); +} + +std::string +PgSqlExchange::convertLocalToDatabaseTime(const time_t input_time) { + struct tm tinfo; + char buffer[20]; + + // We use gmtime_r to avoid adjustment as time_t is already local. + gmtime_r(&input_time, &tinfo); + + // PostgreSQL will assume the value is already in local time since we + // do not specify timezone in the string. + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tinfo); + return (std::string(buffer)); +} + + +std::string +PgSqlExchange::convertToDatabaseTime(const time_t cltt, + const uint32_t valid_lifetime) { + // Calculate expiry time. Store it in the 64-bit value so as we can + // 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))); +} + +time_t +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); +} + +void +PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val, + boost::posix_time::ptime& conv_time) { + time_t tmp_time = convertFromDatabaseTime(db_time_val); + conv_time = boost::posix_time::from_time_t(tmp_time); +} + +const char* +PgSqlExchange::getRawColumnValue(const PgSqlResult& r, const int row, + const size_t col) { + r.rowColCheck(row,col); + const char* value = PQgetvalue(r, row, col); + if (!value) { + isc_throw(DbOperationError, "getRawColumnValue no data for :" + << getColumnLabel(r, col) << " row:" << row); + } + return (value); +} + +bool +PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row, + const size_t col) { + r.rowColCheck(row,col); + return (PQgetisnull(r, row, col)); +} + +void +PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row, + 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) { + 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 + << " for: " << getColumnLabel(r, col) << " row:" << row + << " : must be 't' or 'f'"); + } +} + +void +PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row, + const size_t col, uint8_t &value) { + 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 + << " for: " << getColumnLabel(r, col) << " row:" << row + << " : " << ex.what()); + } +} + +void +PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row, + const size_t col, boost::posix_time::ptime& value) { + std::string db_time_val; + PgSqlExchange::getColumnValue(r, row, col, db_time_val ); + PgSqlExchange::convertFromDatabaseTime(db_time_val, value); +} + +void +PgSqlExchange::getColumnValue(const PgSqlResult& r, const int row, + const size_t col, ElementPtr& value) { + const char* data = getRawColumnValue(r, row, col); + try { + value = Element::fromJSON(data); + } catch (const std::exception& ex) { + isc_throw(DbOperationError, "Cannot convert data: " << data + << " for: " << getColumnLabel(r, col) << " row:" << row + << " : " << ex.what()); + } +} + +isc::asiolink::IOAddress +PgSqlExchange::getInetValue4(const PgSqlResult& r, const int row, + const size_t col) { + const char* data = getRawColumnValue(r, row, col); + try { + asiolink::IOAddress addr(data); + if (!addr.isV4()) { + isc_throw(BadValue, "not a v4 address"); + } + + return (addr); + } catch (const std::exception& ex) { + isc_throw(DbOperationError, "Cannot convert data: " << data + << " for: " << getColumnLabel(r, col) << " row:" << row + << " : " << ex.what()); + } +} + +isc::asiolink::IOAddress +PgSqlExchange::getInetValue6(const PgSqlResult& r, const int row, + const size_t col) { + const char* data = getRawColumnValue(r, row, col); + try { + asiolink::IOAddress addr(data); + if (!addr.isV6()) { + isc_throw(BadValue, "not a v6 address"); + } + + return (addr); + } catch (const std::exception& ex) { + isc_throw(DbOperationError, "Cannot convert data: " << data + << " for: " << getColumnLabel(r, col) << " row:" << row + << " : " << ex.what()); + } +} + +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()); + } +} + +void +PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row, + const size_t col, uint8_t* buffer, + const size_t buffer_size, + size_t &bytes_converted) { + // 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:" + << getColumnLabel(r, col) << " row:" << row); + } + + // 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: " + << getColumnLabel(r, col) << " row:" << row); + } + + // Copy from the allocated buffer to caller's buffer then free + // the allocated buffer. + memcpy(buffer, bytes, bytes_converted); + PQfreemem(bytes); +} + +void +PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row, const size_t col, + std::vector<uint8_t>& value) { + // Returns converted bytes in a dynamically allocated buffer, and + // sets bytes_converted. + size_t bytes_converted = 0; + 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:" + << getColumnLabel(r, col) << " row:" << row); + } + + // Copy from the allocated buffer to caller's buffer then free + // the allocated buffer. + if (bytes_converted) { + value.assign(bytes, bytes + bytes_converted); + } else { + value.clear(); + } + + // Free the PostgreSQL buffer. + PQfreemem(bytes); +} + +Triplet<uint32_t> +PgSqlExchange::getTripletValue(const PgSqlResult& r, const int row, + const size_t col) { + uint32_t col_value; + if (isColumnNull(r, row, col)) { + return (Triplet<uint32_t>()); + } + + getColumnValue(r, row, col, col_value); + return (Triplet<uint32_t>(col_value)); +} + +Triplet<uint32_t> +PgSqlExchange::getTripletValue(const PgSqlResult& r, const int row, + const size_t def_col, const size_t min_col, + const size_t max_col) { + if (isColumnNull(r, row, def_col)) { + return (Triplet<uint32_t>()); + } + + uint32_t value; + getColumnValue(r, row, def_col, value); + + uint32_t min_value = value; + if (!isColumnNull(r, row, min_col)) { + getColumnValue(r, row, min_col, min_value); + } + + uint32_t max_value = value; + if (!isColumnNull(r, row, max_col)) { + getColumnValue(r, row, max_col, max_value); + } + + return (Triplet<uint32_t>(min_value, value, max_value)); +} + +std::string +PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) { + return (r.getColumnLabel(column)); +} + +std::string +PgSqlExchange::dumpRow(const PgSqlResult& r, int row) { + r.rowCheck(row); + std::ostringstream stream; + int columns = r.getCols(); + for (int col = 0; col < columns; ++col) { + const char* val = getRawColumnValue(r, row, col); + std::string name = r.getColumnLabel(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()); +} + +PgSqlResultRowWorker::PgSqlResultRowWorker(const PgSqlResult& r, const int row) + : r_(r), row_(row) { + // Validate the desired row. + r.rowCheck(row); +} + +bool +PgSqlResultRowWorker::isColumnNull(const size_t col) { + return (PgSqlExchange::isColumnNull(r_, row_, col)); +} + +std::string +PgSqlResultRowWorker::getString(const size_t col) { + std::string tmp; + PgSqlExchange::getColumnValue(r_, row_, col, tmp); + return (tmp); +} + +bool +PgSqlResultRowWorker::getBool(const size_t col) { + bool tmp; + PgSqlExchange::getColumnValue(r_, row_, col, tmp); + return (tmp); +} + +double +PgSqlResultRowWorker::getDouble(const size_t col) { + double tmp; + PgSqlExchange::getColumnValue(r_, row_, col, tmp); + return (tmp); +} + +const char* +PgSqlResultRowWorker::getRawColumnValue(const size_t col) { + return (PgSqlExchange::getRawColumnValue(r_, row_, col)); +} + +uint64_t +PgSqlResultRowWorker::getBigInt(const size_t col) { + uint64_t value; + PgSqlExchange::getColumnValue(r_, row_, col, value); + return (value); +} + +uint32_t +PgSqlResultRowWorker::getInt(const size_t col) { + uint32_t value; + PgSqlExchange::getColumnValue(r_, row_, col, value); + return (value); +} + +uint16_t +PgSqlResultRowWorker::getSmallInt(const size_t col) { + uint16_t value; + PgSqlExchange::getColumnValue(r_, row_, col, value); + return (value); +} + +void +PgSqlResultRowWorker::getBytes(const size_t col, std::vector<uint8_t>& value) { + PgSqlExchange::convertFromBytea(r_, row_, col, value); +} + +isc::asiolink::IOAddress +PgSqlResultRowWorker::getInet4(const size_t col) { + return (PgSqlExchange::getInetValue4(r_, row_, col)); +} + +isc::asiolink::IOAddress +PgSqlResultRowWorker::getInet6(const size_t col) { + return (PgSqlExchange::getInetValue6(r_, row_, col)); +} + +boost::posix_time::ptime +PgSqlResultRowWorker::getTimestamp(const size_t col) { + boost::posix_time::ptime value; + getColumnValue(col, value); + return (value); +}; + +data::ElementPtr +PgSqlResultRowWorker::getJSON(const size_t col) { + data::ElementPtr value; + getColumnValue(col, value); + return (value); +} + +isc::util::Triplet<uint32_t> +PgSqlResultRowWorker::getTriplet(const size_t col) { + return (PgSqlExchange::getTripletValue(r_, row_, col)); +} + +isc::util::Triplet<uint32_t> +PgSqlResultRowWorker::getTriplet(const size_t def_col, const size_t min_col, + const size_t max_col) { + return (PgSqlExchange::getTripletValue(r_, row_, def_col, min_col, max_col)); +} + +std::string +PgSqlResultRowWorker::dumpRow() { + return (PgSqlExchange::dumpRow(r_, row_)); +} + +} // end of isc::db namespace +} // end of isc namespace |