Commit 3d405228 authored by Thomas Markwalder's avatar Thomas Markwalder
Browse files

[4277] Cleanup and commentary

parent 3466f650
......@@ -41,8 +41,7 @@ PgSqlTransaction::PgSqlTransaction(PgSqlConnection& conn)
}
PgSqlTransaction::~PgSqlTransaction() {
// Rollback if the PgSqlTransaction::commit wasn't explicitly
// called.
// If commit() wasn't explicitly called, rollback.
if (!committed_) {
conn_.rollback();
}
......@@ -218,7 +217,7 @@ PgSqlConnection::startTransaction() {
PgSqlResult r(PQexec(conn_, "START TRANSACTION"));
if (PQresultStatus(r) != PGRES_COMMAND_OK) {
const char* error_message = PQerrorMessage(conn_);
isc_throw(DbOperationError, "unable to start transaction"
isc_throw(DbOperationError, "unable to start transaction"
<< error_message);
}
}
......
......@@ -82,10 +82,17 @@ class PgSqlResult : public boost::noncopyable {
public:
/// @brief Constructor
///
/// Store the pointer to the result set to being fetched.
/// Store the pointer to the result set to being fetched. Set row
/// and column counts for convenience.
///
PgSqlResult(PGresult *result) : result_(result)
{}
PgSqlResult(PGresult *result) : result_(result), rows_(0), cols_(0) {
if (!result) {
isc_throw (BadValue, "PgSqlResult result pointer cannot be null");
}
rows_ = PQntuples(result);
cols_ = PQnfields(result);
}
/// @brief Destructor
///
......@@ -96,6 +103,50 @@ public:
}
}
/// @brief Returns the number of rows in the result set.
int getRows() const {
return (rows_);
}
/// @brief Returns the number of columns in the result set.
int getCols() const {
return (cols_);
}
/// @brief Determines if a row index is valid
///
/// @param row index to range check
///
/// @throw throws DbOperationError if the row index is out of range
void rowCheck(int row) const {
if (row >= rows_) {
isc_throw (DbOperationError, "row: " << row << ", out of range: 0.." << rows_);
}
}
/// @brief Determines if a column index is valid
///
/// @param col index to range check
///
/// @throw throws DbOperationError if the column index is out of range
void colCheck(int col) const {
if (col >= cols_) {
isc_throw (DbOperationError, "col: " << col << ", out of range: 0.." << cols_);
}
}
/// @brief Determines if both a row and column index are valid
///
/// @param row index to range check
/// @param col index to range check
///
/// @throw throws DbOperationError if either the row or column index
/// is out of range
void rowColCheck(int row, int col) const {
rowCheck(row);
colCheck(col);
}
/// @brief Conversion Operator
///
/// Allows the PgSqlResult object to be passed as the result set argument to
......@@ -113,6 +164,8 @@ public:
private:
PGresult* result_; ///< Result set to be freed
int rows_; ///< Number of rows in the result set
int cols_; ///< Number of columns in the result set
};
......@@ -181,45 +234,50 @@ private:
/// @brief Forward declaration to @ref PgSqlConnection.
class PgSqlConnection;
/// @brief RAII object representing PostgreSQL transaction.
/// @brief RAII object representing a PostgreSQL transaction.
///
/// An instance of this class should be created in a scope where multiple
/// INSERT statements should be executed within a single transaction. The
/// transaction is started when the constructor of this class is invoked.
/// The transaction is ended when the @ref PgSqlTransaction::commit is
/// explicitly called or when the instance of this class is destroyed.
/// The @ref PgSqlTransaction::commit commits changes to the database
/// and the changes remain in the database when the instance of the
/// class is destroyed. If the class instance is destroyed before the
/// @ref PgSqlTransaction::commit is called, the transaction is rolled
/// back. The rollback on destruction guarantees that partial data is
/// not stored in the database when there is an error during any
/// of the operations belonging to a transaction.
/// The @ref PgSqlTransaction::commit commits changes to the database.
/// If the class instance is destroyed before @ref PgSqlTransaction::commit
/// has been called, the transaction is rolled back. The rollback on
/// destruction guarantees that partial data is not stored in the database
/// when an error occurs during any of the operations within a transaction.
///
/// The default PostgreSQL backend configuration enables 'autocommit'.
/// Starting a transaction overrides 'autocommit' setting for this
/// particular transaction only. It does not affect the global 'autocommit'
/// setting for the database connection, i.e. all modifications to the
/// database which don't use transactions will still be auto committed.
/// By default PostgreSQL performs a commit following each statement which
/// alters the database (i.e. "autocommit"). Starting a transaction
/// stops autocommit for the connection until the transaction is ended by
/// either commit or rollback. Other connections are unaffected.
class PgSqlTransaction : public boost::noncopyable {
public:
/// @brief Constructor.
///
/// Starts transaction by making a "START TRANSACTION" query.
/// Starts transaction by executing the SQL statement: "START TRANSACTION"
///
/// @param conn PostgreSQL connection to use for the transaction. This
/// connection will be later used to commit or rollback changes.
///
/// @throw DbOperationError if "START TRANSACTION" query fails.
/// @throw DbOperationError if statement execution fails
PgSqlTransaction(PgSqlConnection& conn);
/// @brief Destructor.
///
/// Rolls back the transaction if changes haven't been committed.
/// If the transaction has not been committed, it is rolled back
/// by executing the SQL statement: "ROLLBACK"
///
/// @throw DbOperationError if statement execution fails
~PgSqlTransaction();
/// @brief Commits transaction.
///
/// Commits all changes made during the transaction by executing the
/// SQL statement: "COMMIT">
///
/// @throw DbOperationError if statement execution fails
void commit();
private:
......
......@@ -51,16 +51,16 @@ void PsqlBindArray::add(const bool& value) {
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>
addTempString(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>
addTempString(boost::lexical_cast<std::string>
(static_cast<uint32_t>(addr)));
} else {
bindString(addr.toText());
addTempString(addr.toText());
}
}
......@@ -70,9 +70,9 @@ void PsqlBindArray::addNull(const int format) {
formats_.push_back(format);
}
// eventually this should replace add(std::string)
void PsqlBindArray::bindString(const std::string& str) {
bound_strs_.push_back(StringPtr(new std::string(str)));
// Eventually this could replace add(std::string&) ?
void PsqlBindArray::addTempString(const std::string& str) {
bound_strs_.push_back(ConstStringPtr(new std::string(str)));
PsqlBindArray::add((bound_strs_.back())->c_str());
}
......@@ -94,6 +94,7 @@ std::string PsqlBindArray::toText() const {
<< static_cast<unsigned int>(data[x]);
}
stream << std::endl;
stream << std::setbase(10);
}
}
}
......@@ -146,6 +147,7 @@ PgSqlExchange::convertFromDatabaseTime(const std::string& db_time_val) {
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 :"
......@@ -157,6 +159,7 @@ PgSqlExchange::getRawColumnValue(const PgSqlResult& r, const int row,
bool
PgSqlExchange::isColumnNull(const PgSqlResult& r, const int row,
const size_t col) {
r.rowColCheck(row,col);
return (PQgetisnull(r, row, col));
}
......@@ -241,21 +244,9 @@ PgSqlExchange::convertFromBytea(const PgSqlResult& r, const int row,
PQfreemem(bytes);
}
#if 0
std::string
PgSqlExchange::getColumnLabel(const size_t column) const {
if (column > columns_.size()) {
std::ostringstream os;
os << "Unknown column:" << column;
return (os.str());
}
return (columns_[column]);
}
#endif
std::string
PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
r.colCheck(column);
const char* label = PQfname(r, column);
if (!label) {
std::ostringstream os;
......@@ -268,8 +259,9 @@ PgSqlExchange::getColumnLabel(const PgSqlResult& r, const size_t column) {
std::string
PgSqlExchange::dumpRow(const PgSqlResult& r, int row) {
r.rowCheck(row);
std::ostringstream stream;
int columns = PQnfields(r);
int columns = r.getCols();
for (int col = 0; col < columns; ++col) {
const char* val = getRawColumnValue(r, row, col);
std::string name = getColumnLabel(r, col);
......
......@@ -30,10 +30,16 @@ namespace dhcp {
/// 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.
/// @brief smart pointer to strings used by PsqlBindArray to ensure scope
/// of strings supplying exchange values
typedef boost::shared_ptr<std::string> StringPtr;
///
/// 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.
......@@ -71,8 +77,9 @@ struct PsqlBindArray {
/// @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. Note that value is expected to be NULL
/// terminated.
/// 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.
void add(const char* value);
......@@ -80,7 +87,9 @@ struct PsqlBindArray {
/// @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.
/// 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);
......@@ -103,54 +112,59 @@ struct PsqlBindArray {
/// is destroyed.
///
/// @param data buffer of binary data.
/// @param len number of bytes of data in buffer
/// @param len number of bytes of data in buffer
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 bool value to add.
/// @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 value bool value to add.
/// @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
/// 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 value bool value to add.
/// @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 its corresponding string literal
/// 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 bool value to add.
/// @param value data value to add.
template<typename T>
void add(const T& numeric) {
bindString(boost::lexical_cast<std::string>(numeric));
void add(const T& value) {
addTempString(boost::lexical_cast<std::string>(value));
}
/// @brief Binds a the given string to the bind array.
///
/// Prior to added the The given string the vector of exchange values,
/// it duplicated as a StringPtr and saved internally. This garauntees
/// it duplicated as a ConstStringPtr and saved internally. This garauntees
/// the string remains in scope until the PsqlBindArray is destroyed,
/// without the caller maintaining the string values.
/// without the caller maintaining the string values.
///
/// @param value bool value to add.
void bindString(const std::string& str);
/// @param str string value to add.
void addTempString(const std::string& str);
/// @brief Adds a NULL value to the bind array
///
......@@ -159,7 +173,7 @@ struct PsqlBindArray {
void addNull(const int format = PsqlBindArray::TEXT_FMT);
//std::vector<const std::string> getBoundStrs() {
std::vector<StringPtr> getBoundStrs() {
std::vector<ConstStringPtr> getBoundStrs() {
return (bound_strs_);
}
......@@ -169,7 +183,7 @@ struct PsqlBindArray {
private:
/// @brief vector of strings which supplied the values
std::vector<StringPtr> bound_strs_;
std::vector<ConstStringPtr> bound_strs_;
};
......@@ -260,7 +274,7 @@ public:
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
static void getColumnValue(const PgSqlResult& r, const int row,
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.
......@@ -272,7 +286,7 @@ public:
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
static void getColumnValue(const PgSqlResult& r, const int row,
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.
......@@ -284,20 +298,34 @@ public:
///
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
static void getColumnValue(const PgSqlResult& r, const int row,
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);
static bool isColumnNull(const PgSqlResult& r, const int row,
/// @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
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.
/// a value of type T.
///
/// @param r the result set containing the query results
/// @param row the row number within the result set
......@@ -307,14 +335,14 @@ public:
/// @throw DbOperationError if the value cannot be fetched or is
/// invalid.
template<typename T>
static void getColumnValue(const PgSqlResult& r, const int row,
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(DbOperationError, "Invalid data:[" << data
<< "] for row: " << row << " col: " << col << ","
<< "] for row: " << row << " col: " << col << ","
<< getColumnLabel(r, col) << " : " << ex.what());
}
}
......@@ -335,12 +363,15 @@ public:
///
/// @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,
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
static std::string dumpRow(const PgSqlResult& r, int row);
protected:
......
This diff is collapsed.
......@@ -50,8 +50,6 @@ public:
PgSqlHostDataSource(const DatabaseConnection::ParameterMap& parameters);
/// @brief Virtual destructor.
///
/// Releases prepared MySQL statements used by the backend.
virtual ~PgSqlHostDataSource();
/// @brief Return all hosts for the specified HW address or DUID.
......@@ -216,7 +214,7 @@ public:
///
/// @return Type of the backend.
virtual std::string getType() const {
return (std::string("mysql"));
return (std::string("postgresql"));
}
/// @brief Returns backend name.
......
......@@ -69,45 +69,71 @@ TEST(PgSqlExchangeTest, convertTimeTest) {
EXPECT_EQ(ref_time, from_time);
}
TEST(PsqlBindArray, basicOperation) {
/// @brief Verifies the ability to add various data types to
/// the bind array.
TEST(PsqlBindArray, addDataTest) {
PsqlBindArray b;
uint8_t small_int = 25;
b.add(small_int);
// Declare a vector to add. Vectors are not currently duplicated
// So they will go out of scope, unless caller ensures it.
std::vector<uint8_t> bytes;
for (int i = 0; i < 10; i++) {
bytes.push_back(i+1);
}
int reg_int = 376;
b.add(reg_int);
// Declare a string
std::string not_temp_str("just a string");
uint64_t big_int = 86749032;
b.add(big_int);
// Now add all the items within a different scope. Everything should
// still be valid once we exit this scope.
{
// Add a const char*
b.add("booya!");
b.add((bool)(1));
b.add((bool)(0));
// Add the non temporary string
b.add(not_temp_str);
b.add(isc::asiolink::IOAddress("192.2.15.34"));
b.add(isc::asiolink::IOAddress("3001::1"));
// Add a temporary string
b.addTempString("walah walah washington");
std::string str("just a string");
b.add(str);
// Add a one byte int
uint8_t small_int = 25;
b.add(small_int);
std::vector<uint8_t> bytes;
for (int i = 0; i < 10; i++) {
bytes.push_back(i+1);
// Add a four byte int
int reg_int = 376;
b.add(reg_int);
// Add a eight byte unsigned int
uint64_t big_int = 48786749032;
b.add(big_int);
// Add boolean true and false
b.add((bool)(1));
b.add((bool)(0));
// Add IP addresses
b.add(isc::asiolink::IOAddress("192.2.15.34"));
b.add(isc::asiolink::IOAddress("3001::1"));
// Add the vector
b.add(bytes);
}
b.add(bytes);
std::string expected =
"0 : \"25\"\n"
"1 : \"376\"\n"
"2 : \"86749032\"\n"
"3 : \"TRUE\"\n"
"4 : \"FALSE\"\n"
"5 : \"3221360418\"\n"
"6 : \"3001::1\"\n"
"7 : \"just a string\"\n"
"8 : 0x0102030405060708090a\n";
// We've left bind scope, everything should be intact.
std::string expected =
"0 : \"booya!\"\n"
"1 : \"just a string\"\n"
"2 : \"walah walah washington\"\n"
"3 : \"25\"\n"
"4 : \"376\"\n"
"5 : \"48786749032\"\n"
"6 : \"TRUE\"\n"
"7 : \"FALSE\"\n"
"8 : \"3221360418\"\n"
"9 : \"3001::1\"\n"
"10 : 0x0102030405060708090a\n";
EXPECT_EQ(expected, b.toText());
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment