Forráskód Böngészése

Row and RowIterator done and tested (windows and linux)

Aleksandar Fabijanic 18 éve
szülő
commit
b2977d3df2

+ 12 - 0
Data/Data_VS71.vcproj

@@ -211,6 +211,12 @@
 				<File
 					RelativePath=".\include\Poco\Data\RecordSet.h">
 				</File>
+				<File
+					RelativePath=".\include\Poco\Data\Row.h">
+				</File>
+				<File
+					RelativePath=".\include\Poco\Data\RowIterator.h">
+				</File>
 				<File
 					RelativePath=".\include\Poco\Data\Session.h">
 				</File>
@@ -278,6 +284,12 @@
 				<File
 					RelativePath=".\src\RecordSet.cpp">
 				</File>
+				<File
+					RelativePath=".\src\Row.cpp">
+				</File>
+				<File
+					RelativePath=".\src\RowIterator.cpp">
+				</File>
 				<File
 					RelativePath=".\src\Session.cpp">
 				</File>

+ 1 - 1
Data/Makefile

@@ -12,7 +12,7 @@ objects = AbstractBinder AbstractBinding AbstractExtraction \
 	AbstractExtractor AbstractPreparation AbstractPrepare \
 	BLOB BLOBStream DataException Limit MetaColumn \
 	PooledSessionHolder PooledSessionImpl \
-	Range RecordSet Session SessionFactory SessionImpl \
+	Range RecordSet Row RowIterator Session SessionFactory SessionImpl \
 	Connector SessionPool Statement StatementCreator StatementImpl
 
 target         = PocoData

+ 16 - 0
Data/ODBC/testsuite/src/ODBCDB2Test.cpp

@@ -1051,6 +1051,21 @@ void ODBCDB2Test::testStoredFunction()
 }
 
 
+void ODBCDB2Test::testRowIterator()
+{
+	if (!_pSession) fail ("Test not available.");
+
+	for (int i = 0; i < 8;)
+	{
+		recreateVectorsTable();
+		_pSession->setFeature("autoBind", bindValues[i]);
+		_pSession->setFeature("autoExtract", bindValues[i+1]);
+		_pExecutor->rowIterator();
+		i += 2;
+	}
+}
+
+
 void ODBCDB2Test::dropObject(const std::string& type, const std::string& name)
 {
 	try
@@ -1305,6 +1320,7 @@ CppUnit::Test* ODBCDB2Test::suite()
 		CppUnit_addTest(pSuite, ODBCDB2Test, testStoredProcedure);
 		CppUnit_addTest(pSuite, ODBCDB2Test, testStoredFunction);
 		CppUnit_addTest(pSuite, ODBCDB2Test, testNull);
+		CppUnit_addTest(pSuite, ODBCDB2Test, testRowIterator);
 
 		return pSuite;
 	}

+ 1 - 0
Data/ODBC/testsuite/src/ODBCDB2Test.h

@@ -123,6 +123,7 @@ public:
 	void testStoredFunction();
 
 	void testNull();
+	void testRowIterator();
 
 	void setUp();
 	void tearDown();

+ 16 - 1
Data/ODBC/testsuite/src/ODBCMySQLTest.cpp

@@ -915,6 +915,21 @@ void ODBCMySQLTest::testStoredFunction()
 }
 
 
+void ODBCMySQLTest::testRowIterator()
+{
+	if (!_pSession) fail ("Test not available.");
+
+	for (int i = 0; i < 8;)
+	{
+		recreateVectorsTable();
+		_pSession->setFeature("autoBind", bindValues[i]);
+		_pSession->setFeature("autoExtract", bindValues[i+1]);
+		_pExecutor->rowIterator();
+		i += 2;
+	}
+}
+
+
 void ODBCMySQLTest::dropObject(const std::string& type, const std::string& name)
 {
 	*_pSession << format("DROP %s IF EXISTS %s", type, name), now;
@@ -1152,7 +1167,7 @@ CppUnit::Test* ODBCMySQLTest::suite()
 		CppUnit_addTest(pSuite, ODBCMySQLTest, testInternalExtraction);
 		CppUnit_addTest(pSuite, ODBCMySQLTest, testInternalStorageType);
 		CppUnit_addTest(pSuite, ODBCMySQLTest, testNull);
-
+		CppUnit_addTest(pSuite, ODBCMySQLTest, testRowIterator);
 		return pSuite;
 	}
 

+ 1 - 0
Data/ODBC/testsuite/src/ODBCMySQLTest.h

@@ -125,6 +125,7 @@ public:
 	void testStoredFunction();
 
 	void testNull();
+	void testRowIterator();
 
 	void setUp();
 	void tearDown();

+ 16 - 0
Data/ODBC/testsuite/src/ODBCOracleTest.cpp

@@ -1068,6 +1068,21 @@ void ODBCOracleTest::testStoredFunction()
 }
 
 
+void ODBCOracleTest::testRowIterator()
+{
+	if (!_pSession) fail ("Test not available.");
+
+	for (int i = 0; i < 8;)
+	{
+		recreateVectorsTable();
+		_pSession->setFeature("autoBind", bindValues[i]);
+		_pSession->setFeature("autoExtract", bindValues[i+1]);
+		_pExecutor->rowIterator();
+		i += 2;
+	}
+}
+
+
 void ODBCOracleTest::dropObject(const std::string& type, const std::string& name)
 {
 	try
@@ -1345,6 +1360,7 @@ CppUnit::Test* ODBCOracleTest::suite()
 		CppUnit_addTest(pSuite, ODBCOracleTest, testInternalExtraction);
 		CppUnit_addTest(pSuite, ODBCOracleTest, testInternalStorageType);
 		CppUnit_addTest(pSuite, ODBCOracleTest, testNull);
+		CppUnit_addTest(pSuite, ODBCOracleTest, testRowIterator);
 
 		return pSuite;
 	}

+ 1 - 0
Data/ODBC/testsuite/src/ODBCOracleTest.h

@@ -123,6 +123,7 @@ public:
 	void testStoredFunction();
 
 	void testNull();
+	void testRowIterator();
 
 	void setUp();
 	void tearDown();

+ 16 - 0
Data/ODBC/testsuite/src/ODBCPostgreSQLTest.cpp

@@ -954,6 +954,21 @@ void ODBCPostgreSQLTest::testStoredFunction()
 }
 
 
+void ODBCPostgreSQLTest::testRowIterator()
+{
+	if (!_pSession) fail ("Test not available.");
+
+	for (int i = 0; i < 8;)
+	{
+		recreateVectorsTable();
+		_pSession->setFeature("autoBind", bindValues[i]);
+		_pSession->setFeature("autoExtract", bindValues[i+1]);
+		_pExecutor->rowIterator();
+		i += 2;
+	}
+}
+
+
 void ODBCPostgreSQLTest::configurePLPgSQL()
 {
 	if (!_pSession) fail ("Test not available.");
@@ -1263,6 +1278,7 @@ CppUnit::Test* ODBCPostgreSQLTest::suite()
 		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testInternalStorageType);
 		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testStoredFunction);
 		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testNull);
+		CppUnit_addTest(pSuite, ODBCPostgreSQLTest, testRowIterator);
 
 		return pSuite;
 	}

+ 1 - 0
Data/ODBC/testsuite/src/ODBCPostgreSQLTest.h

@@ -124,6 +124,7 @@ public:
 
 	void testStoredFunction();
 	void testNull();
+	void testRowIterator();
 
 	void setUp();
 	void tearDown();

+ 16 - 0
Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp

@@ -1054,6 +1054,21 @@ void ODBCSQLServerTest::testStoredFunction()
 }
 
 
+void ODBCSQLServerTest::testRowIterator()
+{
+	if (!_pSession) fail ("Test not available.");
+
+	for (int i = 0; i < 8;)
+	{
+		recreateVectorsTable();
+		_pSession->setFeature("autoBind", bindValues[i]);
+		_pSession->setFeature("autoExtract", bindValues[i+1]);
+		_pExecutor->rowIterator();
+		i += 2;
+	}
+}
+
+
 void ODBCSQLServerTest::dropObject(const std::string& type, const std::string& name)
 {
 	try
@@ -1321,6 +1336,7 @@ CppUnit::Test* ODBCSQLServerTest::suite()
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testInternalExtraction);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testInternalStorageType);
 		CppUnit_addTest(pSuite, ODBCSQLServerTest, testNull);
+		CppUnit_addTest(pSuite, ODBCSQLServerTest, testRowIterator);
 
 		return pSuite;
 	}

+ 1 - 0
Data/ODBC/testsuite/src/ODBCSQLServerTest.h

@@ -126,6 +126,7 @@ public:
 	void testInternalStorageType();
 
 	void testNull();
+	void testRowIterator();
 
 	void setUp();
 	void tearDown();

+ 16 - 0
Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp

@@ -890,6 +890,21 @@ void ODBCSQLiteTest::testNull()
 }
 
 
+void ODBCSQLiteTest::testRowIterator()
+{
+	if (!_pSession) fail ("Test not available.");
+
+	for (int i = 0; i < 8;)
+	{
+		recreateVectorsTable();
+		_pSession->setFeature("autoBind", bindValues[i]);
+		_pSession->setFeature("autoExtract", bindValues[i+1]);
+		_pExecutor->rowIterator();
+		i += 2;
+	}
+}
+
+
 void ODBCSQLiteTest::dropObject(const std::string& type, const std::string& name)
 {
 	try
@@ -1136,6 +1151,7 @@ CppUnit::Test* ODBCSQLiteTest::suite()
 		CppUnit_addTest(pSuite, ODBCSQLiteTest, testInternalExtraction);
 		CppUnit_addTest(pSuite, ODBCSQLiteTest, testInternalStorageType);
 		CppUnit_addTest(pSuite, ODBCSQLiteTest, testNull);
+		CppUnit_addTest(pSuite, ODBCSQLiteTest, testRowIterator);
 
 		return pSuite;
 	}

+ 1 - 0
Data/ODBC/testsuite/src/ODBCSQLiteTest.h

@@ -120,6 +120,7 @@ public:
 	void testInternalStorageType();
 
 	void testNull();
+	void testRowIterator();
 
 	void setUp();
 	void tearDown();

+ 34 - 0
Data/ODBC/testsuite/src/SQLExecutor.cpp

@@ -42,6 +42,7 @@
 #include "Poco/Data/BLOB.h"
 #include "Poco/Data/StatementImpl.h"
 #include "Poco/Data/RecordSet.h"
+#include "Poco/Data/RowIterator.h"
 #include "Poco/Data/ODBC/Connector.h"
 #include "Poco/Data/ODBC/Utility.h"
 #include "Poco/Data/ODBC/Diagnostics.h"
@@ -50,6 +51,8 @@
 #include "Poco/Data/ODBC/ODBCStatementImpl.h"
 #include <sqltypes.h>
 #include <iostream>
+#include <sstream>
+#include <iterator>
 
 
 using namespace Poco::Data;
@@ -2139,3 +2142,34 @@ void SQLExecutor::nulls()
 	assert (rs.isNull("v"));
 	assert (rs["v"] == "");
 }
+
+
+void SQLExecutor::rowIterator()
+{
+	std::string funct = "internalExtraction()";
+	std::vector<Tuple<int, double, std::string> > v;
+	v.push_back(Tuple<int, double, std::string>(1, 1.5f, "3"));
+	v.push_back(Tuple<int, double, std::string>(2, 2.5f, "4"));
+	v.push_back(Tuple<int, double, std::string>(3, 3.5f, "5"));
+	v.push_back(Tuple<int, double, std::string>(4, 4.5f, "6"));
+
+	try { *_pSession << "INSERT INTO Vectors VALUES (?,?,?)", use(v), now; }
+	catch(ConnectionException& ce){ std::cout << ce.toString() << std::endl; fail (funct); }
+	catch(StatementException& se){ std::cout << se.toString() << std::endl; fail (funct); }
+
+	RecordSet rset(*_pSession, "SELECT * FROM Vectors");
+
+	std::ostringstream osLoop;
+	RecordSet::Iterator it = rset.begin();
+	RecordSet::Iterator end = rset.end();
+	for (int i = 1; it != end; ++it, ++i) 
+	{
+		assert (it->get(0) == i);
+		osLoop << *it;
+	}
+	assert (!osLoop.str().empty());
+
+	std::ostringstream osCopy;
+	std::copy(rset.begin(), rset.end(), std::ostream_iterator<Row>(osCopy));
+	assert (osLoop.str() == osCopy.str());
+}

+ 1 - 0
Data/ODBC/testsuite/src/SQLExecutor.h

@@ -131,6 +131,7 @@ public:
 	void internalStorageType();
 	void nulls();
 	void notNulls(const std::string& sqlState = "23502");
+	void rowIterator();
 
 private:
 	Poco::Data::Session* _pSession;

+ 33 - 0
Data/SQLite/testsuite/src/SQLiteTest.cpp

@@ -1669,6 +1669,38 @@ void SQLiteTest::testNull()
 }
 
 
+void SQLiteTest::testRowIterator()
+{
+	Session ses (SessionFactory::instance().create(SQLite::Connector::KEY, "dummy.db"));
+	ses << "DROP TABLE IF EXISTS Vectors", now;
+	ses << "CREATE TABLE Vectors (int0 INTEGER, flt0 REAL, str0 VARCHAR)", now;
+
+	std::vector<Tuple<int, double, std::string> > v;
+	v.push_back(Tuple<int, double, std::string>(1, 1.5f, "3"));
+	v.push_back(Tuple<int, double, std::string>(2, 2.5f, "4"));
+	v.push_back(Tuple<int, double, std::string>(3, 3.5f, "5"));
+	v.push_back(Tuple<int, double, std::string>(4, 4.5f, "6"));
+
+	ses << "INSERT INTO Vectors VALUES (?,?,?)", use(v), now;
+
+	RecordSet rset(ses, "SELECT * FROM Vectors");
+
+	std::ostringstream osLoop;
+	RecordSet::Iterator it = rset.begin();
+	RecordSet::Iterator end = rset.end();
+	for (int i = 1; it != end; ++it, ++i) 
+	{
+		assert (it->get(0) == i);
+		osLoop << *it;
+	}
+	assert (!osLoop.str().empty());
+
+	std::ostringstream osCopy;
+	std::copy(rset.begin(), rset.end(), std::ostream_iterator<Row>(osCopy));
+	assert (osLoop.str() == osCopy.str());
+}
+
+
 void SQLiteTest::setUp()
 {
 }
@@ -1742,6 +1774,7 @@ CppUnit::Test* SQLiteTest::suite()
 	CppUnit_addTest(pSuite, SQLiteTest, testInternalExtraction);
 	CppUnit_addTest(pSuite, SQLiteTest, testPrimaryKeyConstraint);
 	CppUnit_addTest(pSuite, SQLiteTest, testNull);
+	CppUnit_addTest(pSuite, SQLiteTest, testRowIterator);
 
 	return pSuite;
 }

+ 1 - 0
Data/SQLite/testsuite/src/SQLiteTest.h

@@ -111,6 +111,7 @@ public:
 	void testInternalExtraction();
 	void testPrimaryKeyConstraint();
 	void testNull();
+	void testRowIterator();
 
 	void setUp();
 	void tearDown();

+ 5 - 4
Data/include/Poco/Data/RecordSet.h

@@ -78,6 +78,7 @@ class Data_API RecordSet: private Statement
 {
 public:
 	typedef std::map<std::size_t, Row*> RowMap;
+	typedef RowIterator Iterator;
 
 	using Statement::isNull;
 
@@ -132,8 +133,8 @@ public:
 		}
 	}
 
-	const Row& row(std::size_t pos) const;
-		/// Returns row at position pos.
+	Row& row(std::size_t pos);
+		/// Returns reference to row at position pos.
 		/// Rows are lazy-created and cached.
 
 	template <class T>
@@ -278,7 +279,7 @@ private:
 	std::size_t    _currentRow;
 	RowIterator*   _pBegin;
 	RowIterator*   _pEnd;
-	mutable RowMap _rowMap;
+	RowMap         _rowMap;
 };
 
 
@@ -380,7 +381,7 @@ inline bool RecordSet::isNull(const std::string& name)
 inline const RowIterator& RecordSet::end()
 {
 	if (!_pEnd)
-		_pEnd = new RowIterator(*this);
+		_pEnd = new RowIterator(*this, true);
 
 	return *_pEnd;
 }

+ 122 - 23
Data/include/Poco/Data/Row.h

@@ -1,7 +1,7 @@
 //
 // Row.h
 //
-// $Id: //poco/Main/Data/include/Poco/Data/Row.h#7 $
+// $Id: //poco/Main/Data/include/Poco/Data/Row.h#1 $
 //
 // Library: Data
 // Package: DataCore
@@ -42,9 +42,11 @@
 
 #include "Poco/Data/Data.h"
 #include "Poco/DynamicAny.h"
+#include "Poco/Tuple.h"
+#include "Poco/SharedPtr.h"
 #include <vector>
 #include <string>
-#include <iostream>
+#include <sstream>
 
 
 namespace Poco {
@@ -55,10 +57,31 @@ class RecordSet;
 
 
 class Data_API Row
-	/// Row class.
+	/// Row class provides a data type for RecordSet iteration purposes.
+	/// Dereferencing a RowIterator returns Row.
+	/// Rows are sortable. The sortability is maintained at all times (i.e. there
+	/// is always at least one column specified as a sorting criteria) .
+	/// The default and minimal sorting criteria is the first field (position 0).
+	/// The default sorting criteria can be replaced with any other field by 
+	/// calling replaceSortField() member function.
+	/// Additional fields can be added to sorting criteria, in which case the
+	/// field precedence corresponds to addition order (i.e. later added fields
+	/// have lower sorting precedence).
+	/// These features make Row suitable for use with standard sorted 
+	/// containers and algorithms. The main constraint is that all the rows from
+	/// a set that is being sorted must have the same sorting criteria (i.e., the same
+	/// set of fields must be in sorting criteria in the same order). Since rows don't
+	/// know about each other, it is the programmer's responsibility to ensure this
+	/// constraint is satisfied.
+	/// Field names are a shared pointer to a vector of strings. For efficiency sake,
+	/// a constructor taking a shared pointer to names vector argument is provided.
+	/// The stream operator is provided for Row data type as a free-standing function.
 {
 public:
-	enum Comparison
+	typedef std::vector<std::string> NameVec;
+	typedef SharedPtr<std::vector<std::string> > NameVecPtr;
+
+	enum ComparisonType
 	{
 		COMPARE_AS_INTEGER,
 		COMPARE_AS_FLOAT,
@@ -70,9 +93,15 @@ public:
 	Row();
 		/// Creates the Row.
 
+	explicit Row(NameVecPtr pNames);
+		/// Creates the Row.
+
 	~Row();
 		/// Destroys the Row.
 
+	DynamicAny& get(std::size_t col);
+		/// Returns the reference to data value at column location.
+
 	DynamicAny& operator [] (std::size_t col);
 		/// Returns the reference to data value at column location.
 
@@ -83,31 +112,78 @@ public:
 	void append(const std::string& name, const T& val)
 		/// Appends the value to the row.
 	{
+		if (!_pNames) _pNames = new NameVec;
 		DynamicAny da = val;
 		_values.push_back(da);
-		_names.push_back(name);
+		_pNames->push_back(name);
+		if (1 == _values.size()) addSortField(0);
+	}
+	
+	template <typename T>
+	void set(std::size_t pos, const T& val)
+		/// Assigns the value to the row.
+	{
+		try
+		{
+			_values.at(pos) = val;
+		}catch (std::out_of_range&)
+		{
+			throw RangeException("Invalid column number.");
+		}
 	}
-		
+
+	template <typename T>
+	void set(const std::string& name, const T& val)
+		/// Assigns the value to the row.
+	{
+		NameVec::iterator it = _pNames->begin();
+		NameVec::iterator end = _pNames->end();
+		for (int i = 0; it != end; ++it, ++i)
+		{
+			if (*it == name)
+				return set(i, val);
+		}
+
+		std::ostringstream os;
+		os << "Column with name " << name << " not found.";
+		throw NotFoundException(os.str());
+	}
+
 	std::size_t fieldCount() const;
 		/// Returns the number of fields in this row.
 
 	void reset();
-		/// Resets the row.
+		/// Resets the row by clearing all field names and values.
 
 	void separator(const std::string& sep);
 		/// Sets the separator.
 
-	void sortField(std::size_t pos);
-		/// Sets the field used for sorting.
+	void addSortField(std::size_t pos);
+		/// Adds the field used for sorting.
+
+	void addSortField(const std::string& name);
+		/// Adds the field used for sorting.
+
+	void removeSortField(std::size_t pos);
+		/// Removes the field used for sorting.
+
+	void removeSortField(const std::string& name);
+		/// Removes the field used for sorting.
 
-	void sortField(const std::string& name);
-		/// Sets the field used for sorting.
+	void replaceSortField(std::size_t oldPos, std::size_t newPos);
+		/// Replaces the field used for sorting.
 
-	const std::string& toStringN() const;
+	void replaceSortField(const std::string& oldName, const std::string& newName);
+		/// Replaces the field used for sorting.
+
+	void resetSort();
+		/// Resets the sorting criteria to field 0 only.
+
+	const std::string namesToString() const;
 		/// Converts the row names to string, inserting separator
 		/// string between fields and end-of-line at the end.
 
-	const std::string& toStringV() const;
+	const std::string valuesToString() const;
 		/// Converts the row values to string, inserting separator
 		/// string between fields and end-of-line at the end.
 
@@ -120,21 +196,26 @@ public:
 	bool operator < (const Row& other) const;
 		/// Less-then operator.
 
-	void comparison(Comparison comp);
-		/// Sets the type of comparison.
+	NameVecPtr names();
+		/// Returns the shared pointer to names vector.
 
 private:
+	typedef std::vector<DynamicAny> ValueVec;
+	typedef Tuple<std::size_t, ComparisonType> SortTuple;
+	typedef std::vector<SortTuple> SortMap;
+		/// The type for map holding fields used for sorting criteria.
+		/// Fields are added sequentially and have precedence that
+		/// corresponds to adding order rather than field's position in the row.
+		/// That requirement rules out use of std::map due to its sorted nature.
+
 	std::size_t getPosition(const std::string& name);
 	bool isEqualSize(const Row& other) const;
 	bool isEqualType(const Row& other) const;
 
-	std::vector<std::string> _names;
-	std::vector<DynamicAny>  _values;
-	mutable std::string      _strValues;
-	mutable std::string      _strNames;
-	std::string              _separator;
-	std::size_t              _sortField;
-	Comparison               _comparison;
+	std::string         _separator;
+	NameVecPtr          _pNames;
+	ValueVec            _values;
+	SortMap             _sortFields;
 };
 
 
@@ -152,7 +233,7 @@ inline std::size_t Row::fieldCount() const
 
 inline void Row::reset()
 {
-	_names.clear();
+	_pNames->clear();
 	_values.clear();
 }
 
@@ -163,6 +244,24 @@ inline void Row::separator(const std::string& sep)
 }
 
 
+inline Row::NameVecPtr Row::names()
+{
+	return _pNames;
+}
+
+
+inline DynamicAny& Row::operator [] (std::size_t col)
+{
+	return get(col);
+}
+
+
+inline DynamicAny& Row::operator [] (const std::string& name)
+{
+	return get(getPosition(name));
+}
+
+
 } } // namespace Poco::Data
 
 

+ 24 - 18
Data/include/Poco/Data/RowIterator.h

@@ -1,7 +1,7 @@
 //
 // RowIterator.h
 //
-// $Id: //poco/Main/Data/include/Poco/Data/RowIterator.h#7 $
+// $Id: //poco/Main/Data/include/Poco/Data/RowIterator.h#1 $
 //
 // Library: Data
 // Package: DataCore
@@ -43,6 +43,7 @@
 #include "Poco/Data/Data.h"
 #include "Poco/Data/Row.h"
 #include "Poco/DynamicAny.h"
+#include <iterator>
 
 
 namespace Poco {
@@ -53,10 +54,16 @@ class RecordSet;
 
 
 class Data_API RowIterator
-	/// RowIterator class is an interface to a row of RecordSet data.
+	/// RowIterator class.
 {
 public:
-	RowIterator(const RecordSet& recordSet, bool isEmpty = true);
+	typedef std::bidirectional_iterator_tag iterator_category;
+	typedef Row value_type;
+	typedef std::ptrdiff_t difference_type;
+	typedef Row* pointer;
+	typedef Row& reference;
+
+	RowIterator(RecordSet& recordSet, bool positionEnd = false);
 		/// Creates the RowIterator and positions it at the beginning.
 
 	~RowIterator();
@@ -68,24 +75,23 @@ public:
 	bool operator != (const RowIterator& other);
 		/// Inequality operator.
 
-	const Row& operator * () const;
-		/// Returns const reference to the current row.
+	Row& operator * () const;
+		/// Returns reference to the current row.
+
+	Row* operator -> () const;
+		/// Returns pointer to the current row.
 
-	const Row& operator ++ ();
-		/// Advances by one position and returns const reference 
-		/// to the current row.
+	std::size_t operator ++ ();
+		/// Advances by one position and returns current position.
 
-	const Row& operator ++ (int);
-		/// Advances by one position and returns const reference 
-		/// to the previous current row.
+	std::size_t operator ++ (int);
+		/// Advances by one position and returns previous current position.
 
-	const Row& operator -- ();
-		/// Goes back by one position and returns const reference 
-		/// to the current row.
+	std::size_t operator -- ();
+		/// Goes back by one position and returns current position.
 
-	const Row& operator -- (int);
-		/// Goes back by one position and returns const reference 
-		/// to the previous current row.
+	std::size_t operator -- (int);
+		/// Goes back by one position and returns previouscurrent position.
 
 private:
 	RowIterator();
@@ -95,8 +101,8 @@ private:
 
 	static const int POSITION_END;
 
+	RecordSet& _recordSet;
 	std::size_t _position;
-	const RecordSet& _recordSet;
 };
 
 

+ 16 - 5
Data/src/RecordSet.cpp

@@ -121,13 +121,13 @@ DynamicAny RecordSet::value(const std::string& name, std::size_t row) const
 const RowIterator& RecordSet::begin()
 {
 	if (!_pBegin)
-		_pBegin = new RowIterator(*this, 0 == extractions().size());
+		_pBegin = new RowIterator(*this);
 
 	return *_pBegin;
 }
 
 
-const Row& RecordSet::row(std::size_t pos) const
+Row& RecordSet::row(std::size_t pos)
 {
 	if (pos > rowCount() - 1)
 		throw RangeException("Invalid recordset row requested.");
@@ -136,12 +136,23 @@ const Row& RecordSet::row(std::size_t pos) const
 	Row* pRow = 0;
 	if (it == _rowMap.end())
 	{
-		pRow = new Row;
-		for (std::size_t i = 0; i < columnCount(); ++i)
-			pRow->append(metaColumn(static_cast<UInt32>(pos)).name(), value(i, pos));
+		if (_rowMap.size())//reuse first row column names to save some memory 
+		{
+			pRow = new Row(_rowMap.begin()->second->names());
+			for (std::size_t i = 0; i < columnCount(); ++i)
+				pRow->set(i, value(i, pos));
+		}
+		else 
+		{
+			pRow = new Row;
+			for (std::size_t i = 0; i < columnCount(); ++i)
+				pRow->append(metaColumn(static_cast<UInt32>(pos)).name(), value(i, pos));
+		}
 
 		_rowMap.insert(RowMap::value_type(pos, pRow));
 	}
+	else
+		pRow = it->second;
 
 	poco_check_ptr (pRow);
 	return *pRow;

+ 170 - 60
Data/src/Row.cpp

@@ -1,7 +1,7 @@
 //
 // Row.cpp
 //
-// $Id: //poco/Main/Data/src/Row.cpp#2 $
+// $Id: //poco/Main/Data/src/Row.cpp#1 $
 //
 // Library: Data
 // Package: DataCore
@@ -52,60 +52,74 @@ const std::string Row::EOL = "\n";
 
 std::ostream& operator << (std::ostream &os, const Row& row)
 {
-      os << row.toStringV();
+      os << row.valuesToString();
       return os;
 }
 
 
-Row::Row(): 
-	_separator("\t"), 
-	_sortField(0),
-	_comparison(COMPARE_AS_STRING)
+Row::Row(): _separator("\t"), _pNames(0)
 {
 }
 
 
+Row::Row(NameVecPtr pNames): _separator("\t"), _pNames(pNames)
+{
+	if (!_pNames)
+		throw NullPointerException();
+
+	_values.resize(_pNames->size());
+	addSortField(0);
+}
+
+
 Row::~Row()
 {
 }
 
 
-DynamicAny& Row::operator [] (std::size_t col)
+DynamicAny& Row::get(std::size_t col)
 {
 	try
 	{
 		return _values.at(col);
-	}catch (std::range_error& re)
+	}catch (std::out_of_range& re)
 	{
 		throw RangeException(re.what());
 	}
 }
 
 
-DynamicAny& Row::operator [] (const std::string& name)
-{
-	std::size_t col = getPosition(name);
-	return (*this)[col];
-}
-
-
 std::size_t Row::getPosition(const std::string& name)
 {
-	std::vector<std::string>::const_iterator it = _names.begin();
-	std::vector<std::string>::const_iterator end = _names.end();
+	if (!_pNames)
+		throw NullPointerException();
+
+	NameVec::const_iterator it = _pNames->begin();
+	NameVec::const_iterator end = _pNames->end();
 	std::size_t col = 0;
 	for (; it != end; ++it, ++col)
 		if (name == *it) break;
 	
+	if (it == end)
+		throw NotFoundException(name);
+
 	return col;
 }
 
 
-void Row::sortField(std::size_t pos)
+void Row::addSortField(std::size_t pos)
 {
 	poco_assert (pos <= _values.size());
-	_sortField = pos;
 
+	SortMap::iterator it = _sortFields.begin();
+	SortMap::iterator end = _sortFields.end();
+	for (; it != end; ++it)
+	{
+		if (it->get<0>() == pos)
+			throw InvalidAccessException("Field already in comparison set.");
+	}
+
+	ComparisonType ct;
 	if ((_values[pos].type() == typeid(Poco::Int8))   ||
 		(_values[pos].type() == typeid(Poco::UInt8))  ||
 		(_values[pos].type() == typeid(Poco::Int16))  ||
@@ -116,24 +130,103 @@ void Row::sortField(std::size_t pos)
 		(_values[pos].type() == typeid(Poco::UInt64)) ||
 		(_values[pos].type() == typeid(bool)))
 	{
-		comparison(COMPARE_AS_INTEGER);
+		ct = COMPARE_AS_INTEGER;
 	}
 	else if ((_values[pos].type() == typeid(float)) ||
 		(_values[pos].type() == typeid(double)))
 	{
-		comparison(COMPARE_AS_FLOAT);
+		ct = COMPARE_AS_FLOAT;
+	}
+	else
+	{
+		ct = COMPARE_AS_STRING;
+	}
+
+	_sortFields.push_back(SortTuple(pos, ct));
+}
+
+
+void Row::addSortField(const std::string& name)
+{
+	addSortField(getPosition(name));
+}
+
+
+void Row::removeSortField(std::size_t pos)
+{
+	SortMap::iterator it = _sortFields.begin();
+	SortMap::iterator end = _sortFields.end();
+	for (; it != end; ++it)
+	{
+		if (it->get<0>() == pos)
+		{
+			_sortFields.erase(it);
+			return;
+		}
+	}
+}
+
+
+void Row::removeSortField(const std::string& name)
+{
+	removeSortField(getPosition(name));
+}
+
+
+void Row::replaceSortField(std::size_t oldPos, std::size_t newPos)
+{
+	poco_assert (oldPos <= _values.size());
+	poco_assert (newPos <= _values.size());
+
+	ComparisonType ct;
+
+	if ((_values[newPos].type() == typeid(Poco::Int8))   ||
+		(_values[newPos].type() == typeid(Poco::UInt8))  ||
+		(_values[newPos].type() == typeid(Poco::Int16))  ||
+		(_values[newPos].type() == typeid(Poco::UInt16)) ||
+		(_values[newPos].type() == typeid(Poco::Int32))  ||
+		(_values[newPos].type() == typeid(Poco::UInt32)) ||
+		(_values[newPos].type() == typeid(Poco::Int64))  ||
+		(_values[newPos].type() == typeid(Poco::UInt64)) ||
+		(_values[newPos].type() == typeid(bool)))
+	{
+		ct = COMPARE_AS_INTEGER;
+	}
+	else if ((_values[newPos].type() == typeid(float)) ||
+		(_values[newPos].type() == typeid(double)))
+	{
+		ct = COMPARE_AS_FLOAT;
 	}
 	else
 	{
-		comparison(COMPARE_AS_STRING);
+		ct = COMPARE_AS_STRING;
+	}
+
+	SortMap::iterator it = _sortFields.begin();
+	SortMap::iterator end = _sortFields.end();
+	for (; it != end; ++it)
+	{
+		if (it->get<0>() == oldPos)
+		{
+			*it = SortTuple(newPos, ct);
+			return;
+		}
 	}
 
+	throw NotFoundException("Field not found");
+}
+
+
+void Row::replaceSortField(const std::string& oldName, const std::string& newName)
+{
+	replaceSortField(getPosition(oldName), getPosition(newName));
 }
 
 
-void Row::sortField(const std::string& name)
+void Row::resetSort()
 {
-	sortField(getPosition(name));
+	_sortFields.clear();
+	if (_values.size())	addSortField(0);
 }
 
 
@@ -157,12 +250,6 @@ bool Row::isEqualType(const Row& other) const
 }
 
 
-void Row::comparison(Comparison comp)
-{
-	_comparison = comp;
-}
-
-
 bool Row::operator == (const Row& other) const
 {
 	if (!isEqualSize(other)) return false;
@@ -188,57 +275,80 @@ bool Row::operator != (const Row& other) const
 
 bool Row::operator < (const Row& other) const
 {
-	if (_sortField != other._sortField)
+	if (_sortFields != other._sortFields)
 		throw InvalidAccessException("Rows compared have different sorting criteria.");
 
-	switch (_comparison)
+	SortMap::const_iterator it = _sortFields.begin();
+	SortMap::const_iterator end = _sortFields.end();
+	for (; it != end; ++it)
 	{
-	case COMPARE_AS_INTEGER:
-		return (_values[_sortField].convert<Poco::Int64>() < 
-			other._values[other._sortField].convert<Poco::Int64>());
-
-	case COMPARE_AS_FLOAT:
-		return (_values[_sortField].convert<double>() < 
-			other._values[other._sortField].convert<double>());
-
-	case COMPARE_AS_STRING:
-		return (_values[_sortField].convert<std::string>() < 
-			other._values[other._sortField].convert<std::string>());
+		switch (it->get<1>())
+		{
+		case COMPARE_AS_INTEGER:
+			if (_values[it->get<0>()].convert<Poco::Int64>() < 
+				other._values[it->get<0>()].convert<Poco::Int64>())
+				return true;
+			else if (_values[it->get<0>()].convert<Poco::Int64>() != 
+				other._values[it->get<0>()].convert<Poco::Int64>())
+				return false;
+			break;
+
+		case COMPARE_AS_FLOAT:
+			if (_values[it->get<0>()].convert<double>() < 
+				other._values[it->get<0>()].convert<double>())
+				return true;
+			else if (_values[it->get<0>()].convert<double>() < 
+				other._values[it->get<0>()].convert<double>())
+				return false;
+			break;
+
+		case COMPARE_AS_STRING:
+			if (_values[it->get<0>()].convert<std::string>() < 
+				other._values[it->get<0>()].convert<std::string>())
+				return true;
+			else if (_values[it->get<0>()].convert<std::string>() < 
+				other._values[it->get<0>()].convert<std::string>())
+				return false;
+			break;
+		}
 	}
 
-	throw IllegalStateException("Unknown comparison mode.");
+	return false;
 }
 
 
-const std::string& Row::toStringV() const
+const std::string Row::valuesToString() const
 {
-	_strValues.clear();
-	std::vector<DynamicAny>::const_iterator it = _values.begin();
-	std::vector<DynamicAny>::const_iterator end = _values.end();
+	std::string strValues;
+	ValueVec::const_iterator it = _values.begin();
+	ValueVec::const_iterator end = _values.end();
 	for (; it != end; ++it)
 	{
-		_strValues.append(it->convert<std::string>());
-		_strValues.append(_separator);
+		strValues.append(it->convert<std::string>());
+		strValues.append(_separator);
 	}
-	_strValues.replace(_strValues.find_last_of(_separator), _separator.length(), EOL);
+	strValues.replace(strValues.find_last_of(_separator), _separator.length(), EOL);
 
-	return _strValues;
+	return strValues;
 }
 
 
-const std::string& Row::toStringN() const
+const std::string Row::namesToString() const
 {
-	_strNames.clear();
-	std::vector<std::string>::const_iterator it = _names.begin();
-	std::vector<std::string>::const_iterator end = _names.end();
+	if (!_pNames)
+		throw NullPointerException();
+
+	std::string strNames;
+	NameVec::const_iterator it = _pNames->begin();
+	NameVec::const_iterator end = _pNames->end();
 	for (; it != end; ++it)
 	{
-		_strNames.append(*it);
-		_strNames.append(_separator);
+		strNames.append(*it);
+		strNames.append(_separator);
 	}
-	_strNames.replace(_strNames.find_last_of(_separator), _separator.length(), EOL);
+	strNames.replace(strNames.find_last_of(_separator), _separator.length(), EOL);
 
-	return _strNames;
+	return strNames;
 }
 
 

+ 32 - 16
Data/src/RowIterator.cpp

@@ -1,7 +1,7 @@
 //
 // RowIterator.cpp
 //
-// $Id: //poco/Main/Data/src/RowIterator.cpp#2 $
+// $Id: //poco/Main/Data/src/RowIterator.cpp#1 $
 //
 // Library: Data
 // Package: DataCore
@@ -48,9 +48,9 @@ namespace Data {
 const int RowIterator::POSITION_END = std::numeric_limits<std::size_t>::max();
 
 
-RowIterator::RowIterator(const RecordSet& recordSet, bool isEmpty): 
-	_position(isEmpty ? POSITION_END : 0),
-	_recordSet(recordSet)
+RowIterator::RowIterator(RecordSet& recordSet, bool positionEnd): 
+	_recordSet(recordSet),
+	_position((0 == recordSet.rowCount()) || positionEnd ? POSITION_END : 0)
 {
 }
 
@@ -75,43 +75,59 @@ void RowIterator::increment()
 void RowIterator::decrement()
 {
 	if (0 == _position)
-		throw RangeException("End of iterator reached.");
-
-	--_position;
+		throw RangeException("Beginning of iterator reached.");
+	else if (POSITION_END == _position)
+		_position = _recordSet.rowCount() - 1;
+	else
+		--_position;
 }
 
 
-const Row& RowIterator::operator * () const
+Row& RowIterator::operator * () const
 {
+	if (POSITION_END == _position)
+		throw InvalidAccessException("End of iterator reached.");
+
 	return _recordSet.row(_position);
 }
 
 
-const Row& RowIterator::operator ++ ()
+Row* RowIterator::operator -> () const
+{
+	if (POSITION_END == _position)
+		throw InvalidAccessException("End of iterator reached.");
+
+	return &_recordSet.row(_position);
+}
+
+
+std::size_t RowIterator::operator ++ ()
 {
 	increment();
-	return _recordSet.row(_position);
+	return _position;
 }
 
 
-const Row& RowIterator::operator ++ (int)
+std::size_t RowIterator::operator ++ (int)
 {
+	std::size_t oldPos = _position;
 	increment();
-	return _recordSet.row(_position - 1);
+	return oldPos;
 }
 
 
-const Row& RowIterator::operator -- ()
+std::size_t RowIterator::operator -- ()
 {
 	decrement();
-	return _recordSet.row(_position);
+	return _position;
 }
 
 
-const Row& RowIterator::operator -- (int)
+std::size_t RowIterator::operator -- (int)
 {
+	std::size_t oldPos = _position;
 	decrement();
-	return _recordSet.row(_position + 1);
+	return oldPos;
 }
 
 

+ 183 - 29
Data/testsuite/src/DataTest.cpp

@@ -38,7 +38,6 @@
 #include "Poco/Data/BLOBStream.h"
 #include "Poco/Data/MetaColumn.h"
 #include "Poco/Data/Column.h"
-#include "Poco/Data/Row.h"
 #include "Connector.h"
 #include "Poco/BinaryReader.h"
 #include "Poco/BinaryWriter.h"
@@ -46,7 +45,7 @@
 #include "Poco/Exception.h"
 #include <cstring>
 #include <sstream>
-#include <map>
+#include <set>
 
 
 using namespace Poco::Data;
@@ -59,6 +58,7 @@ using Poco::Int64;
 using Poco::UInt64;
 using Poco::InvalidAccessException;
 using Poco::RangeException;
+using Poco::NotFoundException;
 
 
 DataTest::DataTest(const std::string& name): CppUnit::TestCase(name)
@@ -576,13 +576,36 @@ void DataTest::testColumnList()
 void DataTest::testRow()
 {
 	Row row;
-
 	row.append("field0", 0);
 	row.append("field1", 1);
 	row.append("field2", 2);
 	row.append("field3", 3);
 	row.append("field4", 4);
 
+	assert (row["field0"] == 0);
+	assert (row["field1"] == 1);
+	assert (row["field2"] == 2);
+	assert (row["field3"] == 3);
+	assert (row["field4"] == 4);
+
+	assert (row[0] == 0);
+	assert (row[1] == 1);
+	assert (row[2] == 2);
+	assert (row[3] == 3);
+	assert (row[4] == 4);
+
+	try
+	{
+		int i = row[5];
+		fail ("must fail");
+	}catch (RangeException&) {}
+
+	try
+	{
+		int i = row["a bad name"];
+		fail ("must fail");
+	}catch (NotFoundException&) {}
+
 	assert (5 == row.fieldCount());
 	assert (row[0] == 0);
 	assert (row["field0"] == 0);
@@ -595,12 +618,12 @@ void DataTest::testRow()
 	assert (row[4] == 4);
 	assert (row["field4"] == 4);
 
-	assert (row.toStringN() == std::string("field0\tfield1\tfield2\tfield3\tfield4") + Row::EOL);
+	assert (row.namesToString() == std::string("field0\tfield1\tfield2\tfield3\tfield4") + Row::EOL);
 	std::ostringstream os;
 	os << row;
 	assert (os.str() == std::string("0\t1\t2\t3\t4") + Row::EOL);
 	row.separator(",");
-	assert (row.toStringN() == std::string("field0,field1,field2,field3,field4") + Row::EOL);
+	assert (row.namesToString() == std::string("field0,field1,field2,field3,field4") + Row::EOL);
 	os.str("");
 	os << row;
 	assert (os.str() == std::string("0,1,2,3,4") + Row::EOL);
@@ -615,30 +638,6 @@ void DataTest::testRow()
 
 	assert (row != row2);
 
-	std::map<Row, int> rowMap;
-	rowMap.insert(std::map<Row, int>::value_type(row2, 0));
-	rowMap.insert(std::map<Row, int>::value_type(row, 1));
-	std::map<Row, int>::iterator it = rowMap.begin();
-	assert (row == it->first);
-	++it;
-	assert (row2 == it->first);
-
-	rowMap.clear();
-	row.sortField("field4");
-	rowMap.insert(std::map<Row, int>::value_type(row, 0));
-	try
-	{
-		rowMap.insert(std::map<Row, int>::value_type(row2, 1));
-		fail ("must fail");
-	}catch (InvalidAccessException&) {}
-
-	row2.sortField("field4");
-	rowMap.insert(std::map<Row, int>::value_type(row2, 1));
-	it = rowMap.begin();
-	assert (row2 == it->first);
-	++it;
-	assert (row == it->first);
-
 	Row row3;
 
 	row3.append("field0", 0);
@@ -648,6 +647,160 @@ void DataTest::testRow()
 	row3.append("field4", 4);
 
 	assert (row3 == row);
+	assert (!(row < row3 | row3 < row));
+
+	Row row4(row3.names());
+	try
+	{
+		row4.set("badfieldname", 0);
+		fail ("must fail");
+	}catch (NotFoundException&) {}
+
+	row4.set("field0", 0);
+	row4.set("field1", 1);
+	row4.set("field2", 2);
+	row4.set("field3", 3);
+	row4.set("field4", 4);
+	assert (row3 == row4);
+	try
+	{
+		row4.set(5, 0);
+		fail ("must fail");
+	}catch (RangeException&) {}
+	row4.set("field0", 1);
+	assert (row3 != row4);
+	assert (row3 < row4);
+}
+
+
+void DataTest::testRowSort()
+{
+	Row row1;
+	row1.append("0", 0);
+	row1.append("1", 1);
+	row1.append("2", 2);
+	row1.append("3", 3);
+	row1.append("4", 4);
+
+	Row row2;
+	row2.append("0", 0);
+	row2.append("1", 1);
+	row2.append("2", 2);
+	row2.append("3", 3);
+	row2.append("4", 4);
+
+	std::multiset<Row> rowSet1;
+	rowSet1.insert(row1);
+	rowSet1.insert(row2);
+	std::multiset<Row>::iterator it1 = rowSet1.begin();
+	assert (row1 == *it1);
+	++it1;
+	assert (row2 == *it1);
+
+	Row row3;
+	row3.append("0", 1);
+	row3.append("1", 1);
+	row3.append("2", 2);
+	row3.append("3", 3);
+	row3.append("4", 4);
+
+	Row row4;
+	row4.append("0", 0);
+	row4.append("1", 1);
+	row4.append("2", 2);
+	row4.append("3", 3);
+	row4.append("4", 4);
+
+	std::set<Row> rowSet2;
+	rowSet2.insert(row4);
+	rowSet2.insert(row3);
+	std::set<Row>::iterator it2 = rowSet2.begin();
+	assert (row4 == *it2);
+	++it2;
+	assert (row3 == *it2);
+
+	Row row5;
+	row5.append("0", 2);
+	row5.append("1", 2);
+	row5.append("2", 0);
+	row5.append("3", 3);
+	row5.append("4", 4);
+	row5.addSortField("1");
+
+	Row row6;
+	row6.append("0", 1);
+	row6.append("1", 0);
+	row6.append("2", 1);
+	row6.append("3", 3);
+	row6.append("4", 4);
+	row6.addSortField("1");
+
+	Row row7;
+	row7.append("0", 0);
+	row7.append("1", 1);
+	row7.append("2", 2);
+	row7.append("3", 3);
+	row7.append("4", 4);
+
+	std::set<Row> rowSet3;
+	rowSet3.insert(row5);
+	rowSet3.insert(row6);
+	try
+	{
+		rowSet3.insert(row7);//has no same sort criteria
+		fail ("must fail");
+	} catch (InvalidAccessException&) {}
+
+	row7.addSortField("1");
+	testRowStrictWeak(row7, row6, row5);
+	rowSet3.insert(row7);
+
+	std::set<Row>::iterator it3 = rowSet3.begin();
+	assert (row7 == *it3);
+	++it3;
+	assert (row6 == *it3);
+	++it3;
+	assert (row5 == *it3);
+
+	row5.replaceSortField("0", "2");
+	row6.replaceSortField("0", "2");
+	row7.replaceSortField("0", "2");
+
+	rowSet3.clear();
+	rowSet3.insert(row7);
+	rowSet3.insert(row6);
+	rowSet3.insert(row5);
+
+	it3 = rowSet3.begin();
+	assert (row5 == *it3);
+	++it3;
+	assert (row6 == *it3);
+	++it3;
+	assert (row7 == *it3);
+
+	row5.resetSort();
+	row6.resetSort();
+	row7.resetSort();
+
+	rowSet3.clear();
+	rowSet3.insert(row5);
+	rowSet3.insert(row6);
+	rowSet3.insert(row7);
+
+	it3 = rowSet3.begin();
+	assert (row7 == *it3);
+	++it3;
+	assert (row6 == *it3);
+	++it3;
+	assert (row5 == *it3);
+}
+
+
+void DataTest::testRowStrictWeak(const Row& row1, const Row& row2, const Row& row3)
+{
+	assert (row1 < row2 && !(row2 < row1)); // antisymmetric
+	assert (row1 < row2 && row2 < row3 && row1 < row3); // transitive
+	assert (!(row1 < row1)); // irreflexive
 }
 
 
@@ -674,6 +827,7 @@ CppUnit::Test* DataTest::suite()
 	CppUnit_addTest(pSuite, DataTest, testColumnDeque);
 	CppUnit_addTest(pSuite, DataTest, testColumnList);
 	CppUnit_addTest(pSuite, DataTest, testRow);
+	CppUnit_addTest(pSuite, DataTest, testRowSort);
 
 	return pSuite;
 }

+ 11 - 0
Data/testsuite/src/DataTest.h

@@ -39,6 +39,7 @@
 #include "Poco/Data/Data.h"
 #include "Poco/BinaryReader.h"
 #include "Poco/BinaryWriter.h"
+#include "Poco/Data/Row.h"
 #include "CppUnit/TestCase.h"
 
 
@@ -57,6 +58,7 @@ public:
 	void testColumnDeque();
 	void testColumnList();
 	void testRow();
+	void testRowSort();
 
 	void setUp();
 	void tearDown();
@@ -64,6 +66,15 @@ public:
 	static CppUnit::Test* suite();
 
 private:
+	void testRowStrictWeak(const Poco::Data::Row& row1, 
+		const Poco::Data::Row& row2, 
+		const Poco::Data::Row& row3);
+		/// Strict weak ordering requirement for sorted containers
+		/// as described in Josuttis "The Standard C++ Library"
+		/// chapter 6.5. pg. 176.
+		/// For this to pass, the following condition must be satisifed: 
+		/// row1 < row2 < row3
+
 	void writeToBLOB(Poco::BinaryWriter& writer);
 	void readFromBLOB(Poco::BinaryReader& reader);
 };