summaryrefslogtreecommitdiff
path: root/src/dal
diff options
context:
space:
mode:
authorHuynh Tran <nthuynh75@gmail.com>2005-06-16 17:19:27 +0000
committerHuynh Tran <nthuynh75@gmail.com>2005-06-16 17:19:27 +0000
commit4a33343730f075018cc7c5424335f2137d11619a (patch)
tree59c579b1dbb6edee819a59a65b0c4c1e52878f71 /src/dal
parent154515dbaeff0dfb0501f96464a7016945f45fbd (diff)
downloadmanaserv-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/dal')
-rw-r--r--src/dal/dalexcept.h51
-rw-r--r--src/dal/dataprovider.cpp4
-rw-r--r--src/dal/dataprovider.h130
-rw-r--r--src/dal/dataproviderfactory.cpp13
-rw-r--r--src/dal/dataproviderfactory.h18
-rw-r--r--src/dal/mysqldataprovider.cpp128
-rw-r--r--src/dal/mysqldataprovider.h72
-rw-r--r--src/dal/recordset.cpp82
-rw-r--r--src/dal/recordset.h80
-rw-r--r--src/dal/sqlitedataprovider.cpp87
-rw-r--r--src/dal/sqlitedataprovider.h73
-rw-r--r--src/dal/testdataprovider.cpp199
-rw-r--r--src/dal/testdataprovider.h138
-rw-r--r--src/dal/testmain.cpp44
-rw-r--r--src/dal/testrecordset.cpp286
-rw-r--r--src/dal/testrecordset.h209
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_