diff options
author | Huynh Tran <nthuynh75@gmail.com> | 2005-06-16 17:19:27 +0000 |
---|---|---|
committer | Huynh Tran <nthuynh75@gmail.com> | 2005-06-16 17:19:27 +0000 |
commit | 4a33343730f075018cc7c5424335f2137d11619a (patch) | |
tree | 59c579b1dbb6edee819a59a65b0c4c1e52878f71 /src | |
parent | 154515dbaeff0dfb0501f96464a7016945f45fbd (diff) | |
download | manaserv-4a33343730f075018cc7c5424335f2137d11619a.tar.gz manaserv-4a33343730f075018cc7c5424335f2137d11619a.tar.bz2 manaserv-4a33343730f075018cc7c5424335f2137d11619a.tar.xz manaserv-4a33343730f075018cc7c5424335f2137d11619a.zip |
Simplified APIs, change namespace from tmw to tmwserv, implemented MySQL data provider, added unit tests (require CPPUnit) and bug fixes.
Diffstat (limited to 'src')
-rw-r--r-- | src/dal/dalexcept.h | 51 | ||||
-rw-r--r-- | src/dal/dataprovider.cpp | 4 | ||||
-rw-r--r-- | src/dal/dataprovider.h | 130 | ||||
-rw-r--r-- | src/dal/dataproviderfactory.cpp | 13 | ||||
-rw-r--r-- | src/dal/dataproviderfactory.h | 18 | ||||
-rw-r--r-- | src/dal/mysqldataprovider.cpp | 128 | ||||
-rw-r--r-- | src/dal/mysqldataprovider.h | 72 | ||||
-rw-r--r-- | src/dal/recordset.cpp | 82 | ||||
-rw-r--r-- | src/dal/recordset.h | 80 | ||||
-rw-r--r-- | src/dal/sqlitedataprovider.cpp | 87 | ||||
-rw-r--r-- | src/dal/sqlitedataprovider.h | 73 | ||||
-rw-r--r-- | src/dal/testdataprovider.cpp | 199 | ||||
-rw-r--r-- | src/dal/testdataprovider.h | 138 | ||||
-rw-r--r-- | src/dal/testmain.cpp | 44 | ||||
-rw-r--r-- | src/dal/testrecordset.cpp | 286 | ||||
-rw-r--r-- | src/dal/testrecordset.h | 209 |
16 files changed, 1180 insertions, 434 deletions
diff --git a/src/dal/dalexcept.h b/src/dal/dalexcept.h index 047cb51d..6edf2938 100644 --- a/src/dal/dalexcept.h +++ b/src/dal/dalexcept.h @@ -21,14 +21,14 @@ */ -#ifndef _TMW_DAL_EXCEPT_H_ -#define _TMW_DAL_EXCEPT_H_ +#ifndef _TMWSERV_DAL_EXCEPT_H_ +#define _TMWSERV_DAL_EXCEPT_H_ #include <string> -namespace tmw +namespace tmwserv { namespace dal { @@ -69,7 +69,7 @@ class DbException: public std::exception * @return the error message. */ virtual const char* - what(void) + what(void) const throw() { return mMsg.c_str(); @@ -82,37 +82,6 @@ class DbException: public std::exception /** - * Database creation failure. - */ -class DbCreationFailure: public DbException -{ - public: - /** - * Default constructor. - */ - DbCreationFailure(void) - throw() - : DbException("") - { - // NOOP - } - - - /** - * Constructor. - * - * @param msg the error message. - */ - DbCreationFailure(const std::string& msg) - throw() - : DbException(msg) - { - // NOOP - } -}; - - -/** * Database connection failure. */ class DbConnectionFailure: public DbException @@ -213,8 +182,16 @@ class AlreadySetException: public std::exception }; +/** + * Missing column headers exception. + */ +class RsColumnHeadersNotSet: public std::exception +{ +}; + + } // namespace dal -} // namespace tmw +} // namespace tmwserv -#endif // _TMW_DAL_EXCEPT_H_ +#endif // _TMWSERV_DAL_EXCEPT_H_ diff --git a/src/dal/dataprovider.cpp b/src/dal/dataprovider.cpp index 5475ad53..dbfab728 100644 --- a/src/dal/dataprovider.cpp +++ b/src/dal/dataprovider.cpp @@ -24,7 +24,7 @@ #include "dataprovider.h" -namespace tmw +namespace tmwserv { namespace dal { @@ -64,4 +64,4 @@ DataProvider::isConnected(void) const } // namespace dal -} // namespace tmw +} // namespace tmwserv diff --git a/src/dal/dataprovider.h b/src/dal/dataprovider.h index 6975d084..4e897b3c 100644 --- a/src/dal/dataprovider.h +++ b/src/dal/dataprovider.h @@ -21,8 +21,8 @@ */ -#ifndef _TMW_DATA_PROVIDER_H_ -#define _TMW_DATA_PROVIDER_H_ +#ifndef _TMWSERV_DATA_PROVIDER_H_ +#define _TMWSERV_DATA_PROVIDER_H_ #include <string> @@ -31,7 +31,7 @@ #include "recordset.h" -namespace tmw +namespace tmwserv { namespace dal { @@ -41,13 +41,23 @@ namespace dal * Enumeration type for the database backends. */ typedef enum { - MYSQL, - SQLITE + DB_BKEND_MYSQL, + DB_BKEND_SQLITE } DbBackends; /** * An abstract data provider. + * + * Notes: + * - depending on the database backend, the connection to an unexisting + * database may actually create it as a side-effect (e.g. SQLite). + * + * Limitations: + * - this class does not provide APIs for: + * - remote connections, + * - creating new databases, + * - dropping existing databases. */ class DataProvider { @@ -67,143 +77,75 @@ class DataProvider /** - * Get the database backend name. - * - * @return the database backend name. - */ - virtual DbBackends - getDbBackend(void) const - throw() = 0; - - - /** - * Create a new database. - * - * @param dbName the database name. - * @param dbPath the database file path (optional) + * Get the connection status. * - * @exception DbCreationFailure if unsuccessful creation. - * @exception std::exception if unexpected exception. + * @return true if connected. */ - virtual void - createDb(const std::string& dbName, - const std::string& dbPath = "") - throw(DbCreationFailure, - std::exception) = 0; + bool + isConnected(void) const + throw(); /** - * Create a connection to the database. + * Get the name of the database backend. * - * @param dbName the database name. - * @param userName the user name. - * @param password the user password. - * - * @exception DbConnectionFailure if unsuccessful connection. - * @exception std::exception if unexpected exception. + * @return the database backend name. */ - virtual void - connect(const std::string& dbName, - const std::string& userName, - const std::string& password) - throw(DbConnectionFailure, - std::exception) = 0; + virtual DbBackends + getDbBackend(void) const + throw() = 0; /** * Create a connection to the database. * * @param dbName the database name. - * @param dbPath the database file path. * @param userName the user name. * @param password the user password. * * @exception DbConnectionFailure if unsuccessful connection. - * @exception std::exception if unexpected exception. */ virtual void connect(const std::string& dbName, - const std::string& dbPath, const std::string& userName, - const std::string& password) - throw(DbConnectionFailure, - std::exception) = 0; + const std::string& password) = 0; /** * Execute a SQL query. * * @param sql the SQL query. - * @param refresh if true, refresh the cache (optional) + * @param refresh if true, refresh the cache (default = false). * * @return a recordset. * * @exception DbSqlQueryExecFailure if unsuccessful execution. - * @exception std::exception if unexpected exception. + * @exception std::runtime_error if trying to query a closed database. */ virtual const RecordSet& execSql(const std::string& sql, - const bool refresh = false) - throw(DbSqlQueryExecFailure, - std::exception) = 0; + const bool refresh = false) = 0; /** * Close the connection to the database. * * @exception DbDisconnectionFailure if unsuccessful disconnection. - * @exception std::exception if unexpected exception. */ virtual void - disconnect(void) - throw(DbDisconnectionFailure, - std::exception) = 0; - - - /** - * Get the connection status. - * - * @return true if connected. - */ - bool - isConnected(void) const - throw(); + disconnect(void) = 0; protected: - /** - * The database name. - */ - std::string mDbName; - - - /** - * The database path. - */ - std::string mDbPath; - - - /** - * The connection status. - */ - bool mIsConnected; - - - /** - * Cache the last SQL query. - */ - std::string mSql; - - - /** - * Cache the result of the last SQL query. - */ - RecordSet mRecordSet; + std::string mDbName; /**< the database name */ + bool mIsConnected; /**< the connection status */ + std::string mSql; /**< cache the last SQL query */ + RecordSet mRecordSet; /**< cache the result of the last SQL query */ }; } // namespace dal -} // namespace tmw +} // namespace tmwserv -#endif // _TMW_DATA_PROVIDER_H_ +#endif // _TMWSERV_DATA_PROVIDER_H_ diff --git a/src/dal/dataproviderfactory.cpp b/src/dal/dataproviderfactory.cpp index 252104af..98eb6667 100644 --- a/src/dal/dataproviderfactory.cpp +++ b/src/dal/dataproviderfactory.cpp @@ -27,7 +27,7 @@ #include "sqlitedataprovider.h" -namespace tmw +namespace tmwserv { namespace dal { @@ -58,23 +58,22 @@ DataProviderFactory::~DataProviderFactory(void) */ DataProvider* DataProviderFactory::createDataProvider(void) - throw(std::runtime_error) { -#ifdef USE_MYSQL +#ifdef MYSQL_SUPPORT MySqlDataProvider* provider = new MySqlDataProvider; return provider; #endif -#ifdef USE_SQLITE +#ifdef SQLITE_SUPPORT SqLiteDataProvider* provider = new SqLiteDataProvider; return provider; #endif // a data provider cannot be created as the server has been compiled - // without support for any databases. - throw std::runtime_error("missing database backend support"); + // without support for any database. + throw std::runtime_error("missing database backend support."); } } // namespace dal -} // namespace tmw +} // namespace tmwserv diff --git a/src/dal/dataproviderfactory.h b/src/dal/dataproviderfactory.h index e6ae3cf3..480f6da0 100644 --- a/src/dal/dataproviderfactory.h +++ b/src/dal/dataproviderfactory.h @@ -21,8 +21,8 @@ */ -#ifndef _TMW_DATA_PROVIDER_FACTORY_H_ -#define _TMW_DATA_PROVIDER_FACTORY_H_ +#ifndef _TMWSERV_DATA_PROVIDER_FACTORY_H_ +#define _TMWSERV_DATA_PROVIDER_FACTORY_H_ #include <stdexcept> @@ -30,7 +30,7 @@ #include "dataprovider.h" -namespace tmw +namespace tmwserv { namespace dal { @@ -38,6 +38,11 @@ namespace dal /** * A factory to create data providers. + * + * Note: + * - this class does not assume the ownership of the pointers returned + * by createDataProvider(). + * - this class is a singleton. */ class DataProviderFactory { @@ -46,8 +51,7 @@ class DataProviderFactory * Create a new data provider. */ static DataProvider* - createDataProvider(void) - throw(std::runtime_error); + createDataProvider(void); private: @@ -80,7 +84,7 @@ class DataProviderFactory } // namespace dal -} // namespace tmw +} // namespace tmwserv -#endif // _TMW_DATA_PROVIDER_FACTORY_H_ +#endif // _TMWSERV_DATA_PROVIDER_FACTORY_H_ diff --git a/src/dal/mysqldataprovider.cpp b/src/dal/mysqldataprovider.cpp index de3418f4..20478161 100644 --- a/src/dal/mysqldataprovider.cpp +++ b/src/dal/mysqldataprovider.cpp @@ -21,10 +21,12 @@ */ +#include "dalexcept.h" + #include "mysqldataprovider.h" -namespace tmw +namespace tmwserv { namespace dal { @@ -46,7 +48,12 @@ MySqlDataProvider::MySqlDataProvider(void) MySqlDataProvider::~MySqlDataProvider(void) throw() { - // NOOP + // we are using the MySQL C API, there are no exceptions to catch. + + // make sure that the database is closed. + // disconnect() calls mysql_close() which takes care of freeing + // the memory allocated for the handle. + disconnect(); } @@ -57,20 +64,7 @@ DbBackends MySqlDataProvider::getDbBackend(void) const throw() { - return MYSQL; -} - - -/** - * Create a new database. - */ -void -MySqlDataProvider::createDb(const std::string& dbName, - const std::string& dbPath) - throw(DbCreationFailure, - std::exception) -{ - // TODO + return DB_BKEND_MYSQL; } @@ -81,25 +75,32 @@ void MySqlDataProvider::connect(const std::string& dbName, const std::string& userName, const std::string& password) - throw(DbConnectionFailure, - std::exception) { - connect(dbName, "", userName, password); -} + // allocate and initialize a new MySQL object suitable + // for mysql_real_connect(). + mDb = mysql_init(NULL); + if (!mDb) { + throw DbConnectionFailure( + "unable to initialize the MySQL library: no memory"); + } -/** - * Create a connection to the database. - */ -void -MySqlDataProvider::connect(const std::string& dbName, - const std::string& dbPath, - const std::string& userName, - const std::string& password) - throw(DbConnectionFailure, - std::exception) -{ - // TODO + // insert connection options here. + + // actually establish the connection. + if (!mysql_real_connect(mDb, // handle to the connection + NULL, // localhost + userName.c_str(), // user name + password.c_str(), // user password + dbName.c_str(), // database name + 0, // use default TCP port + NULL, // use defaut socket + 0)) // client flags + { + throw DbConnectionFailure(mysql_error(mDb)); + } + + mIsConnected = true; } @@ -109,14 +110,54 @@ MySqlDataProvider::connect(const std::string& dbName, const RecordSet& MySqlDataProvider::execSql(const std::string& sql, const bool refresh) - throw(DbSqlQueryExecFailure, - std::exception) { + if (!mIsConnected) { + throw std::runtime_error("not connected to database"); + } + // do something only if the query is different from the previous // or if the cache must be refreshed // otherwise just return the recordset from cache. if (refresh || (sql != mSql)) { - // TODO + mRecordSet.clear(); + + // actually execute the query. + if (mysql_query(mDb, sql.c_str()) != 0) { + throw DbSqlQueryExecFailure(mysql_error(mDb)); + } + + if (mysql_field_count(mDb) > 0) { + MYSQL_RES* res; + + // get the result of the query. + if (!(res = mysql_store_result(mDb))) { + throw DbSqlQueryExecFailure(mysql_error(mDb)); + } + + // set the field names. + unsigned int nFields = mysql_num_fields(res); + MYSQL_FIELD* fields = mysql_fetch_fields(res); + Row fieldNames; + for (unsigned int i = 0; i < nFields; ++i) { + fieldNames.push_back(fields[i].name); + } + mRecordSet.setColumnHeaders(fieldNames); + + // populate the RecordSet. + MYSQL_ROW row; + while ((row = mysql_fetch_row(res))) { + Row r; + + for (unsigned int i = 0; i < nFields; ++i) { + r.push_back(static_cast<char *>(row[i])); + } + + mRecordSet.add(r); + } + + // free memory + mysql_free_result(res); + } } return mRecordSet; @@ -128,12 +169,21 @@ MySqlDataProvider::execSql(const std::string& sql, */ void MySqlDataProvider::disconnect(void) - throw(DbDisconnectionFailure, - std::exception) { - // TODO + if (!isConnected()) { + return; + } + + // mysql_close() closes the connection and deallocates the connection + // handle as it was allocated by mysql_init(). + mysql_close(mDb); + + // deinitialize the MySQL client library. + mysql_library_end(); + + mIsConnected = false; } } // namespace dal -} // namespace tmw +} // namespace tmwserv diff --git a/src/dal/mysqldataprovider.h b/src/dal/mysqldataprovider.h index 967e9ce2..76194680 100644 --- a/src/dal/mysqldataprovider.h +++ b/src/dal/mysqldataprovider.h @@ -21,16 +21,18 @@ */ -#ifndef _TMW_MYSQL_DATA_PROVIDER_H_ -#define _TMW_MYSQL_DATA_PROVIDER_H_ +#ifndef _TMWSERV_MYSQL_DATA_PROVIDER_H_ +#define _TMWSERV_MYSQL_DATA_PROVIDER_H_ #include <string> +#include <mysql/mysql.h> + #include "dataprovider.h" -namespace tmw +namespace tmwserv { namespace dal { @@ -57,7 +59,7 @@ class MySqlDataProvider: public DataProvider /** - * Get the database backend name. + * Get the name of the database backend. * * @return the database backend name. */ @@ -67,22 +69,6 @@ class MySqlDataProvider: public DataProvider /** - * Create a new database. - * - * @param dbName the database name. - * @param dbPath the database file path (optional) - * - * @exception DbCreationFailure if unsuccessful creation. - * @exception std::exception if unexpected exception. - */ - void - createDb(const std::string& dbName, - const std::string& dbPath = "") - throw(DbCreationFailure, - std::exception); - - - /** * Create a connection to the database. * * @param dbName the database name. @@ -90,69 +76,45 @@ class MySqlDataProvider: public DataProvider * @param password the user password. * * @exception DbConnectionFailure if unsuccessful connection. - * @exception std::exception if unexpected exception. */ void connect(const std::string& dbName, const std::string& userName, - const std::string& password) - throw(DbConnectionFailure, - std::exception); - - - /** - * Create a connection to the database. - * - * @param dbName the database name. - * @param dbPath the database file path. - * @param userName the user name. - * @param password the user password. - * - * @exception DbConnectionFailure if unsuccessful connection. - * @exception std::exception if unexpected exception. - */ - void - connect(const std::string& dbName, - const std::string& dbPath, - const std::string& userName, - const std::string& password) - throw(DbConnectionFailure, - std::exception); + const std::string& password); /** * Execute a SQL query. * * @param sql the SQL query. - * @param refresh if true, refresh the cache (optional) + * @param refresh if true, refresh the cache (default = false). * * @return a recordset. * * @exception DbSqlQueryExecFailure if unsuccessful execution. - * @exception std::exception if unexpected exception. + * @exception std::runtime_error if trying to query a closed database. */ const RecordSet& execSql(const std::string& sql, - const bool refresh = false) - throw(DbSqlQueryExecFailure, - std::exception); + const bool refresh = false); /** * Close the connection to the database. * * @exception DbDisconnectionFailure if unsuccessful disconnection. - * @exception std::exception if unexpected exception. */ void - disconnect(void) - throw(DbDisconnectionFailure, - std::exception); + disconnect(void); + + + private: + MYSQL* mDb; /**< the handle to the database connection */ }; } // namespace dal -} // namespace tmw +} // namespace tmwserv -#endif // _TMW_MYSQL_DATA_PROVIDER_H_ +#endif // _TMWSERV_MYSQL_DATA_PROVIDER_H_ diff --git a/src/dal/recordset.cpp b/src/dal/recordset.cpp index dc35fa3e..ae335f5e 100644 --- a/src/dal/recordset.cpp +++ b/src/dal/recordset.cpp @@ -26,7 +26,7 @@ #include "recordset.h" -namespace tmw +namespace tmwserv { namespace dal { @@ -57,7 +57,6 @@ RecordSet::~RecordSet(void) */ void RecordSet::clear(void) - throw() { mHeaders.clear(); mRows.clear(); @@ -65,13 +64,25 @@ RecordSet::clear(void) /** + * Check if the RecordSet is empty. + */ +bool +RecordSet::isEmpty(void) const +{ + // we just need to check the size of the list of column headers + // as it is not possible to insert a new record if the column + // headers are not defined. + return (mHeaders.size() == 0); +} + + +/** * Get the number of rows. * * @return the number of rows. */ unsigned int RecordSet::rows(void) const - throw() { return mRows.size(); } @@ -84,7 +95,6 @@ RecordSet::rows(void) const */ unsigned int RecordSet::cols(void) const - throw() { return mHeaders.size(); } @@ -95,7 +105,6 @@ RecordSet::cols(void) const */ void RecordSet::setColumnHeaders(const Row& headers) - throw(AlreadySetException) { if (mHeaders.size() > 0) { throw AlreadySetException(); @@ -110,11 +119,19 @@ RecordSet::setColumnHeaders(const Row& headers) */ void RecordSet::add(const Row& row) - throw(std::invalid_argument) { - if (row.size() != mHeaders.size()) { - throw std::invalid_argument( - "the new row does not have the required number of columns."); + const unsigned int nCols = mHeaders.size(); + + if (nCols == 0) { + throw RsColumnHeadersNotSet(); + } + + if (row.size() != nCols) { + std::ostringstream msg; + msg << "row has " << row.size() << " columns; " + << "expected: " << nCols << std::ends; + + throw std::invalid_argument(msg.str()); } mRows.push_back(row); @@ -127,8 +144,12 @@ RecordSet::add(const Row& row) const std::string& RecordSet::operator()(const unsigned int row, const unsigned int col) const - throw(std::out_of_range) { + if (mHeaders.size() == 0) { + throw std::invalid_argument( + "nothing to return as the recordset is empty."); + } + if ((row >= mRows.size()) || (col >= mHeaders.size())) { std::ostringstream os; os << "(" << row << ", " << col << ") is out of range; " @@ -148,9 +169,12 @@ RecordSet::operator()(const unsigned int row, const std::string& RecordSet::operator()(const unsigned int row, const std::string& name) const - throw(std::out_of_range, - std::invalid_argument) { + if (mHeaders.size() == 0) { + throw std::invalid_argument( + "nothing to return as the recordset is empty."); + } + if (row >= mRows.size()) { std::ostringstream os; os << "row " << row << " is out of range; " @@ -170,8 +194,8 @@ RecordSet::operator()(const unsigned int row, } // find the field index. - const int nCols = mHeaders.size(); - int i; + const unsigned int nCols = mHeaders.size(); + unsigned int i; for (i = 0; i < nCols; ++i) { if (mHeaders[i] == name) { break; @@ -188,17 +212,18 @@ RecordSet::operator()(const unsigned int row, std::ostream& operator<<(std::ostream& out, const RecordSet& rhs) { - using namespace tmw::dal; + using namespace tmwserv::dal; // print the field names first. if (rhs.mHeaders.size() > 0) { - Row::const_iterator it = rhs.mHeaders.begin(); - out << "[ " << (*it); - it++; - for (; it != rhs.mHeaders.end(); ++it) { - out << " | " << (*it); + out << "|"; + for (Row::const_iterator it = rhs.mHeaders.begin(); + it != rhs.mHeaders.end(); + ++it) + { + out << (*it) << "|"; } - out << " ]" << std::endl; + out << std::endl << std::endl; } // and then print every line. @@ -206,13 +231,14 @@ operator<<(std::ostream& out, const RecordSet& rhs) it != rhs.mRows.end(); ++it) { - Row::const_iterator it2 = (*it).begin(); - out << "[ " << (*it2); - it2++; - for (; it2 != (*it).end(); ++it2) { - out << " | " << (*it2); + out << "|"; + for (Row::const_iterator it2 = (*it).begin(); + it2 != (*it).end(); + ++it2) + { + out << (*it2) << "|"; } - out << " ]" << std::endl; + out << std::endl; } return out; @@ -220,4 +246,4 @@ operator<<(std::ostream& out, const RecordSet& rhs) } // namespace dal -} // namespace tmw +} // namespace tmwserv diff --git a/src/dal/recordset.h b/src/dal/recordset.h index a42b3d6b..eac33cfd 100644 --- a/src/dal/recordset.h +++ b/src/dal/recordset.h @@ -21,25 +21,25 @@ */ -#ifndef _TMW_RECORDSET_H_ -#define _TMW_RECORDSET_H_ +#ifndef _TMWSERV_RECORDSET_H_ +#define _TMWSERV_RECORDSET_H_ #include <iostream> -#include <vector> #include <stdexcept> +#include <vector> #include "dalexcept.h" -namespace tmw +namespace tmwserv { namespace dal { /** - * A record from the RecordSet. + * Data type for a row in a RecordSet. */ typedef std::vector<std::string> Row; @@ -47,8 +47,10 @@ typedef std::vector<std::string> Row; /** * A RecordSet to store the result of a SQL query. * - * Limitations: the field values are stored and returned as string, - * no information about the field data types are stored. + * Limitations: + * - the field values are stored and returned as string, + * - no information about the field data types are stored. + * - not thread-safe. */ class RecordSet { @@ -68,11 +70,19 @@ class RecordSet /** - * Remove all the Records. + * Remove all the records. */ void - clear(void) - throw(); + clear(void); + + + /** + * Check if the RecordSet is empty. + * + * @return true if empty. + */ + bool + isEmpty(void) const; /** @@ -81,8 +91,7 @@ class RecordSet * @return the number of rows. */ unsigned int - rows(void) const - throw(); + rows(void) const; /** @@ -91,8 +100,7 @@ class RecordSet * @return the number of columns. */ unsigned int - cols(void) const - throw(); + cols(void) const; /** @@ -104,8 +112,7 @@ class RecordSet * are already set. */ void - setColumnHeaders(const Row& headers) - throw(AlreadySetException); + setColumnHeaders(const Row& headers); /** @@ -116,12 +123,13 @@ class RecordSet * * @param row the new row. * + * @exception RsColumnHeadersNotSet if the row is being added before + * the column headers. * @exception std::invalid_argument if the number of columns in the * new row is not equal to the number of column headers. */ void - add(const Row& row) - throw(std::invalid_argument); + add(const Row& row); /** @@ -135,11 +143,11 @@ class RecordSet * @return the field value. * * @exception std::out_of_range if row or col are out of range. + * @exception std::invalid_argument if the recordset is empty. */ const std::string& operator()(const unsigned int row, - const unsigned int col) const - throw(std::out_of_range); + const unsigned int col) const; /** @@ -153,13 +161,12 @@ class RecordSet * @return the field value. * * @exception std::out_of_range if the row index is out of range. - * @exception std::invalid_argument if the field name is not found. + * @exception std::invalid_argument if the field name is not found or + * the recordset is empty. */ const std::string& operator()(const unsigned int row, - const std::string& name) const - throw(std::out_of_range, - std::invalid_argument); + const std::string& name) const; /** @@ -179,34 +186,25 @@ class RecordSet /** * Copy constructor. */ - RecordSet(const RecordSet& rhs) - throw(); + RecordSet(const RecordSet& rhs); /** - * Operator= + * Assignment operator. */ RecordSet& - operator=(const RecordSet& rhs) - throw(); - + operator=(const RecordSet& rhs); - /** - * A list of field names. - */ - Row mHeaders; - - /** - * A list of records. - */ + private: + Row mHeaders; /**< a list of field names */ typedef std::vector<Row> Rows; - Rows mRows; + Rows mRows; /**< a list of records */ }; } // namespace dal -} // namespace tmw +} // namespace tmwserv -#endif // _TMW_RECORDSET_H_ +#endif // _TMWSERV_RECORDSET_H_ diff --git a/src/dal/sqlitedataprovider.cpp b/src/dal/sqlitedataprovider.cpp index f30d0762..30de91c5 100644 --- a/src/dal/sqlitedataprovider.cpp +++ b/src/dal/sqlitedataprovider.cpp @@ -21,12 +21,10 @@ */ -#include <iostream> - #include "sqlitedataprovider.h" -namespace tmw +namespace tmwserv { namespace dal { @@ -52,47 +50,23 @@ SqLiteDataProvider::~SqLiteDataProvider(void) try { // make sure that the database is closed. // disconnect() calls sqlite3_close() which takes care of freeing - // the memory held by the class attribute mDb. + // the memory allocated for the handle. disconnect(); } - catch (const DbDisconnectionFailure& e) { - // TODO + catch (...) { + // ignore } } /** - * Get the database backend name. + * Get the name of the database backend. */ DbBackends SqLiteDataProvider::getDbBackend(void) const throw() { - return SQLITE; -} - - -/** - * Create a new database. - */ -void -SqLiteDataProvider::createDb(const std::string& dbName, - const std::string& dbPath) - throw(DbCreationFailure, - std::exception) -{ - // TODO: handle dbPath - - // sqlite3_open creates the database file if it does not exist - // and hence createDb is not very useful here. - if (sqlite3_open(dbName.c_str(), &mDb) != SQLITE_OK) { - throw DbCreationFailure(sqlite3_errmsg(mDb)); - } - - // nothing else to do, close the database. - if (sqlite3_close(mDb) != SQLITE_OK) { - throw DbCreationFailure(sqlite3_errmsg(mDb)); - } + return DB_BKEND_SQLITE; } @@ -103,33 +77,19 @@ void SqLiteDataProvider::connect(const std::string& dbName, const std::string& userName, const std::string& password) - throw(DbConnectionFailure, - std::exception) { - connect(dbName, "", userName, password); -} - - -/** - * Create a connection to the database. - */ -void -SqLiteDataProvider::connect(const std::string& dbName, - const std::string& dbPath, - const std::string& userName, - const std::string& password) - throw(DbConnectionFailure, - std::exception) -{ - // TODO: handle dbPath - - // sqlite3_open creates the database file if it does not exist. + // sqlite3_open creates the database file if it does not exist + // as a side-effect. if (sqlite3_open(dbName.c_str(), &mDb) != SQLITE_OK) { - // there is no need to check for the error code returned by - // sqlite3_close() as we are going to throw an exception anyway + // save the error message thrown by sqlite3_open() + // as we may lose it when sqlite3_close() runs. + std::string msg(sqlite3_errmsg(mDb)); + + // the SQLite3 documentation suggests that we try to close + // the database after an unsuccessful call to sqlite3_open(). sqlite3_close(mDb); - throw DbConnectionFailure(sqlite3_errmsg(mDb)); + throw DbConnectionFailure(msg); } mIsConnected = true; @@ -142,9 +102,11 @@ SqLiteDataProvider::connect(const std::string& dbName, const RecordSet& SqLiteDataProvider::execSql(const std::string& sql, const bool refresh) - throw(DbSqlQueryExecFailure, - std::exception) { + if (!mIsConnected) { + throw std::runtime_error("not connected to database"); + } + // do something only if the query is different from the previous // or if the cache must be refreshed // otherwise just return the recordset from cache. @@ -154,6 +116,8 @@ SqLiteDataProvider::execSql(const std::string& sql, int nCols; char* errMsg; + mRecordSet.clear(); + int errCode = sqlite3_get_table( mDb, // an open database sql.c_str(), // SQL to be executed @@ -164,12 +128,9 @@ SqLiteDataProvider::execSql(const std::string& sql, ); if (errCode != SQLITE_OK) { - std::cout << sqlite3_errmsg(mDb) << std::endl; - throw DbSqlQueryExecFailure(); + throw DbSqlQueryExecFailure(sqlite3_errmsg(mDb)); } - mRecordSet.clear(); - // the first row of result[] contains the field names. Row fieldNames; for(int col = 0; col < nCols; ++col) { @@ -203,8 +164,6 @@ SqLiteDataProvider::execSql(const std::string& sql, */ void SqLiteDataProvider::disconnect(void) - throw(DbDisconnectionFailure, - std::exception) { if (!isConnected()) { return; @@ -219,4 +178,4 @@ SqLiteDataProvider::disconnect(void) } // namespace dal -} // namespace tmw +} // namespace tmwserv diff --git a/src/dal/sqlitedataprovider.h b/src/dal/sqlitedataprovider.h index f113bd54..384067c5 100644 --- a/src/dal/sqlitedataprovider.h +++ b/src/dal/sqlitedataprovider.h @@ -21,18 +21,18 @@ */ -#ifndef _TMW_SQLITE_DATA_PROVIDER_H_ -#define _TMW_SQLITE_DATA_PROVIDER_H_ +#ifndef _TMWSERV_SQLITE_DATA_PROVIDER_H_ +#define _TMWSERV_SQLITE_DATA_PROVIDER_H_ #include <string> -#include "sqlite3.h" +#include <sqlite3.h> #include "dataprovider.h" -namespace tmw +namespace tmwserv { namespace dal { @@ -59,7 +59,7 @@ class SqLiteDataProvider: public DataProvider /** - * Get the database backend name. + * Get the name of the database backend. * * @return the database backend name. */ @@ -69,99 +69,52 @@ class SqLiteDataProvider: public DataProvider /** - * Create a new database. - * - * @param dbName the database name. - * @param dbPath the database file path (optional) - * - * @exception DbCreationFailure if unsuccessful creation. - * @exception std::exception if unexpected exception. - */ - void - createDb(const std::string& dbName, - const std::string& dbPath = "") - throw(DbCreationFailure, - std::exception); - - - /** - * Create a connection to the database. - * - * @param dbName the database name. - * @param userName the user name. - * @param password the user password. - * - * @exception DbConnectionFailure if unsuccessful connection. - * @exception std::exception if unexpected exception. - */ - void - connect(const std::string& dbName, - const std::string& userName, - const std::string& password) - throw(DbConnectionFailure, - std::exception); - - - /** * Create a connection to the database. * * @param dbName the database name. - * @param dbPath the database file path. * @param userName the user name. * @param password the user password. * * @exception DbConnectionFailure if unsuccessful connection. - * @exception std::exception if unexpected exception. */ void connect(const std::string& dbName, - const std::string& dbPath, const std::string& userName, - const std::string& password) - throw(DbConnectionFailure, - std::exception); + const std::string& password); /** * Execute a SQL query. * * @param sql the SQL query. - * @param refresh if true, refresh the cache (optional) + * @param refresh if true, refresh the cache (default = false). * * @return a recordset. * * @exception DbSqlQueryExecFailure if unsuccessful execution. - * @exception std::exception if unexpected exception. + * @exception std::runtime_error if trying to query a closed database. */ const RecordSet& execSql(const std::string& sql, - const bool refresh = false) - throw(DbSqlQueryExecFailure, - std::exception); + const bool refresh = false); /** * Close the connection to the database. * * @exception DbDisconnectionFailure if unsuccessful disconnection. - * @exception std::exception if unexpected exception. */ void - disconnect(void) - throw(DbDisconnectionFailure, - std::exception); + disconnect(void); private: - /** - * The database. - */ - sqlite3* mDb; + sqlite3* mDb; /**< the handle to the database connection */ }; } // namespace dal -} // namespace tmw +} // namespace tmwserv -#endif // _TMW_SQLITE_DATA_PROVIDER_H_ +#endif // _TMWSERV_SQLITE_DATA_PROVIDER_H_ diff --git a/src/dal/testdataprovider.cpp b/src/dal/testdataprovider.cpp new file mode 100644 index 00000000..08b66da8 --- /dev/null +++ b/src/dal/testdataprovider.cpp @@ -0,0 +1,199 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + + +#include "dataproviderfactory.h" +#include "testdataprovider.h" + + +// register the fixture into the 'registry' +CPPUNIT_TEST_SUITE_REGISTRATION(DataProviderTest); + + +using namespace tmwserv::dal; + + +/** + * Set up fixtures. + */ +void +DataProviderTest::setUp(void) +{ + // obtain a data provider. + try { + mDb = DataProviderFactory::createDataProvider(); + } + catch (const std::runtime_error& e) { + CPPUNIT_FAIL(e.what()); + } + + // init db info and account. +#ifdef SQLITE_SUPPORT + mDbName = "mydb.db"; +#else + mDbName = "mydb"; +#endif + mDbUser = "guest"; + mDbPassword = "guest"; + + // init SQL queries. + mSqlCreateTable = "create table employees ("; + mSqlCreateTable += " id int primary key, "; + mSqlCreateTable += " name varchar(32) not null);"; + + mSqlInsertRow = "insert into employees values (1, 'john');"; + + mSqlFetchRow = "select * from employees;"; +} + + +/** + * Tear down fixtures. + */ +void +DataProviderTest::tearDown(void) +{ + delete mDb; +} + + +/** + * Connection to an existing database. + */ +void +DataProviderTest::testConnection1(void) +{ + mDb->connect(mDbName, mDbUser, mDbPassword); + CPPUNIT_ASSERT(mDb->isConnected()); +} + + +/** + * Create a new table in the database. + */ +void +DataProviderTest::testCreateTable1(void) +{ + mDb->connect(mDbName, mDbUser, mDbPassword); + CPPUNIT_ASSERT(mDb->isConnected()); + + const RecordSet& rs = mDb->execSql(mSqlCreateTable); + CPPUNIT_ASSERT(rs.isEmpty()); + + mDb->disconnect(); + CPPUNIT_ASSERT(!mDb->isConnected()); +} + + +/** + * Create the same table one more time in the database. + */ +void +DataProviderTest::testCreateTable2(void) +{ + mDb->connect(mDbName, mDbUser, mDbPassword); + CPPUNIT_ASSERT(mDb->isConnected()); + + // this should throw tmwserv::dal::DbSqlQueryExecFailure. + const RecordSet& rs = mDb->execSql(mSqlCreateTable); +} + + +/** + * Insert a new row to the table. + */ +void +DataProviderTest::testInsert1(void) +{ + mDb->connect(mDbName, mDbUser, mDbPassword); + CPPUNIT_ASSERT(mDb->isConnected()); + + const RecordSet& rs = mDb->execSql(mSqlInsertRow); + // an insert query does not return any records + // so the recordset remains empty. + CPPUNIT_ASSERT(rs.isEmpty()); + + mDb->disconnect(); + CPPUNIT_ASSERT(!mDb->isConnected()); +} + + +/** + * Insert the same record again. + */ +void +DataProviderTest::testInsert2(void) +{ + mDb->connect(mDbName, mDbUser, mDbPassword); + CPPUNIT_ASSERT(mDb->isConnected()); + + // this should throw tmwserv::dal::DbSqlQueryExecFailure + // as we are violating the primary key uniqueness. + mDb->execSql(mSqlInsertRow); +} + + +/** + * Fetch data from the table. + */ +void +DataProviderTest::testFetch1(void) +{ + mDb->connect(mDbName, mDbUser, mDbPassword); + CPPUNIT_ASSERT(mDb->isConnected()); + + const RecordSet& rs = mDb->execSql(mSqlFetchRow); + CPPUNIT_ASSERT(!rs.isEmpty()); + + std::string id("1"); + std::string name("john"); + CPPUNIT_ASSERT_EQUAL(id, rs(0, "id")); + CPPUNIT_ASSERT_EQUAL(name, rs(0, "name")); + + mDb->disconnect(); + CPPUNIT_ASSERT(!mDb->isConnected()); +} + + +/** + * Disconnection from an open database. + */ +void +DataProviderTest::testDisconnection1(void) +{ + mDb->connect(mDbName, mDbUser, mDbPassword); + CPPUNIT_ASSERT(mDb->isConnected()); + + mDb->disconnect(); + CPPUNIT_ASSERT(!mDb->isConnected()); +} + + +/** + * Disconnection from a closed database. + */ +void +DataProviderTest::testDisconnection2(void) +{ + mDb->disconnect(); + CPPUNIT_ASSERT(!mDb->isConnected()); +} diff --git a/src/dal/testdataprovider.h b/src/dal/testdataprovider.h new file mode 100644 index 00000000..e677840a --- /dev/null +++ b/src/dal/testdataprovider.h @@ -0,0 +1,138 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + + + +#ifndef _TMWSERV_TEST_DATA_PROVIDER_H_ +#define _TMWSERV_TEST_DATA_PROVIDER_H_ + + +#include <cppunit/extensions/HelperMacros.h> + +#include "dalexcept.h" + + +/** + * Unit test for the DataProvider class. + */ +class DataProviderTest: public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(DataProviderTest); + + // add tests to the test suite. + CPPUNIT_TEST(testConnection1); + CPPUNIT_TEST(testCreateTable1); + CPPUNIT_TEST_EXCEPTION(testCreateTable2, + tmwserv::dal::DbSqlQueryExecFailure); + CPPUNIT_TEST(testInsert1); + CPPUNIT_TEST_EXCEPTION(testInsert2, tmwserv::dal::DbSqlQueryExecFailure); + CPPUNIT_TEST(testFetch1); + CPPUNIT_TEST(testDisconnection1); + CPPUNIT_TEST(testDisconnection2); + + CPPUNIT_TEST_SUITE_END(); + + + public: + /** + * Set up fixtures. + */ + void + setUp(void); + + + /** + * Tear down fixtures. + */ + void + tearDown(void); + + + /** + * Connection to an existing database. + */ + void + testConnection1(void); + + + /** + * Create a new table in the database. + */ + void + testCreateTable1(void); + + + /** + * Create the same table one more time in the database. + */ + void + testCreateTable2(void); + + + /** + * Insert a new record into the table. + */ + void + testInsert1(void); + + + /** + * Insert the same record again. + */ + void + testInsert2(void); + + + /** + * Fetch data from the table. + */ + void + testFetch1(void); + + + /** + * Disconnection from an open database. + */ + void + testDisconnection1(void); + + + /** + * Disconnection from a closed database. + */ + void + testDisconnection2(void); + + + private: + tmwserv::dal::DataProvider* mDb; /**< the data provider */ + std::string mDbName; /**< the database name */ + std::string mDbPath; /**< the database path */ + std::string mDbUser; /**< the database user */ + std::string mDbPassword; /**< the database password */ + std::string mSqlCreateTable; /**< SQL query to create table */ + std::string mSqlInsertRow; /**< SQL query to delete table */ + std::string mSqlFetchRow; /**< SQL query to fetch data */ +}; + + +#endif // _TMWSERV_TEST_DATA_PROVIDER_H_ diff --git a/src/dal/testmain.cpp b/src/dal/testmain.cpp new file mode 100644 index 00000000..b9beadec --- /dev/null +++ b/src/dal/testmain.cpp @@ -0,0 +1,44 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + + +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <cppunit/ui/text/TextTestRunner.h> + + +int main(int argc, char* argv[]) +{ + using namespace CppUnit; + + // get the top level suite from the registry. + Test* suite = TestFactoryRegistry::getRegistry().makeTest(); + + // add the test to the list of test to run. + TextTestRunner runner; + runner.addTest(suite); + + // run the tests. + bool wasSucessful = runner.run(); + + // return error code 1 if the one of test failed. + return wasSucessful ? 0 : 1; +} diff --git a/src/dal/testrecordset.cpp b/src/dal/testrecordset.cpp new file mode 100644 index 00000000..3d8e928a --- /dev/null +++ b/src/dal/testrecordset.cpp @@ -0,0 +1,286 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + + +#include <sstream> + +#include "recordset.h" +#include "testrecordset.h" + + +// register the fixture into the 'registry' +CPPUNIT_TEST_SUITE_REGISTRATION(RecordSetTest); + + +using namespace tmwserv::dal; + + +/** + * Set up fixtures. + */ +void +RecordSetTest::setUp(void) +{ + // populate mNonEmptyRs. + Row headers; + headers.push_back("id"); + headers.push_back("name"); + mNonEmptyRs.setColumnHeaders(headers); + + Row r1; + r1.push_back("1"); + r1.push_back("john"); + mNonEmptyRs.add(r1); + + Row r2; + r2.push_back("2"); + r2.push_back("mike"); + mNonEmptyRs.add(r2); +} + + +/** + * Tear down fixtures. + */ +void +RecordSetTest::tearDown(void) +{ + // NOOP +} + + +/** + * Test RecordSet::rows() on an empty RecordSet. + */ +void +RecordSetTest::testRows1(void) +{ + CPPUNIT_ASSERT_EQUAL((unsigned int) 0, mEmptyRs.rows()); +} + + +/** + * Test RecordSet::rows() on a non-empty RecordSet. + */ +void +RecordSetTest::testRows2(void) +{ + CPPUNIT_ASSERT_EQUAL((unsigned int) 2, mNonEmptyRs.rows()); +} + + +/** + * Test RecordSet::cols() on an empty RecordSet. + */ +void +RecordSetTest::testCols1(void) +{ + CPPUNIT_ASSERT_EQUAL((unsigned int) 0, mEmptyRs.cols()); +} + + +/** + * Test RecordSet::cols() on a non-empty RecordSet. + */ +void +RecordSetTest::testCols2(void) +{ + CPPUNIT_ASSERT_EQUAL((unsigned int) 2, mNonEmptyRs.cols()); +} + + +/** + * Call RecordSet::isEmpty() from an empty RecordSet. + */ +void +RecordSetTest::testIsEmpty1(void) +{ + CPPUNIT_ASSERT(mEmptyRs.isEmpty()); +} + + +/** + * Call RecordSet::isEmpty() from a non-empty RecordSet. + */ +void +RecordSetTest::testIsEmpty2(void) +{ + CPPUNIT_ASSERT(!mNonEmptyRs.isEmpty()); +} + + +/** + * Call RecordSet::operator() from an empty RecordSet. + */ +void +RecordSetTest::testOperator1(void) +{ + // this should throw std::invalid_argument. + mEmptyRs(0, 0); +} + + +/** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index and a column index as parameters. + */ +void +RecordSetTest::testOperator2(void) +{ + std::string value("mike"); + + CPPUNIT_ASSERT_EQUAL(value, mNonEmptyRs(1, 1)); +} + + +/** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index and a column index that are out of range as + * parameters. + */ +void +RecordSetTest::testOperator3(void) +{ + // this should throw std::out_of_range. + mNonEmptyRs(2, 2); +} + + +/** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index and a field name as parameters. + */ +void +RecordSetTest::testOperator4(void) +{ + std::string value("1"); + + CPPUNIT_ASSERT_EQUAL(value, mNonEmptyRs(0, "id")); +} + + +/** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index that is out of range and a field name as parameters. + */ +void +RecordSetTest::testOperator5(void) +{ + // this should throw std::out_of_range. + mNonEmptyRs(3, "id"); +} + + +/** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index and a field name that does not exist as parameters. + */ +void +RecordSetTest::testOperator6(void) +{ + // this should throw std::invalid_argument. + mNonEmptyRs(1, "noname"); +} + + +/** + * Test writing an empty RecordSet to an output stream. + */ +void +RecordSetTest::testOutputStream1(void) +{ + std::string emptyStr; + + std::ostringstream os; + os << mEmptyRs; + + CPPUNIT_ASSERT_EQUAL(emptyStr, os.str()); +} + + +/** + * Test writing a non-empty RecordSet to an output stream. + */ +void +RecordSetTest::testOutputStream2(void) +{ + std::ostringstream os1; + os1 << "|id|name|" << std::endl << std::endl + << "|1|john|" << std::endl + << "|2|mike|" << std::endl; + + std::ostringstream os2; + os2 << mNonEmptyRs; + + CPPUNIT_ASSERT_EQUAL(os1.str(), os2.str()); +} + + +/** + * Test RecordSet::add() to add a new now. + */ +void +RecordSetTest::testAdd1(void) +{ + std::string id("3"); + std::string name("elena"); + + Row r; + r.push_back(id); + r.push_back(name); + mNonEmptyRs.add(r); + + CPPUNIT_ASSERT_EQUAL((unsigned int) 3, mNonEmptyRs.rows()); + CPPUNIT_ASSERT_EQUAL((unsigned int) 2, mNonEmptyRs.cols()); + CPPUNIT_ASSERT_EQUAL(id, mNonEmptyRs(2, 0)); + CPPUNIT_ASSERT_EQUAL(name, mNonEmptyRs(2, 1)); +} + + +/** + * Test RecordSet::add() to add a new now with a different number + * of fields. + */ +void +RecordSetTest::testAdd2(void) +{ + Row r; + r.push_back("4"); + + // this should throw std::invalid_argument. + mNonEmptyRs.add(r); +} + + +/** + * Test RecordSet::add() to add a new now to a RecordSet that does + * not have column headers. + */ +void +RecordSetTest::testAdd3(void) +{ + Row r; + r.push_back("5"); + + // this should throw tmw::dal::RsColumnHeadersNotSet. + mEmptyRs.add(r); +} diff --git a/src/dal/testrecordset.h b/src/dal/testrecordset.h new file mode 100644 index 00000000..60b31137 --- /dev/null +++ b/src/dal/testrecordset.h @@ -0,0 +1,209 @@ +/* + * The Mana World Server + * Copyright 2004 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or any later version. + * + * The Mana World is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + + +#ifndef _TMWSERV_TEST_RECORDSET_H_ +#define _TMWSERV_TEST_RECORDSET_H_ + + +#include <cppunit/extensions/HelperMacros.h> + +#include "dalexcept.h" + + +/** + * Unit test for the RecordSet class. + */ +class RecordSetTest: public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RecordSetTest); + + // add tests to the test suite. + CPPUNIT_TEST(testRows1); + CPPUNIT_TEST(testRows2); + CPPUNIT_TEST(testCols1); + CPPUNIT_TEST(testCols2); + CPPUNIT_TEST(testIsEmpty1); + CPPUNIT_TEST(testIsEmpty2); + CPPUNIT_TEST_EXCEPTION(testOperator1, std::invalid_argument); + CPPUNIT_TEST(testOperator2); + CPPUNIT_TEST_EXCEPTION(testOperator3, std::out_of_range); + CPPUNIT_TEST(testOperator4); + CPPUNIT_TEST_EXCEPTION(testOperator5, std::out_of_range); + CPPUNIT_TEST_EXCEPTION(testOperator6, std::invalid_argument); + CPPUNIT_TEST(testOutputStream1); + CPPUNIT_TEST(testOutputStream2); + CPPUNIT_TEST(testAdd1); + CPPUNIT_TEST_EXCEPTION(testAdd2, std::invalid_argument); + CPPUNIT_TEST_EXCEPTION(testAdd3, tmwserv::dal::RsColumnHeadersNotSet); + + CPPUNIT_TEST_SUITE_END(); + + + public: + /** + * Set up fixtures. + */ + void + setUp(void); + + + /** + * Tear down fixtures. + */ + void + tearDown(void); + + + /** + * Call RecordSet::rows() from an empty RecordSet. + */ + void + testRows1(void); + + + /** + * Call RecordSet::rows() from a non-empty RecordSet. + */ + void + testRows2(void); + + + /** + * Call RecordSet::cols() from an empty RecordSet. + */ + void + testCols1(void); + + + /** + * Call RecordSet::cols() from a non-empty RecordSet. + */ + void + testCols2(void); + + + /** + * Call RecordSet::isEmpty() from an empty RecordSet. + */ + void + testIsEmpty1(void); + + + /** + * Call RecordSet::isEmpty() from a non-empty RecordSet. + */ + void + testIsEmpty2(void); + + + /** + * Call RecordSet::operator() from an empty RecordSet. + */ + void + testOperator1(void); + + + /** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index and a column index as parameters. + */ + void + testOperator2(void); + + /** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index and a column index that are out of range as + * parameters. + */ + void + testOperator3(void); + + + /** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index and a field name as parameters. + */ + void + testOperator4(void); + + + /** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index that is out of range and a field name as parameters. + */ + void + testOperator5(void); + + + /** + * Call RecordSet::operator() from a non-empty RecordSet with + * a row index and a field name that does not exist as parameters. + */ + void + testOperator6(void); + + + /** + * Test writing an empty RecordSet to an output stream. + */ + void + testOutputStream1(void); + + + /** + * Test writing a non-empty RecordSet to an output stream. + */ + void + testOutputStream2(void); + + + /** + * Call RecordSet::add() to add a new now. + */ + void + testAdd1(void); + + + /** + * Call RecordSet::add() to add a new now with a different number + * of fields. + */ + void + testAdd2(void); + + + /** + * Call RecordSet::add() to add a new now to a RecordSet that does + * not have column headers. + */ + void + testAdd3(void); + + + private: + tmwserv::dal::RecordSet mEmptyRs; /**< empty recordset */ + tmwserv::dal::RecordSet mNonEmptyRs; /**< recordset with some data */ +}; + + +#endif // _TMWSERV_TEST_RECORDSET_H_ |