From b2977d3df2115bef72076b5fd9ad8b6cbbe06b6a Mon Sep 17 00:00:00 2001 From: Aleksandar Fabijanic Date: Sat, 23 Jun 2007 01:22:55 +0000 Subject: [PATCH] Row and RowIterator done and tested (windows and linux) --- Data/Data_VS71.vcproj | 12 + Data/Makefile | 2 +- Data/ODBC/testsuite/src/ODBCDB2Test.cpp | 16 ++ Data/ODBC/testsuite/src/ODBCDB2Test.h | 1 + Data/ODBC/testsuite/src/ODBCMySQLTest.cpp | 17 +- Data/ODBC/testsuite/src/ODBCMySQLTest.h | 1 + Data/ODBC/testsuite/src/ODBCOracleTest.cpp | 16 ++ Data/ODBC/testsuite/src/ODBCOracleTest.h | 1 + .../ODBC/testsuite/src/ODBCPostgreSQLTest.cpp | 16 ++ Data/ODBC/testsuite/src/ODBCPostgreSQLTest.h | 1 + Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp | 16 ++ Data/ODBC/testsuite/src/ODBCSQLServerTest.h | 1 + Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp | 16 ++ Data/ODBC/testsuite/src/ODBCSQLiteTest.h | 1 + Data/ODBC/testsuite/src/SQLExecutor.cpp | 34 +++ Data/ODBC/testsuite/src/SQLExecutor.h | 1 + Data/SQLite/testsuite/src/SQLiteTest.cpp | 33 +++ Data/SQLite/testsuite/src/SQLiteTest.h | 1 + Data/include/Poco/Data/RecordSet.h | 9 +- Data/include/Poco/Data/Row.h | 145 +++++++++-- Data/include/Poco/Data/RowIterator.h | 42 ++-- Data/src/RecordSet.cpp | 21 +- Data/src/Row.cpp | 226 +++++++++++++----- Data/src/RowIterator.cpp | 48 ++-- Data/testsuite/src/DataTest.cpp | 212 +++++++++++++--- Data/testsuite/src/DataTest.h | 11 + 26 files changed, 745 insertions(+), 155 deletions(-) diff --git a/Data/Data_VS71.vcproj b/Data/Data_VS71.vcproj index d57f868ec..39a1ac68e 100644 --- a/Data/Data_VS71.vcproj +++ b/Data/Data_VS71.vcproj @@ -211,6 +211,12 @@ + + + + @@ -278,6 +284,12 @@ + + + + diff --git a/Data/Makefile b/Data/Makefile index 192ec134b..3c54670f0 100644 --- a/Data/Makefile +++ b/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 diff --git a/Data/ODBC/testsuite/src/ODBCDB2Test.cpp b/Data/ODBC/testsuite/src/ODBCDB2Test.cpp index d070dbc02..6e6ad07a5 100644 --- a/Data/ODBC/testsuite/src/ODBCDB2Test.cpp +++ b/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; } diff --git a/Data/ODBC/testsuite/src/ODBCDB2Test.h b/Data/ODBC/testsuite/src/ODBCDB2Test.h index d7ce913cb..9579152aa 100644 --- a/Data/ODBC/testsuite/src/ODBCDB2Test.h +++ b/Data/ODBC/testsuite/src/ODBCDB2Test.h @@ -123,6 +123,7 @@ public: void testStoredFunction(); void testNull(); + void testRowIterator(); void setUp(); void tearDown(); diff --git a/Data/ODBC/testsuite/src/ODBCMySQLTest.cpp b/Data/ODBC/testsuite/src/ODBCMySQLTest.cpp index cc874b038..b499e6719 100644 --- a/Data/ODBC/testsuite/src/ODBCMySQLTest.cpp +++ b/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; } diff --git a/Data/ODBC/testsuite/src/ODBCMySQLTest.h b/Data/ODBC/testsuite/src/ODBCMySQLTest.h index dec6dde6f..1dacdb9a0 100644 --- a/Data/ODBC/testsuite/src/ODBCMySQLTest.h +++ b/Data/ODBC/testsuite/src/ODBCMySQLTest.h @@ -125,6 +125,7 @@ public: void testStoredFunction(); void testNull(); + void testRowIterator(); void setUp(); void tearDown(); diff --git a/Data/ODBC/testsuite/src/ODBCOracleTest.cpp b/Data/ODBC/testsuite/src/ODBCOracleTest.cpp index d15a31210..bd3d98267 100644 --- a/Data/ODBC/testsuite/src/ODBCOracleTest.cpp +++ b/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; } diff --git a/Data/ODBC/testsuite/src/ODBCOracleTest.h b/Data/ODBC/testsuite/src/ODBCOracleTest.h index aac7ac712..69a8ac50e 100644 --- a/Data/ODBC/testsuite/src/ODBCOracleTest.h +++ b/Data/ODBC/testsuite/src/ODBCOracleTest.h @@ -123,6 +123,7 @@ public: void testStoredFunction(); void testNull(); + void testRowIterator(); void setUp(); void tearDown(); diff --git a/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.cpp b/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.cpp index 65526bcce..03d74cf2c 100644 --- a/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.cpp +++ b/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; } diff --git a/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.h b/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.h index 53a9466d8..4f0d59062 100644 --- a/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.h +++ b/Data/ODBC/testsuite/src/ODBCPostgreSQLTest.h @@ -124,6 +124,7 @@ public: void testStoredFunction(); void testNull(); + void testRowIterator(); void setUp(); void tearDown(); diff --git a/Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp b/Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp index f4b85d1ad..20722fa6b 100644 --- a/Data/ODBC/testsuite/src/ODBCSQLServerTest.cpp +++ b/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; } diff --git a/Data/ODBC/testsuite/src/ODBCSQLServerTest.h b/Data/ODBC/testsuite/src/ODBCSQLServerTest.h index f82778c58..c05c2cce1 100644 --- a/Data/ODBC/testsuite/src/ODBCSQLServerTest.h +++ b/Data/ODBC/testsuite/src/ODBCSQLServerTest.h @@ -126,6 +126,7 @@ public: void testInternalStorageType(); void testNull(); + void testRowIterator(); void setUp(); void tearDown(); diff --git a/Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp b/Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp index 6fa161cc7..c66bf9aa5 100644 --- a/Data/ODBC/testsuite/src/ODBCSQLiteTest.cpp +++ b/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; } diff --git a/Data/ODBC/testsuite/src/ODBCSQLiteTest.h b/Data/ODBC/testsuite/src/ODBCSQLiteTest.h index b3ed875dd..14756718b 100644 --- a/Data/ODBC/testsuite/src/ODBCSQLiteTest.h +++ b/Data/ODBC/testsuite/src/ODBCSQLiteTest.h @@ -120,6 +120,7 @@ public: void testInternalStorageType(); void testNull(); + void testRowIterator(); void setUp(); void tearDown(); diff --git a/Data/ODBC/testsuite/src/SQLExecutor.cpp b/Data/ODBC/testsuite/src/SQLExecutor.cpp index 0118dc475..ddba66d23 100644 --- a/Data/ODBC/testsuite/src/SQLExecutor.cpp +++ b/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 #include +#include +#include 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 > v; + v.push_back(Tuple(1, 1.5f, "3")); + v.push_back(Tuple(2, 2.5f, "4")); + v.push_back(Tuple(3, 3.5f, "5")); + v.push_back(Tuple(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(osCopy)); + assert (osLoop.str() == osCopy.str()); +} diff --git a/Data/ODBC/testsuite/src/SQLExecutor.h b/Data/ODBC/testsuite/src/SQLExecutor.h index ff9df3635..55e70479a 100644 --- a/Data/ODBC/testsuite/src/SQLExecutor.h +++ b/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; diff --git a/Data/SQLite/testsuite/src/SQLiteTest.cpp b/Data/SQLite/testsuite/src/SQLiteTest.cpp index 6b16d9b7d..6fad5382c 100644 --- a/Data/SQLite/testsuite/src/SQLiteTest.cpp +++ b/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 > v; + v.push_back(Tuple(1, 1.5f, "3")); + v.push_back(Tuple(2, 2.5f, "4")); + v.push_back(Tuple(3, 3.5f, "5")); + v.push_back(Tuple(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(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; } diff --git a/Data/SQLite/testsuite/src/SQLiteTest.h b/Data/SQLite/testsuite/src/SQLiteTest.h index 4b61e5f45..c24189b03 100644 --- a/Data/SQLite/testsuite/src/SQLiteTest.h +++ b/Data/SQLite/testsuite/src/SQLiteTest.h @@ -111,6 +111,7 @@ public: void testInternalExtraction(); void testPrimaryKeyConstraint(); void testNull(); + void testRowIterator(); void setUp(); void tearDown(); diff --git a/Data/include/Poco/Data/RecordSet.h b/Data/include/Poco/Data/RecordSet.h index 6e6dc63c8..4bcaf7cf8 100644 --- a/Data/include/Poco/Data/RecordSet.h +++ b/Data/include/Poco/Data/RecordSet.h @@ -78,6 +78,7 @@ class Data_API RecordSet: private Statement { public: typedef std::map 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 @@ -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; } diff --git a/Data/include/Poco/Data/Row.h b/Data/include/Poco/Data/Row.h index 57a3f0ed9..ac8b93da2 100644 --- a/Data/include/Poco/Data/Row.h +++ b/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 #include -#include +#include 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 NameVec; + typedef SharedPtr > 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 + 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 + 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 sortField(const std::string& name); - /// Sets the field used for sorting. + void addSortField(const std::string& name); + /// Adds the field used for sorting. - const std::string& toStringN() const; + 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 replaceSortField(std::size_t oldPos, std::size_t newPos); + /// Replaces the field used for sorting. + + 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 ValueVec; + typedef Tuple SortTuple; + typedef std::vector 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 _names; - std::vector _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 diff --git a/Data/include/Poco/Data/RowIterator.h b/Data/include/Poco/Data/RowIterator.h index c405f64c9..0d628bd6b 100644 --- a/Data/include/Poco/Data/RowIterator.h +++ b/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 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. - const Row& operator ++ (); - /// Advances by one position and returns const reference - /// to the current row. + Row* operator -> () const; + /// Returns pointer to the current row. - const Row& operator ++ (int); - /// Advances by one position and returns const reference - /// to the previous current row. + std::size_t operator ++ (); + /// Advances by one position and returns current position. - const Row& operator -- (); - /// Goes back by one position and returns const reference - /// to the current row. + std::size_t operator ++ (int); + /// Advances by one position and returns previous current position. - const Row& operator -- (int); - /// Goes back by one position and returns const reference - /// to the previous current row. + std::size_t operator -- (); + /// Goes back by one position and returns current position. + + 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; }; diff --git a/Data/src/RecordSet.cpp b/Data/src/RecordSet.cpp index 6d42d2023..7a85abd27 100644 --- a/Data/src/RecordSet.cpp +++ b/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(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(pos)).name(), value(i, pos)); + } _rowMap.insert(RowMap::value_type(pos, pRow)); } + else + pRow = it->second; poco_check_ptr (pRow); return *pRow; diff --git a/Data/src/Row.cpp b/Data/src/Row.cpp index 3773ac702..8c3f83589 100644 --- a/Data/src/Row.cpp +++ b/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::const_iterator it = _names.begin(); - std::vector::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 { - comparison(COMPARE_AS_STRING); + ct = COMPARE_AS_STRING; } + _sortFields.push_back(SortTuple(pos, ct)); } -void Row::sortField(const std::string& name) +void Row::addSortField(const std::string& name) { - sortField(getPosition(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 + { + 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::resetSort() +{ + _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() < - other._values[other._sortField].convert()); + switch (it->get<1>()) + { + case COMPARE_AS_INTEGER: + if (_values[it->get<0>()].convert() < + other._values[it->get<0>()].convert()) + return true; + else if (_values[it->get<0>()].convert() != + other._values[it->get<0>()].convert()) + return false; + break; - case COMPARE_AS_FLOAT: - return (_values[_sortField].convert() < - other._values[other._sortField].convert()); + case COMPARE_AS_FLOAT: + if (_values[it->get<0>()].convert() < + other._values[it->get<0>()].convert()) + return true; + else if (_values[it->get<0>()].convert() < + other._values[it->get<0>()].convert()) + return false; + break; - case COMPARE_AS_STRING: - return (_values[_sortField].convert() < - other._values[other._sortField].convert()); + case COMPARE_AS_STRING: + if (_values[it->get<0>()].convert() < + other._values[it->get<0>()].convert()) + return true; + else if (_values[it->get<0>()].convert() < + other._values[it->get<0>()].convert()) + 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::const_iterator it = _values.begin(); - std::vector::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()); - _strValues.append(_separator); + strValues.append(it->convert()); + 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::const_iterator it = _names.begin(); - std::vector::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; } diff --git a/Data/src/RowIterator.cpp b/Data/src/RowIterator.cpp index e5881b546..00331aedd 100644 --- a/Data/src/RowIterator.cpp +++ b/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::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; } diff --git a/Data/testsuite/src/DataTest.cpp b/Data/testsuite/src/DataTest.cpp index 29fbbe1d3..7375446cd 100644 --- a/Data/testsuite/src/DataTest.cpp +++ b/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 #include -#include +#include 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 rowMap; - rowMap.insert(std::map::value_type(row2, 0)); - rowMap.insert(std::map::value_type(row, 1)); - std::map::iterator it = rowMap.begin(); - assert (row == it->first); - ++it; - assert (row2 == it->first); - - rowMap.clear(); - row.sortField("field4"); - rowMap.insert(std::map::value_type(row, 0)); - try - { - rowMap.insert(std::map::value_type(row2, 1)); - fail ("must fail"); - }catch (InvalidAccessException&) {} - - row2.sortField("field4"); - rowMap.insert(std::map::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 rowSet1; + rowSet1.insert(row1); + rowSet1.insert(row2); + std::multiset::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 rowSet2; + rowSet2.insert(row4); + rowSet2.insert(row3); + std::set::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 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::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; } diff --git a/Data/testsuite/src/DataTest.h b/Data/testsuite/src/DataTest.h index 697e9c9da..5b014211d 100644 --- a/Data/testsuite/src/DataTest.h +++ b/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); };