void
PgSQLHostMgrTest::TearDown() {
- HostMgr::instance().getHostDataSource()->rollback();
+ try {
+ HostMgr::instance().getHostDataSource()->rollback();
+ } catch(...) {
+ // we don't care if we aren't in a transaction.
+ }
+
HostMgr::delBackend("postgresql");
// If data wipe enabled, delete transient data otherwise destroy the schema
db::test::destroyPgSQLSchema();
#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>
PsqlBindArray::add((bound_strs_.back())->c_str());
}
-std::string PsqlBindArray::toText() const {
- std::ostringstream stream;
- for (int i = 0; i < values_.size(); ++i) {
- stream << i << " : ";
- stream << toText(i) << std::endl;
+void
+PsqlBindArray::addOptionalString(const util::Optional<std::string>& value) {
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ add(value);
}
-
- return (stream.str());
}
-std::string
-PsqlBindArray::toText(size_t index) const {
- if (index >= values_.size()) {
- // We don't throw to keep this exception safe for logging.
- return(std::string("<index-out-of-range>"));
+void
+PsqlBindArray::addOptionalBool(const util::Optional<bool>& value) {
+ if (value.unspecified()) {
+ addNull();
+ } else {
+ add(value);
}
+}
+
+void
+PsqlBindArray::addOptionalIPv4Address(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 {
+ // Make sure it is an IPv4 address.
+ if (!value.get().isV4()) {
+ isc_throw(BadValue, "unable to add address to PsqlBindAray '"
+ << value.get().toText() << "' is not an IPv4 address");
+ }
- if (lengths_[index] == 0) {
- return(std::string("empty"));
+ add((value.get().toUint32()));
}
+}
+
+void
+PsqlBindArray::addTimestamp(const boost::posix_time::ptime& timestamp) {
+ time_t input_time = boost::posix_time::to_time_t(timestamp);
+ // Converts to timestamp to local date/time string.
+ addTempString(PgSqlExchange::convertToDatabaseTime(input_time));
+}
+
+void
+PsqlBindArray::addTimestamp() {
+ time_t now;
+ time(&now);
+ addTempString(PgSqlExchange::convertToDatabaseTime(now));
+}
+std::string
+PsqlBindArray::toText() const {
std::ostringstream stream;
- if (formats_[index] == TEXT_FMT) {
- stream << "\"" << values_[index] << "\"";
- } else {
- const char *data = values_[index];
- stream << "0x";
- for (int x = 0; x < lengths_[index]; ++x) {
- stream << std::setfill('0') << std::setw(2)
- << std::setbase(16)
- << static_cast<unsigned int>(data[x]);
+
+ for (int i = 0; i < values_.size(); ++i) {
+ stream << i << " : ";
+
+ if (lengths_[i] == 0) {
+ stream << "empty" << std::endl;
+ continue;
}
- stream << std::setbase(10);
+ 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());
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));
}
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) {
#include <asiolink/io_address.h>
#include <database/database_connection.h>
#include <util/triplet.h>
+#include <util/boost_time_utils.h>
#include <exceptions/exceptions.h>
#include <boost/lexical_cast.hpp>
/// @param triple Triplet instance from which to get the value.
void addMax(const isc::util::Triplet<uint32_t>& triplet);
- /// @brief Adds an @c Optional of integer type to 0the bind array.
+ /// @brief Adds an @c Optional string to the bind array.
+ ///
+ /// @param value Optional string value to add
+ void addOptionalString(const util::Optional<std::string>& value);
+
+ /// @brief Adds an @c Optional boolean to the bind array.
+ ///
+ /// @param value Optional boolean value to add
+ void addOptionalBool(const util::Optional<bool>& value);
+
+ /// @brief Adds an @c Optional of integer type to the bind array.
///
/// @tparam T Numeric type corresponding to the binding type, e.g.
/// @c uint8_t, @c uint16_t etc.
}
}
+ /// @brief Adds an @c Optional IPv4 address to the bind array.
+ ///
+ /// @param value Optional boolean value to add
+ /// @throw BadValue if the address is not a IPv4 address.
+ void addOptionalIPv4Address(const util::Optional<isc::asiolink::IOAddress>& value);
+
+ /// @brief Adds a timestamp from a ptime to the bind array.
+ ///
+ /// Precision is seconds.
+ ///
+ /// @param timestamp Timestamp value to be sent to the database.
+ void addTimestamp(const boost::posix_time::ptime& timestamp);
+
+ /// @brief Adds a timestamp of the current time to the bind array.
+ ///
+ /// Precision is seconds.
+ void addTimestamp();
+
/// @brief Dumps the contents of the array to a string.
/// @return std::string containing the dump
std::string toText() const;
- /// @brief Dumps the contents of an array element's value to a string.
- /// @return std::string containing the dump
- std::string toText(size_t index) const;
-
// --- the following methods are mostly useful for testing -----
/// @brief Determines if specified value is null
/// @return Converted timestamp as time_t value.
static time_t convertFromDatabaseTime(const std::string& db_time_val);
+ /// @brief Converts time stamp from the database to a boost::posix::ptime
+ ///
+ /// 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.
+ /// @param[out] conv_time resulting time as a ptime (UTC)
+ static void convertFromDatabaseTime(const std::string& db_time_val,
+ boost::posix_time::ptime& conv_time);
+
/// @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
" id, bool_col, bytea_col, bigint_col, smallint_col, "
" int_col, text_col,"
" extract(epoch from timestamp_col)::bigint as timestamp_col,"
- " varchar_col FROM basics";
+ " varchar_col"
+ " FROM basics";
runSql(r, sql, PGRES_TUPLES_OK, line);
ASSERT_EQ(r->getRows(), exp_rows) << "fetch at line: " << line
using namespace isc::db;
using namespace isc::db::test;
using namespace isc::util;
+using namespace boost::posix_time;
+using namespace boost::gregorian;
namespace {
Triplet<uint32_t> empty;
Triplet<uint32_t> not_empty(1,2,3);
- // Add an unspecified triplet value.
+ // Add triplets to the array.
b.add(empty);
b.add(not_empty);
b.addMin(empty);
// We've left bind scope, everything should be intact.
EXPECT_EQ(6, b.size());
+
+ // Verify contents are correct.
std::string expected =
"0 : empty\n"
"1 : \"2\"\n"
EXPECT_EQ(expected, b.toText());
}
+/// @brief Verifies the ability to add Optional strings to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalString) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<std::string> empty;
+ Optional<std::string> not_empty("whoopee!");
+
+ // Add strings to the array.
+ b.addOptionalString(empty);
+ b.addOptionalString(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"whoopee!\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+
+/// @brief Verifies the ability to add Optional booleans to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalBool) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<bool> empty;
+ Optional<bool> am_false(false);
+ Optional<bool> am_true(true);
+
+ // Add booleans to the array.
+ b.addOptionalBool(empty);
+ b.addOptionalBool(am_false);
+ b.addOptionalBool(am_true);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(3, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"0\"\n"
+ "2 : \"1\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
+
+
/// @brief Verifies the ability to add OptionalIntegers to
/// the bind array.
TEST(PsqlBindArray, addOptionalInteger) {
Optional<uint32_t> empty;
Optional<uint32_t> not_empty(123);
- ASSERT_TRUE(empty.unspecified());
-
- // Add an unspecified triplet value.
+ // Add the integers to the array..
b.addOptionalInteger(empty);
b.addOptionalInteger(not_empty);
}
// We've left bind scope, everything should be intact.
EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
std::string expected =
"0 : empty\n"
"1 : \"123\"\n";
EXPECT_EQ(expected, b.toText());
}
+/// @brief Verifies the ability to add Optional IPv4 addresses to
+/// the bind array.
+TEST(PsqlBindArray, addOptionalIPv4Address) {
+
+ PsqlBindArray b;
+
+ // Add all the items within a different scope. Everything should
+ // still be valid once we exit this scope.
+ {
+ Optional<asiolink::IOAddress> empty;
+ Optional<asiolink::IOAddress> not_empty(asiolink::IOAddress("192.16.1.1"));
+
+ // Verify we cannot add a v6 address.
+ Optional<asiolink::IOAddress> not_v4(asiolink::IOAddress("3001::1"));
+ ASSERT_THROW_MSG(b.addOptionalIPv4Address(not_v4), BadValue,
+ "unable to add address to PsqlBindAray"
+ " '3001::1' is not an IPv4 address");
+
+ // Add the addresses to the array..
+ b.addOptionalInteger(empty);
+ b.addOptionalInteger(not_empty);
+ }
+
+ // We've left bind scope, everything should be intact.
+ EXPECT_EQ(2, b.size());
+
+ // Verify contents are correct.
+ std::string expected =
+ "0 : empty\n"
+ "1 : \"192.16.1.1\"\n";
+
+ EXPECT_EQ(expected, b.toText());
+}
/// @brief Verifies that PgResultSet row and column meta-data is correct
TEST_F(PgSqlBasicsTest, rowColumnBasics) {
MAX_DB_TIME), BadValue);
}
+/// @brief Verify that we can read and write ptime using TIMESTAMP columns.
+TEST_F(PgSqlBasicsTest, ptimeTimestamp) {
+ // Create a prepared statement for inserting a TIMESTAMP
+ PgSqlTaggedStatement statement[] = {
+ { 1, { OID_TIMESTAMP }, "timestamp_insert",
+ "INSERT INTO BASICS (timestamp_col) values ($1)" }
+ };
+
+ ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
+
+ time_duration duration = hours(10) + minutes(14) + seconds(15);
+
+ // US National Ice Cream day
+ ptime nice_day(date(2021, Jul, 18), duration);
+
+ // Add timestamp with default/fractional seconds.
+ PsqlBindArrayPtr bind_array(new PsqlBindArray());
+ bind_array->addTimestamp(nice_day);
+ std::cout << "bind array: " << bind_array->toText() << std::endl;
+
+ PgSqlResultPtr r;
+ RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
+
+ // Fetch the newly inserted row.
+ FETCH_ROWS(r, 1);
+
+ // Fetch the timestamp column
+ ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, 0, TIMESTAMP_COL));
+ std::string timestamp_str = "";
+ ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, 0, TIMESTAMP_COL,
+ timestamp_str));
+
+ // Convert fetched values into a ptime.
+ ptime fetched_time;
+ ASSERT_NO_THROW(PgSqlExchange::convertFromDatabaseTime(timestamp_str, fetched_time));
+
+ ASSERT_EQ(fetched_time, nice_day);
+}
+
}; // namespace