diff --git a/src/Wt/Dbo/Session b/src/Wt/Dbo/Session index ed387c5..88792fc 100644 --- a/src/Wt/Dbo/Session +++ b/src/Wt/Dbo/Session @@ -368,6 +368,13 @@ public: */ void rereadAll(const char *tableName = 0); + /*! \brief Disconnects all objects from session management. + * + * This disconnects all objects from the session management. + * Disconnected objects are not synchronized with the database. + */ + virtual void disconnectAll(); + /*! \brief Discards all unflushed changes. * * This method is useful when the flushMode() is set to Manual. It discards @@ -472,6 +479,7 @@ private: virtual void dropTable(Session& session, std::set& tablesDropped); virtual void rereadAll(); + virtual void disconnectAll(); std::string primaryKeys() const; }; @@ -487,6 +495,7 @@ private: virtual void dropTable(Session& session, std::set& tablesDropped); virtual void rereadAll(); + virtual void disconnectAll(); }; typedef const std::type_info * const_typeinfo_ptr; diff --git a/src/Wt/Dbo/Session.C b/src/Wt/Dbo/Session.C index 83ccd98..fddba49 100644 --- a/src/Wt/Dbo/Session.C +++ b/src/Wt/Dbo/Session.C @@ -85,6 +85,11 @@ void Session::MappingInfo::rereadAll() throw Exception("Not to be done."); } +void Session::MappingInfo::disconnectAll() +{ + throw Exception("Not to be done."); +} + std::string Session::MappingInfo::primaryKeys() const { if (surrogateIdFieldName) @@ -1096,6 +1101,23 @@ void Session::rereadAll(const char *tableName) i->second->rereadAll(); } +void Session::disconnectAll() +{ + if (!dirtyObjects_.empty()) + std::cerr << "Warning: Wt::Dbo::Session disconnecting with " + << dirtyObjects_.size() << " dirty objects" << std::endl; + + for (MetaDboBaseSet::iterator i = dirtyObjects_.begin(); + i != dirtyObjects_.end(); ++i) + (*i)->decRef(); + + dirtyObjects_.clear(); + + for (ClassRegistry::iterator i = classRegistry_.begin(); + i != classRegistry_.end(); ++i) + i->second->disconnectAll(); +} + void Session::discardUnflushed() { objectsToAdd_.clear(); diff --git a/src/Wt/Dbo/Session_impl.h b/src/Wt/Dbo/Session_impl.h index b76350a..b94ee9b 100644 --- a/src/Wt/Dbo/Session_impl.h +++ b/src/Wt/Dbo/Session_impl.h @@ -384,6 +384,17 @@ void Session::Mapping::rereadAll() } template +void Session::Mapping::disconnectAll() +{ + for (typename Registry::iterator i = registry_.begin(); + i != registry_.end(); ++i) { + i->second->setSession(0); + i->second->setState(MetaDboBase::Disconnected); + } + registry_.clear(); +} + +template void Session::Mapping::init(Session& session) { if (!initialized_) { diff --git a/src/Wt/Dbo/ptr b/src/Wt/Dbo/ptr index 4128bad..ac4526d 100644 --- a/src/Wt/Dbo/ptr +++ b/src/Wt/Dbo/ptr @@ -68,6 +68,7 @@ public: New = 0x000, Persisted = 0x001, Orphaned = 0x002, + Disconnected = 0x004, // flags NeedsDelete = 0x010, @@ -106,6 +107,7 @@ public: bool isPersisted() const { return 0 != (state_ & (Persisted | SavedInTransaction)); } bool isOrphaned() const { return 0 != (state_ & Orphaned); } + bool isDisconnected() const { return 0 != (state_ & Disconnected); } bool isDeleted() const { return 0 != (state_ & (NeedsDelete | DeletedInTransaction)); } @@ -137,6 +139,7 @@ protected: int refCount_; void checkNotOrphaned(); + void checkNotDisconnected(); }; /*! \class dbo_default_traits Wt/Dbo/Dbo Wt/Dbo/Dbo diff --git a/src/Wt/Dbo/ptr.C b/src/Wt/Dbo/ptr.C index fb137c0..4f199a1 100644 --- a/src/Wt/Dbo/ptr.C +++ b/src/Wt/Dbo/ptr.C @@ -85,6 +85,13 @@ void MetaDboBase::checkNotOrphaned() } } +void MetaDboBase::checkNotDisconnected() +{ + if (isDisconnected()) { + throw Exception("modifying disconnected dbo ptr"); + } +} + ptr_base::~ptr_base() { } diff --git a/src/Wt/Dbo/ptr_impl.h b/src/Wt/Dbo/ptr_impl.h index e5d1cc6..649a3de 100644 --- a/src/Wt/Dbo/ptr_impl.h +++ b/src/Wt/Dbo/ptr_impl.h @@ -51,7 +51,7 @@ MetaDbo::~MetaDbo() if (refCount_) throw std::logic_error("Dbo: refCount > 0"); - if ((!isOrphaned()) && session()) + if ((!isOrphaned()) && (!isDisconnected()) && session()) session()->prune(this); delete obj_; @@ -61,6 +61,7 @@ template void MetaDbo::flush() { checkNotOrphaned(); + checkNotDisconnected(); if (state_ & NeedsDelete) { state_ &= ~NeedsDelete; diff --git a/test/dbo/DboDisconnectTest.C b/test/dbo/DboDisconnectTest.C new file mode 100644 index 0000000..708895d --- /dev/null +++ b/test/dbo/DboDisconnectTest.C @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2009 Emweb bvba, Kessel-Lo, Belgium. + * + * See the LICENSE file for terms of use. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +namespace dbo = Wt::Dbo; + +class TestDbo; +typedef dbo::collection< dbo::ptr > TestDboCollection; +class TestDbo : public Wt::Dbo::Dbo +{ +public: + int intC; + double doubleC; + + template + void persist(Action &a) + { + Wt::Dbo::field(a, intC, "intC"); + Wt::Dbo::field(a, doubleC, "doubleC"); + } + static const char *TableName() + { + return "func"; + } +}; + + +struct Dbo3Fixture +{ + typedef std::map> TestMapType; + TestMapType TestMap; + dbo::SqlConnection *connection_; + dbo::Session *session_; + + Dbo3Fixture() + { + connection_ = new dbo::backend::Sqlite3(":memory:"); + connection_->setProperty("show-queries", "true"); + + session_ = new dbo::Session(); + session_->setConnection(*connection_); + session_->mapClass(TestDbo::TableName()); + session_->createTables(); + + { + dbo::Transaction tr(*session_); + dbo::ptr New1 = session_->add(new TestDbo()); + New1.modify()->intC = 1; + New1.modify()->doubleC = 1.1; + dbo::ptr New2 = session_->add(new TestDbo()); + New2.modify()->intC = 2; + New2.modify()->doubleC = 2.2; + } + + { + //Check initial values + FetchAll(); + dbo::ptr DisconnectedTestPtr = TestMap[1]; //id: 1 + std::cout << "Before modified: intC: " << DisconnectedTestPtr->intC << ", expected: 1" << std::endl; + + //Modify from outside session_'s knowledge + dbo::Transaction tr(*session_); + session_->execute(std::string("UPDATE ") + TestDbo::TableName() + " SET intC = 99 WHERE id = 1;"); + tr.commit(); + + //Check again + FetchAll(); + //should remain unchanged because in FetchAll() none of the ptrs are deleted or modified but swapped with a local TestMap object + std::cout << "After re-fetched: intC: " << DisconnectedTestPtr->intC << ", expected: 1" << std::endl; + //should be a new value because FetchAll uses a collection to map all ptr with newer values from the database + dbo::ptr NormalTestPtr = TestMap[1]; //id: 1 + std::cout << "After re-assignment: intC: " << NormalTestPtr->intC << ", expected: 99" << std::endl; + BOOST_REQUIRE(DisconnectedTestPtr->intC == 1); + BOOST_REQUIRE(NormalTestPtr->intC == 99); + + dbo::Transaction tr2(*session_); + NormalTestPtr.modify()->intC = 10; + tr2.commit(); + + try + { + dbo::Transaction tr3(*session_); + DisconnectedTestPtr.modify()->intC = 5; + tr3.commit(); + } + catch(std::exception &e) + { + std::cout << "Exception caught: " << e.what(); + } + } + } + + void FetchAll() + { + //Strong exception safety(make TestMap empty by swapping with an empty map, swap back if exception is caught) + TestMapType testmap; + TestMap.swap(testmap); + + session_->disconnectAll(); + + try + { + dbo::Transaction tr(*session_); + TestDboCollection collection = session_->find(); + for(TestDboCollection::const_iterator itr = collection.begin(); + itr != collection.end(); + ++itr) + { + TestMap[itr->id()] = *itr; + } + tr.commit(); + } + catch(...) + { + testmap.swap(TestMap); + } + } + + ~Dbo3Fixture() + { + session_->dropTables(); + + delete session_; + delete connection_; + } +}; + +BOOST_AUTO_TEST_CASE( dbo3_reload_test ) +{ + Dbo3Fixture f; +} \ No newline at end of file