123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923 |
- // Copyright (C) 2016-2017 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 <dhcpsrv/pgsql_connection.h>
- #include <dhcpsrv/pgsql_exchange.h>
- #include <boost/lexical_cast.hpp>
- #include <gtest/gtest.h>
- #include <sstream>
- #include <vector>
- using namespace isc;
- using namespace isc::dhcp;
- namespace {
- /// @brief Verifies the ability to add various data types to
- /// the bind array.
- TEST(PsqlBindArray, addDataTest) {
- PsqlBindArray b;
- // 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);
- }
- // Declare a string
- std::string not_temp_str("just a string");
- // 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!");
- // Add the non temporary string
- b.add(not_temp_str);
- // Add a temporary string
- b.addTempString("walah walah washington");
- // Add a one byte int
- uint8_t small_int = 25;
- b.add(small_int);
- // 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);
- }
- // 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());
- }
- /// @brief Defines a pointer to a PgSqlConnection
- typedef boost::shared_ptr<PgSqlConnection> PgSqlConnectionPtr;
- /// @brief Defines a pointer to a PgSqlResult
- typedef boost::shared_ptr<PgSqlResult> PgSqlResultPtr;
- /// @brief Fixture for exercising basic PostgreSQL operations and data types
- ///
- /// This class is intended to be used to verify basic operations and to
- /// verify that each PostgreSQL data type currently used by Kea, can be
- /// correctly written to and read from PostgreSQL. Rather than use tables
- /// that belong to Kea the schema proper, it creates its own. Currently it
- /// consists of a single table, called "basics" which contains one column for
- /// each of the supported data types.
- ///
- /// It creates the schema during construction, deletes it upon destruction, and
- /// provides functions for executing SQL statements, executing prepared
- /// statements, fetching all rows in the table, and deleting all the rows in
- /// the table.
- class PgSqlBasicsTest : public ::testing::Test {
- public:
- /// @brief Column index for each column
- enum BasicColIndex {
- ID_COL,
- BOOL_COL,
- BYTEA_COL,
- BIGINT_COL,
- SMALLINT_COL,
- INT_COL,
- TEXT_COL,
- TIMESTAMP_COL,
- VARCHAR_COL,
- NUM_BASIC_COLS
- };
- /// @brief Constructor
- ///
- /// Creates the database connection, opens the database, and destroys
- /// the table (if present) and then recreates it.
- PgSqlBasicsTest() : expectedColNames_(NUM_BASIC_COLS) {
- // Create database connection parameter list
- PgSqlConnection::ParameterMap params;
- params["name"] = "keatest";
- params["user"] = "keatest";
- params["password"] = "keatest";
- // Create and open the database connection
- conn_.reset(new PgSqlConnection(params));
- conn_->openDatabase();
- // Create the list of expected column names
- expectedColNames_[ID_COL] = "id";
- expectedColNames_[BOOL_COL] = "bool_col";
- expectedColNames_[BYTEA_COL] = "bytea_col";
- expectedColNames_[BIGINT_COL] = "bigint_col";
- expectedColNames_[SMALLINT_COL] = "smallint_col";
- expectedColNames_[INT_COL] = "int_col";
- expectedColNames_[TEXT_COL] = "text_col";
- expectedColNames_[TIMESTAMP_COL] = "timestamp_col";
- expectedColNames_[VARCHAR_COL] = "varchar_col";
- destroySchema();
- createSchema();
- }
- /// @brief Destructor
- ///
- /// Destroys the table. The database resources are freed and the connection
- /// closed by the destruction of conn_.
- virtual ~PgSqlBasicsTest () {
- destroySchema();
- }
- /// @brief Gets the expected name of the column for a given column index
- ///
- /// Returns the name of column as we expect it to be when the column is
- /// fetched from the database.
- ///
- /// @param col index of the desired column
- ///
- /// @return string containing the column name
- ///
- /// @throw BadValue if the index is out of range
- const std::string& expectedColumnName(int col) {
- if (col < 0 || col >= NUM_BASIC_COLS) {
- isc_throw(BadValue,
- "definedColunName: invalid column value" << col);
- }
- return (expectedColNames_[col]);
- }
- /// @brief Creates the basics table
- /// Asserts if the creation step fails
- void createSchema() {
- // One column for OID type, plus an auto-increment
- const char* sql =
- "CREATE TABLE basics ( "
- " id SERIAL PRIMARY KEY NOT NULL, "
- " bool_col BOOLEAN, "
- " bytea_col BYTEA, "
- " bigint_col BIGINT, "
- " smallint_col SMALLINT, "
- " int_col INT, "
- " text_col TEXT, "
- " timestamp_col TIMESTAMP WITH TIME ZONE, "
- " varchar_col VARCHAR(255) "
- "); ";
- PgSqlResult r(PQexec(*conn_, sql));
- ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
- << " create basics table failed: " << PQerrorMessage(*conn_);
- }
- /// @brief Destroys the basics table
- /// Asserts if the destruction fails
- void destroySchema() {
- if (conn_) {
- PgSqlResult r(PQexec(*conn_, "DROP TABLE IF EXISTS basics;"));
- ASSERT_EQ(PQresultStatus(r), PGRES_COMMAND_OK)
- << " drop basics table failed: " << PQerrorMessage(*conn_);
- }
- }
- /// @brief Executes a SQL statement and tests for an expected outcome
- ///
- /// @param r pointer which will contain the result set returned by the
- /// statement's execution.
- /// @param sql string containing the SQL statement text. Note that
- /// PostgreSQL supports executing text which contains more than one SQL
- /// statement separated by semicolons.
- /// @param exp_outcome expected status value returned with within the
- /// result set such as PGRES_COMMAND_OK, PGRES_TUPLES_OK.
- /// @lineno line number from where the call was invoked
- ///
- /// Asserts if the result set status does not equal the expected outcome.
- void runSql(PgSqlResultPtr& r, const std::string& sql, int exp_outcome,
- int lineno) {
- r.reset(new PgSqlResult(PQexec(*conn_, sql.c_str())));
- ASSERT_EQ(PQresultStatus(*r), exp_outcome)
- << " runSql at line: " << lineno << " failed, sql:[" << sql
- << "]\n reason: " << PQerrorMessage(*conn_);
- }
- /// @brief Executes a SQL statement and tests for an expected outcome
- ///
- /// @param r pointer which will contain the result set returned by the
- /// statement's execution.
- /// @param statement statement descriptor of the prepared statement
- /// to execute.
- /// @param bind_array bind array containing the input values to submit
- /// along with the statement
- /// @param exp_outcome expected status value returned with within the
- /// result set such as PGRES_COMMAND_OK, PGRES_TUPLES_OK.
- /// @lineno line number from where the call was invoked
- ///
- /// Asserts if the result set status does not equal the expected outcome.
- void runPreparedStatement(PgSqlResultPtr& r,
- PgSqlTaggedStatement& statement,
- PsqlBindArrayPtr bind_array, int exp_outcome,
- int lineno) {
- r.reset(new PgSqlResult(PQexecPrepared(*conn_, statement.name,
- statement.nbparams,
- &bind_array->values_[0],
- &bind_array->lengths_[0],
- &bind_array->formats_[0], 0)));
- ASSERT_EQ(PQresultStatus(*r), exp_outcome)
- << " runPreparedStatement at line: " << lineno
- << " statement name:[" << statement.name
- << "]\n reason: " << PQerrorMessage(*conn_);
- }
- /// @brief Fetches all of the rows currently in the table
- ///
- /// Executes a select statement which returns all of the rows in the
- /// basics table, in their order of insertion. Each row contains all
- /// of the defined columns, in the order they are defined.
- ///
- /// @param r pointer which will contain the result set returned by the
- /// statement's execution.
- /// @param exp_rows expected number of rows fetched. (This can be 0).
- /// @lineno line number from where the call was invoked
- ///
- /// Asserts if the result set status does not equal the expected outcome.
- void fetchRows(PgSqlResultPtr& r, int exp_rows, int line) {
- std::string sql =
- "SELECT"
- " 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";
- runSql(r, sql, PGRES_TUPLES_OK, line);
- ASSERT_EQ(r->getRows(), exp_rows) << "fetch at line: " << line
- << " wrong row count, expected: " << exp_rows
- << " , have: " << r->getRows();
- }
- /// @brief Database connection
- PgSqlConnectionPtr conn_;
- /// @brief List of column names as we expect them to be in fetched rows
- std::vector<std::string> expectedColNames_;
- };
- // Macros defined to ease passing invocation line number for output tracing
- // (Yes I could have used scoped tracing but that's so ugly in code...)
- #define RUN_SQL(a,b,c) (runSql(a,b,c, __LINE__))
- #define RUN_PREP(a,b,c,d) (runPreparedStatement(a,b,c,d, __LINE__))
- #define FETCH_ROWS(a,b) (fetchRows(a,b,__LINE__))
- #define WIPE_ROWS(a) (RUN_SQL(a, "DELETE FROM BASICS", PGRES_COMMAND_OK))
- /// @brief Verifies that PgResultSet row and colum meta-data is correct
- TEST_F(PgSqlBasicsTest, rowColumnBasics) {
- // We fetch the table contents, which at this point should be no rows.
- PgSqlResultPtr r;
- FETCH_ROWS(r, 0);
- // Column meta-data is determined by the select statement and is
- // present whether or not any rows were returned.
- EXPECT_EQ(r->getCols(), NUM_BASIC_COLS);
- // Negative indexes should be out of range. We test negative values
- // as PostgreSQL functions accept column values as type int.
- EXPECT_THROW(r->colCheck(-1), DbOperationError);
- // Iterate over the column indexes verifying:
- // 1. the column is valid
- // 2. the result set column name matches the expected column name
- for (int i = 0; i < NUM_BASIC_COLS; i++) {
- EXPECT_NO_THROW(r->colCheck(i));
- EXPECT_EQ(r->getColumnLabel(i), expectedColumnName(i));
- }
- // Verify above range column value is detected.
- EXPECT_THROW(r->colCheck(NUM_BASIC_COLS), DbOperationError);
- // Verify the fetching a column label for out of range columns
- // do NOT throw.
- std::string label;
- ASSERT_NO_THROW(label = r->getColumnLabel(-1));
- EXPECT_EQ(label, "Unknown column:-1");
- ASSERT_NO_THROW(label = r->getColumnLabel(NUM_BASIC_COLS));
- std::ostringstream os;
- os << "Unknown column:" << NUM_BASIC_COLS;
- EXPECT_EQ(label, os.str());
- // Verify row count and checking. With an empty result set all values of
- // row are invalid.
- EXPECT_EQ(r->getRows(), 0);
- EXPECT_THROW(r->rowCheck(-1), DbOperationError);
- EXPECT_THROW(r->rowCheck(0), DbOperationError);
- EXPECT_THROW(r->rowCheck(1), DbOperationError);
- // Verify Row-column check will always fail with an empty result set.
- EXPECT_THROW(r->rowColCheck(-1, 1), DbOperationError);
- EXPECT_THROW(r->rowColCheck(0, 1), DbOperationError);
- EXPECT_THROW(r->rowColCheck(1, 1), DbOperationError);
- // Insert three minimal rows. We don't really care about column content
- // for this test.
- int num_rows = 3;
- for (int i = 0; i < num_rows; i++) {
- RUN_SQL(r, "INSERT INTO basics (bool_col) VALUES ('t')",
- PGRES_COMMAND_OK);
- }
- // Fetch the newly created rows.
- FETCH_ROWS(r, num_rows);
- // Verify we row count and checking
- EXPECT_EQ(r->getRows(), num_rows);
- EXPECT_THROW(r->rowCheck(-1), DbOperationError);
- // Iterate over the row count, verifying that expected rows are valid
- for (int i = 0; i < num_rows; i++) {
- EXPECT_NO_THROW(r->rowCheck(i));
- EXPECT_NO_THROW(r->rowColCheck(i, 0));
- }
- // Verify an above range row is detected.
- EXPECT_THROW(r->rowCheck(num_rows), DbOperationError);
- }
- /// @brief Verify that we can read and write BOOL columns
- TEST_F(PgSqlBasicsTest, boolTest) {
- // Create a prepared statement for inserting bool_col
- const char* st_name = "bool_insert";
- PgSqlTaggedStatement statement[] = {
- {1, { OID_BOOL }, st_name,
- "INSERT INTO BASICS (bool_col) values ($1)" }
- };
- ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
- bool bools[] = { true, false };
- PsqlBindArrayPtr bind_array(new PsqlBindArray());
- PgSqlResultPtr r;
- // Insert bool rows
- for (int i = 0; i < 2; ++i) {
- bind_array.reset(new PsqlBindArray());
- bind_array->add(bools[i]);
- RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
- }
- // Fetch the newly inserted rows.
- FETCH_ROWS(r, 2);
- // Verify the fetched bool values are what we expect.
- bool fetched_bool;
- int row = 0;
- for ( ; row < 2; ++row ) {
- // Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BOOL_COL));
- // Fetch and verify the column value
- fetched_bool = !bools[row];
- ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, BOOL_COL,
- fetched_bool));
- EXPECT_EQ(fetched_bool, bools[row]);
- }
- // While we here, verify that bad row throws
- ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, 1, fetched_bool),
- DbOperationError);
- // Clean out the table
- WIPE_ROWS(r);
- // Verify we can insert a NULL boolean
- bind_array.reset(new PsqlBindArray());
- bind_array->addNull();
- // Run the insert with the bind array.
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted row.
- FETCH_ROWS(r, 1);
- // Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, 1));
- }
- /// @brief Verify that we can read and write BYTEA columns
- TEST_F(PgSqlBasicsTest, byteaTest) {
- const char* st_name = "bytea_insert";
- PgSqlTaggedStatement statement[] = {
- {1, { OID_BYTEA }, st_name,
- "INSERT INTO BASICS (bytea_col) values ($1)" }
- };
- ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
- const uint8_t bytes[] = {
- 0x01, 0x02, 0x03, 0x04
- };
- std::vector<uint8_t> vbytes(bytes, bytes + sizeof(bytes));
- // Verify we can insert bytea from a vector
- PsqlBindArrayPtr bind_array(new PsqlBindArray());
- PgSqlResultPtr r;
- bind_array->add(vbytes);
- RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
- // Verify we can insert bytea from a buffer.
- bind_array.reset(new PsqlBindArray());
- bind_array->add(bytes, sizeof(bytes));
- RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted rows.
- int num_rows = 2;
- FETCH_ROWS(r, num_rows);
- uint8_t fetched_bytes[sizeof(bytes)];
- size_t byte_count;
- int row = 0;
- for ( ; row < num_rows; ++row) {
- // Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BYTEA_COL));
- // Extract the data into a correctly sized buffer
- memset(fetched_bytes, 0, sizeof(fetched_bytes));
- ASSERT_NO_THROW(PgSqlExchange::convertFromBytea(*r, row, BYTEA_COL,
- fetched_bytes,
- sizeof(fetched_bytes),
- byte_count));
- // Verify the data is correct
- ASSERT_EQ(byte_count, sizeof(bytes));
- for (int i = 0; i < sizeof(bytes); i++) {
- ASSERT_EQ(bytes[i], fetched_bytes[i]);
- }
- }
- // While we here, verify that bad row throws
- ASSERT_THROW(PgSqlExchange::convertFromBytea(*r, row, BYTEA_COL,
- fetched_bytes,
- sizeof(fetched_bytes),
- byte_count), DbOperationError);
- // Verify that too small of a buffer throws
- ASSERT_THROW(PgSqlExchange::convertFromBytea(*r, 0, BYTEA_COL,
- fetched_bytes,
- sizeof(fetched_bytes) - 1,
- byte_count), DbOperationError);
- // Clean out the table
- WIPE_ROWS(r);
- // Verify we can insert a NULL for a bytea column
- bind_array.reset(new PsqlBindArray());
- bind_array->addNull(PsqlBindArray::BINARY_FMT);
- RUN_PREP(r,statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted row.
- FETCH_ROWS(r, 1);
- // Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, BYTEA_COL));
- // Verify that fetching a NULL bytea, returns 0 byte count
- ASSERT_NO_THROW(PgSqlExchange::convertFromBytea(*r, 0, BYTEA_COL,
- fetched_bytes,
- sizeof(fetched_bytes),
- byte_count));
- EXPECT_EQ(byte_count, 0);
- }
- /// @brief Verify that we can read and write BIGINT columns
- TEST_F(PgSqlBasicsTest, bigIntTest) {
- // Create a prepared statement for inserting BIGINT
- const char* st_name = "bigint_insert";
- PgSqlTaggedStatement statement[] = {
- { 1, { OID_INT8 }, st_name,
- "INSERT INTO BASICS (bigint_col) values ($1)" }
- };
- ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
- // Build our reference list of reference values
- std::vector<int64_t> ints;
- ints.push_back(-1);
- ints.push_back(0);
- ints.push_back(0x7fffffffffffffff);
- ints.push_back(0xffffffffffffffff);
- // Insert a row for each reference value
- PsqlBindArrayPtr bind_array;
- PgSqlResultPtr r;
- for (int i = 0; i < ints.size(); ++i) {
- bind_array.reset(new PsqlBindArray());
- bind_array->add(ints[i]);
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- }
- // Fetch the newly inserted rows.
- FETCH_ROWS(r, ints.size());
- // Iterate over the rows, verifying each value against its reference
- int64_t fetched_int;
- int row = 0;
- for ( ; row < ints.size(); ++row ) {
- // Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, BIGINT_COL));
- // Fetch and verify the column value
- fetched_int = 777;
- ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, BIGINT_COL,
- fetched_int));
- EXPECT_EQ(fetched_int, ints[row]);
- }
- // While we here, verify that bad row throws
- ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, BIGINT_COL,
- fetched_int), DbOperationError);
- // Clean out the table
- WIPE_ROWS(r);
- // Verify we can insert a NULL value.
- bind_array.reset(new PsqlBindArray());
- bind_array->addNull();
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted row.
- FETCH_ROWS(r, 1);
- // Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, BIGINT_COL));
- }
- /// @brief Verify that we can read and write SMALLINT columns
- TEST_F(PgSqlBasicsTest, smallIntTest) {
- // Create a prepared statement for inserting a SMALLINT
- const char* st_name = "smallint_insert";
- PgSqlTaggedStatement statement[] = {
- { 1, { OID_INT2 }, st_name,
- "INSERT INTO BASICS (smallint_col) values ($1)" }
- };
- ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
- // Build our reference list of reference values
- std::vector<int16_t>ints;
- ints.push_back(-1);
- ints.push_back(0);
- ints.push_back(0x7fff);
- ints.push_back(0xffff);
- // Insert a row for each reference value
- PsqlBindArrayPtr bind_array;
- PgSqlResultPtr r;
- for (int i = 0; i < ints.size(); ++i) {
- bind_array.reset(new PsqlBindArray());
- bind_array->add(ints[i]);
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- }
- // Fetch the newly inserted rows.
- FETCH_ROWS(r, ints.size());
- // Iterate over the rows, verifying each value against its reference
- int16_t fetched_int;
- int row = 0;
- for ( ; row < ints.size(); ++row ) {
- // Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, SMALLINT_COL));
- // Fetch and verify the column value
- fetched_int = 777;
- ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, SMALLINT_COL,
- fetched_int));
- EXPECT_EQ(fetched_int, ints[row]);
- }
- // While we here, verify that bad row throws
- ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, SMALLINT_COL,
- fetched_int),
- DbOperationError);
- // Clean out the table
- WIPE_ROWS(r);
- // Verify we can insert a NULL value.
- bind_array.reset(new PsqlBindArray());
- bind_array->addNull();
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted row.
- FETCH_ROWS(r, 1);
- // Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, SMALLINT_COL));
- }
- /// @brief Verify that we can read and write INT columns
- TEST_F(PgSqlBasicsTest, intTest) {
- // Create a prepared statement for inserting an INT
- const char* st_name = "int_insert";
- PgSqlTaggedStatement statement[] = {
- { 1, { OID_INT4 }, st_name,
- "INSERT INTO BASICS (int_col) values ($1)" }
- };
- ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
- // Build our reference list of reference values
- std::vector<int32_t> ints;
- ints.push_back(-1);
- ints.push_back(0);
- ints.push_back(0x7fffffff);
- ints.push_back(0xffffffff);
- // Insert a row for each reference value
- PsqlBindArrayPtr bind_array;
- PgSqlResultPtr r;
- for (int i = 0; i < ints.size(); ++i) {
- bind_array.reset(new PsqlBindArray());
- bind_array->add(ints[i]);
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- }
- // Fetch the newly inserted rows.
- FETCH_ROWS(r, ints.size());
- // Iterate over the rows, verifying each value against its reference
- int32_t fetched_int;
- int row = 0;
- for ( ; row < ints.size(); ++row ) {
- // Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, INT_COL));
- // Fetch and verify the column value
- fetched_int = 777;
- ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, INT_COL,
- fetched_int));
- EXPECT_EQ(fetched_int, ints[row]);
- }
- // While we here, verify that bad row throws
- ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, INT_COL, fetched_int),
- DbOperationError);
- // Clean out the table
- WIPE_ROWS(r);
- // Verify we can insert a NULL value.
- bind_array.reset(new PsqlBindArray());
- bind_array->addNull();
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted rows
- FETCH_ROWS(r, 1);
- // Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, INT_COL));
- }
- /// @brief Verify that we can read and write TEXT columns
- TEST_F(PgSqlBasicsTest, textTest) {
- // Create a prepared statement for inserting TEXT
- PgSqlTaggedStatement statement[] = {
- { 1, { OID_TEXT }, "text_insert",
- "INSERT INTO BASICS (text_col) values ($1)" }
- };
- ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
- // Our reference string.
- std::string ref_string = "This is a text string";
- // Insert the reference from std::string
- PsqlBindArrayPtr bind_array;
- PgSqlResultPtr r;
- bind_array.reset(new PsqlBindArray());
- bind_array->add(ref_string);
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Insert the reference from a buffer
- bind_array.reset(new PsqlBindArray());
- bind_array->add(ref_string.c_str());
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted rows.
- FETCH_ROWS(r, 2);
- // Iterate over the rows, verifying the value against the reference
- std::string fetched_str;
- int row = 0;
- for ( ; row < 2; ++row ) {
- // Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, TEXT_COL));
- // Fetch and verify the column value
- fetched_str = "";
- ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, TEXT_COL,
- fetched_str));
- EXPECT_EQ(fetched_str, ref_string);
- }
- // While we here, verify that bad row throws
- ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, TEXT_COL, fetched_str),
- DbOperationError);
- // Clean out the table
- WIPE_ROWS(r);
- // Verify we can insert a NULL value.
- bind_array.reset(new PsqlBindArray());
- bind_array->addNull();
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted row.
- FETCH_ROWS(r, 1);
- // Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, TEXT_COL));
- }
- /// @brief Verify that we can read and write VARCHAR columns
- TEST_F(PgSqlBasicsTest, varcharTest) {
- // Create a prepared statement for inserting a VARCHAR
- PgSqlTaggedStatement statement[] = {
- { 1, { OID_VARCHAR }, "varchar_insert",
- "INSERT INTO BASICS (varchar_col) values ($1)" }
- };
- ASSERT_NO_THROW(conn_->prepareStatement(statement[0]));
- // Our reference string.
- std::string ref_string = "This is a varchar string";
- // Insert the reference from std::string
- PsqlBindArrayPtr bind_array;
- PgSqlResultPtr r;
- bind_array.reset(new PsqlBindArray());
- bind_array->add(ref_string);
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Insert the reference from a buffer
- bind_array.reset(new PsqlBindArray());
- bind_array->add(ref_string.c_str());
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted rows.
- FETCH_ROWS(r, 2);
- // Iterate over the rows, verifying the value against the reference
- std::string fetched_str;
- int row = 0;
- for ( ; row < 2; ++row ) {
- // Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, VARCHAR_COL));
- // Fetch and verify the column value
- fetched_str = "";
- ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, VARCHAR_COL,
- fetched_str));
- EXPECT_EQ(fetched_str, ref_string);
- }
- // While we here, verify that bad row throws
- ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, VARCHAR_COL,
- fetched_str), DbOperationError);
- // Clean out the table
- WIPE_ROWS(r);
- // Verify we can insert a NULL value.
- bind_array.reset(new PsqlBindArray());
- bind_array->addNull();
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted rows
- FETCH_ROWS(r, 1);
- // Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, VARCHAR_COL));
- }
- /// @brief Verify that we can read and write TIMESTAMP columns
- TEST_F(PgSqlBasicsTest, timeStampTest) {
- // 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]));
- // Build our list of reference times
- time_t now;
- time(&now);
- std::vector<time_t> times;
- times.push_back(now);
- times.push_back(DatabaseConnection::MAX_DB_TIME);
- // Note on a 32-bit OS this value is really -1. PosgreSQL will store it
- // and return it intact.
- times.push_back(0xFFFFFFFF);
- // Insert a row for each reference value
- PsqlBindArrayPtr bind_array;
- PgSqlResultPtr r;
- std::string time_str;
- for (int i = 0; i < times.size(); ++i) {
- // Timestamps are inserted as strings so convert them first
- ASSERT_NO_THROW(time_str =
- PgSqlExchange::convertToDatabaseTime(times[i]));
- // Add it to the bind array and insert it
- bind_array.reset(new PsqlBindArray());
- bind_array->add(time_str);
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- }
- // Insert a row with ref time plus one day
- times.push_back(now + 24*3600);
- ASSERT_NO_THROW(time_str =
- PgSqlExchange::convertToDatabaseTime(times[0], 24*3600));
- bind_array.reset(new PsqlBindArray());
- bind_array->add(time_str);
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted rows.
- FETCH_ROWS(r, times.size());
- // Iterate over the rows, verifying the value against its reference
- std::string fetched_str;
- int row = 0;
- for ( ; row < times.size(); ++row ) {
- // Verify the column is not null.
- ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, row, TIMESTAMP_COL));
- // Fetch and verify the column value
- fetched_str = "";
- ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, row, TIMESTAMP_COL,
- fetched_str));
- time_t fetched_time;
- ASSERT_NO_THROW(fetched_time =
- PgSqlExchange::convertFromDatabaseTime(fetched_str));
- EXPECT_EQ(fetched_time, times[row]) << " row: " << row;
- }
- // While we here, verify that bad row throws
- ASSERT_THROW(PgSqlExchange::getColumnValue(*r, row, TIMESTAMP_COL,
- fetched_str), DbOperationError);
- // Clean out the table
- WIPE_ROWS(r);
- // Verify we can insert a NULL value.
- bind_array.reset(new PsqlBindArray());
- bind_array->addNull();
- RUN_PREP(r, statement[0], bind_array, PGRES_COMMAND_OK);
- // Fetch the newly inserted rows
- FETCH_ROWS(r, 1);
- // Verify the column is null.
- ASSERT_TRUE(PgSqlExchange::isColumnNull(*r, 0, TIMESTAMP_COL));
- // Verify exceeding max time throws
- ASSERT_THROW(PgSqlExchange::convertToDatabaseTime(times[0],
- DatabaseConnection::
- MAX_DB_TIME), BadValue);
- }
- }; // namespace
|