diff options
Diffstat (limited to 'odb-tests/common/session/custom')
-rw-r--r-- | odb-tests/common/session/custom/buildfile | 43 | ||||
-rw-r--r-- | odb-tests/common/session/custom/driver.cxx | 231 | ||||
-rw-r--r-- | odb-tests/common/session/custom/session.cxx | 57 | ||||
-rw-r--r-- | odb-tests/common/session/custom/session.hxx | 191 | ||||
-rw-r--r-- | odb-tests/common/session/custom/session.txx | 159 | ||||
-rw-r--r-- | odb-tests/common/session/custom/test.hxx | 118 | ||||
-rw-r--r-- | odb-tests/common/session/custom/testscript | 33 |
7 files changed, 832 insertions, 0 deletions
diff --git a/odb-tests/common/session/custom/buildfile b/odb-tests/common/session/custom/buildfile new file mode 100644 index 0000000..1b64de1 --- /dev/null +++ b/odb-tests/common/session/custom/buildfile @@ -0,0 +1,43 @@ +# file : common/session/custom/buildfile +# license : GNU GPL v2; see accompanying LICENSE file + +import libodb = libodb%lib{odb} + +libs = + +for db: $databases + import libs += libodb-$db%lib{odb-$db} + +import libs += lib{common} + +exe{driver}: {hxx txx cxx}{* -*-odb -*-odb-*} {hxx ixx cxx}{test-odb} testscript + +# Introduce the metadata library target to make sure the libodb library is +# resolved for the odb_compile ad hoc rule (see build/root.build for details). +# +libue{test-meta}: $libodb + +<{hxx ixx cxx}{test-odb}>: hxx{test} libue{test-meta} + +for db: $databases +{ + exe{driver}: {hxx ixx cxx}{test-odb-$db}: include = $multi + <{hxx ixx cxx}{test-odb-$db}>: hxx{test} libue{test-meta} +} + +exe{driver}: libue{test-meta} $libs + +# Specify the ODB custom options to be used by the odb_compile ad hoc rule +# (see build/root.build for details). +# +odb_options = --table-prefix session_custom_ \ + --generate-schema \ + --generate-session \ + --session-type ::session \ + --hxx-prologue '#include "session.hxx"' + +cxx.poptions =+ "-I$out_base" "-I$src_base" + +# Testscript's run-time prerequisites. +# +exe{driver}: ../../../alias{database-client}: include = adhoc diff --git a/odb-tests/common/session/custom/driver.cxx b/odb-tests/common/session/custom/driver.cxx new file mode 100644 index 0000000..3056fd6 --- /dev/null +++ b/odb-tests/common/session/custom/driver.cxx @@ -0,0 +1,231 @@ +// file : common/session/custom/driver.cxx +// license : GNU GPL v2; see accompanying LICENSE file + +// Test custom session (C++11 only). +// + +#include <memory> +#include <cstddef> // std::size_t +#include <iostream> + +#include <odb/tracer.hxx> +#include <odb/database.hxx> +#include <odb/session.hxx> +#include <odb/transaction.hxx> +#include <odb/details/config.hxx> // ODB_CXX11_* + +#include <libcommon/common.hxx> + +#include "session.hxx" + +#include "test.hxx" +#include "test-odb.hxx" + +#undef NDEBUG +#include <cassert> + +using namespace std; + +using odb::database; +using odb::transaction; + +struct counting_tracer: odb::tracer +{ + virtual void + execute (odb::connection&, const char*) {count++;} + size_t count; +}; + +static counting_tracer tracer; + +struct failed {}; + +int +main (int argc, char* argv[]) +{ + try + { + unique_ptr<database> db (create_database (argc, argv)); + + // Simple Tech Ltd. + // + { + shared_ptr<employer> er (new employer ("Simple Tech Ltd", "ST")); + + shared_ptr<employee> john (new employee ("John", "Doe", er)); + shared_ptr<employee> jane (new employee ("Jane", "Doe", er)); + + transaction t (db->begin ()); + + db->persist (er); + db->persist (john); + db->persist (jane); + + t.commit (); + } + + // Complex Systems Inc. + // + { + shared_ptr<employer> er (new employer ("Complex Systems Inc", "CS")); + + shared_ptr<employee> john (new employee ("John", "Smith", er)); + shared_ptr<employee> jane (new employee ("Jane", "Smith", er)); + + transaction t (db->begin ()); + + db->persist (er); + db->persist (john); + db->persist (jane); + + t.commit (); + } + + { + session s; + shared_ptr<employer> st, cs; + shared_ptr<employee> ste, cse; + + { + transaction t (db->begin ()); + + st = db->load<employer> ("Simple Tech Ltd"); +#ifdef ODB_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGUMENT + ste = db->load<employee> (st->employees ()[0].object_id ()); +#else + ste = db->load<employee> (st->employees ()[0].object_id<employee> ()); +#endif + + // Test object cache. + // + shared_ptr<employee> e (st->employees ()[0].load ()); + assert (ste->employer () == st); + assert (ste == e); + + t.commit (); + } + + { + transaction t (db->begin ()); + + cs = db->load<employer> ("Complex Systems Inc"); +#ifdef ODB_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGUMENT + cse = db->load<employee> (cs->employees ()[0].object_id ()); +#else + cse = db->load<employee> (cs->employees ()[0].object_id<employee> ()); +#endif + cs->employees ()[0].load (); + + t.commit (); + } + + cs->symbol ("CSI"); + + // Swap employees. + // + ste->employer (cs); + cse->employer (st); + st->employees ()[0] = cse; + cs->employees ()[0] = ste; + + { + transaction t (db->begin ()); + tracer.count = 0; + t.tracer (tracer); + s.flush (*db); + assert (tracer.count == 3); + t.commit (); + } + + { + transaction t (db->begin ()); + tracer.count = 0; + t.tracer (tracer); + s.flush (*db); + assert (tracer.count == 0); + t.commit (); + } + + cs->symbol ("COMP"); + st->symbol ("SMPL"); + + { + transaction t (db->begin ()); + tracer.count = 0; + t.tracer (tracer); + s.flush (*db); + assert (tracer.count == 2); + t.commit (); + } + + { + transaction t (db->begin ()); + tracer.count = 0; + t.tracer (tracer); + s.flush (*db); + assert (tracer.count == 0); + t.commit (); + } + + // Explicit update. + // + cs->symbol ("CS"); + st->symbol ("ST"); + + { + transaction t (db->begin ()); + db->update (cs); + tracer.count = 0; + t.tracer (tracer); + s.flush (*db); + assert (tracer.count == 1); + t.commit (); + } + + // Rollback after update. + // + cs->symbol ("CSI"); + + try + { + transaction t (db->begin ()); + tracer.count = 0; + t.tracer (tracer); + s.flush (*db); + assert (tracer.count == 1); + throw failed (); + t.commit (); + } + catch (const failed&) + { + transaction t (db->begin ()); + tracer.count = 0; + t.tracer (tracer); + s.flush (*db); + assert (tracer.count == 1); + t.commit (); + } + } + + // Test session destruction before transaction is commited. + // + { + transaction t (db->begin ()); + { + session s; + shared_ptr<employer> st (db->load<employer> ("Simple Tech Ltd")); + st->symbol ("STL"); + tracer.count = 0; + t.tracer (tracer); + s.flush (*db); + assert (tracer.count == 1); + } + t.commit (); + } + } + catch (const odb::exception& e) + { + cerr << e.what () << endl; + return 1; + } +} diff --git a/odb-tests/common/session/custom/session.cxx b/odb-tests/common/session/custom/session.cxx new file mode 100644 index 0000000..1a08c79 --- /dev/null +++ b/odb-tests/common/session/custom/session.cxx @@ -0,0 +1,57 @@ +// file : common/session/custom/session.cxx +// license : GNU GPL v2; see accompanying LICENSE file + +#include <cassert> + +#include "session.hxx" + +session* session::current; + +session:: +session () + : tran_ (0) +{ + assert (current == 0); + current = this; +} + +session:: +~session () +{ + // Unregister from transaction. + // + if (tran_ != 0) + tran_->callback_unregister (this); + + assert (current == this); + current = 0; +} + +void session:: +flush (odb::database& db) +{ + bool flushed (false); + + for (type_map::iterator i (map_.begin ()), e (map_.end ()); i != e; ++i) + { + bool r (i->second->flush (db)); + flushed = flushed || r; + } + + // If we flushed anything, then register the post-commit/rollback callback. + // + if (flushed) + { + tran_ = &odb::transaction::current (); + tran_->callback_register ( + &mark, this, odb::transaction::event_all, 0, &tran_); + } +} + +void session:: +mark (unsigned short event, void* key, unsigned long long) +{ + session& s (*static_cast<session*> (key)); + for (type_map::iterator i (s.map_.begin ()), e (s.map_.end ()); i != e; ++i) + i->second->mark (event); +} diff --git a/odb-tests/common/session/custom/session.hxx b/odb-tests/common/session/custom/session.hxx new file mode 100644 index 0000000..2d2f597 --- /dev/null +++ b/odb-tests/common/session/custom/session.hxx @@ -0,0 +1,191 @@ +// file : common/session/custom/session.hxx +// license : GNU GPL v2; see accompanying LICENSE file + +#ifndef SESSION_HXX +#define SESSION_HXX + +#include <map> +#include <memory> +#include <typeinfo> + +#include <odb/database.hxx> +#include <odb/transaction.hxx> + +#include <odb/traits.hxx> // odb::object_traits +#include <odb/details/type-info.hxx> // odb::details::type_info_comparator + +// This custom session implementation assumes we are working with +// one database at a time. +// +class session +{ +public: + session (); + ~session (); + +private: + session (const session&); + session& operator= (const session&); + + // Session for the current thread. This can be implemented in pretty + // much any way that makes sense to the application. It can be a global + // session as we have here. In multi-threaded applications we could use + // TLS instead. + // +public: + static session* current; + + // Change tracking interface. + // +public: + // Call flush() within a transaction to apply the changes to the + // database. + // + void + flush (odb::database&); + +private: + struct object_map_base + { + virtual + ~object_map_base () {} + + // Return true if we flushed anything. + // + virtual bool + flush (odb::database&) = 0; + + virtual void + mark (unsigned short event) = 0; + }; + + enum object_state + { + tracking, // Tracking any modifications by storing the original copy. + changed, // Known to be changed. + flushed // Flushed but not yet committed/rolled back. + }; + + template <typename T> + struct object_data + { + typedef typename odb::object_traits<T>::pointer_type pointer_type; + + explicit + object_data (pointer_type o): obj (o), state (tracking) {} + + pointer_type obj; + pointer_type orig; + object_state state; + }; + + template <typename T> + struct object_map: object_map_base, + std::map<typename odb::object_traits<T>::id_type, + object_data<T> > + { + virtual bool + flush (odb::database&); + + virtual void + mark (unsigned short event); + }; + + // Object cache interface. + // +public: + static bool + _has_cache () {return current != 0;} + + template <typename T> + struct cache_position + { + typedef object_map<T> map; + typedef typename map::iterator iterator; + + cache_position (): map_ (0) {} + cache_position (map& m, const iterator& p): map_ (&m), pos_ (p) {} + + cache_position (const cache_position& p) + : map_ (p.map_) + { + // It might not be ok to use an uninitialized iterator. + // + if (p.map_ != 0) + pos_ = p.pos_; + } + + cache_position& + operator= (const cache_position& p) + { + // It might not be ok to use an uninitialized iterator on the rhs. + // + if (p.map_ != 0) + pos_ = p.pos_; + map_ = p.map_; + return *this; + } + + map* map_; + iterator pos_; + }; + + // Cache management. + // + template <typename T> + static cache_position<T> + _cache_insert (odb::database&, + const typename odb::object_traits<T>::id_type&, + const typename odb::object_traits<T>::pointer_type&); + + template <typename T> + static typename odb::object_traits<T>::pointer_type + _cache_find (odb::database&, const typename odb::object_traits<T>::id_type&); + + template <typename T> + static void + _cache_erase (const cache_position<T>& p) + { + if (p.map_ != 0) + p.map_->erase (p.pos_); + } + + // Notifications. + // + template <typename T> + static void + _cache_persist (const cache_position<T>& p) + { + _cache_load (p); + } + + template <typename T> + static void + _cache_load (const cache_position<T>&); + + template <typename T> + static void + _cache_update (odb::database&, const T&); + + template <typename T> + static void + _cache_erase (odb::database&, + const typename odb::object_traits<T>::id_type&); + +private: + // Post-commit/rollback callback. + // + static void + mark (unsigned short event, void* key, unsigned long long); + +private: + typedef std::map<const std::type_info*, + std::shared_ptr<object_map_base>, + odb::details::type_info_comparator> type_map; + type_map map_; + odb::transaction* tran_; +}; + +#include "session.txx" + +#endif // SESSION_HXX diff --git a/odb-tests/common/session/custom/session.txx b/odb-tests/common/session/custom/session.txx new file mode 100644 index 0000000..65ab933 --- /dev/null +++ b/odb-tests/common/session/custom/session.txx @@ -0,0 +1,159 @@ +// file : common/session/custom/session.txx +// license : GNU GPL v2; see accompanying LICENSE file + +#include <cassert> + +template <typename T> +typename session::cache_position<T> session:: +_cache_insert (odb::database&, + const typename odb::object_traits<T>::id_type& id, + const typename odb::object_traits<T>::pointer_type& obj) +{ + if (current == 0) + return cache_position<T> (); // No session, return empty position. + + std::shared_ptr<object_map_base>& pm (current->map_[&typeid (T)]); + + if (!pm) + pm.reset (new object_map<T>); + + object_map<T>& m (static_cast<object_map<T>&> (*pm)); + + typename object_map<T>::value_type vt (id, object_data<T> (obj)); + std::pair<typename object_map<T>::iterator, bool> r (m.insert (vt)); + + // We shall never try to re-insert the same object into the cache. + // + assert (r.second); + + return cache_position<T> (m, r.first); +} + +template <typename T> +typename odb::object_traits<T>::pointer_type session:: +_cache_find (odb::database&, const typename odb::object_traits<T>::id_type& id) +{ + typedef typename odb::object_traits<T>::pointer_type pointer_type; + + if (current == 0) + return pointer_type (); // No session, return NULL pointer. + + type_map::const_iterator ti (current->map_.find (&typeid (T))); + + if (ti == current->map_.end ()) + return pointer_type (); + + const object_map<T>& m (static_cast<const object_map<T>&> (*ti->second)); + typename object_map<T>::const_iterator oi (m.find (id)); + + if (oi == m.end ()) + return pointer_type (); + + return oi->second.obj; +} + +template <typename T> +void session:: +_cache_load (const cache_position<T>& p) +{ + typedef typename odb::object_traits<T>::pointer_type pointer_type; + + if (p.map_ == 0) + return; // Empty position. + + // Make a copy for change tracking. If our object model had a + // polymorphic hierarchy, then we would have had to use a + // virtual function-based mechanism (e.g., clone()) instead of + // the copy constructor since for a polymorphic hierarchy all + // the derived objects are stored as pointers to the root object. + // + p.pos_->second.orig = pointer_type (new T (*p.pos_->second.obj)); +} + +template <typename T> +void session:: +_cache_update (odb::database&, const T& obj) +{ + typedef odb::object_traits<T> object_traits; + typedef typename object_traits::pointer_type pointer_type; + + if (current == 0) + return; // No session. + + // User explicitly updated the object by calling database::update(). + // Change the state to flushed and reset the original copy (we are + // still tracking changes after the update). + // + type_map::iterator ti (current->map_.find (&typeid (T))); + + if (ti == current->map_.end ()) + return; // This object is not in the session. + + object_map<T>& m (static_cast<object_map<T>&> (*ti->second)); + typename object_map<T>::iterator oi (m.find (object_traits::id (obj))); + + if (oi == m.end ()) + return; // This object is not in the session. + + object_data<T>& d (oi->second); + d.orig = pointer_type (new T (*d.obj)); + d.state = flushed; +} + +template <typename T> +void session:: +_cache_erase (odb::database&, + const typename odb::object_traits<T>::id_type& id) +{ + if (current == 0) + return; // No session. + + type_map::iterator ti (current->map_.find (&typeid (T))); + + if (ti == current->map_.end ()) + return; + + object_map<T>& m (static_cast<object_map<T>&> (*ti->second)); + typename object_map<T>::iterator oi (m.find (id)); + + if (oi == m.end ()) + return; + + m.erase (oi); + + if (m.empty ()) + current->map_.erase (ti); +} + +template <typename T> +bool session::object_map<T>:: +flush (odb::database& db) +{ + bool r (false); + for (typename object_map<T>::iterator i (this->begin ()), e (this->end ()); + i != e; ++i) + { + object_data<T>& d (i->second); + + if (d.state == changed || d.obj->changed (*d.orig)) + db.update (d.obj); // State changed by the update() notification. + + r = r || d.state == flushed; + } + + return r; +} + +template <typename T> +void session::object_map<T>:: +mark (unsigned short event) +{ + for (typename object_map<T>::iterator i (this->begin ()), e (this->end ()); + i != e; ++i) + { + object_data<T>& d (i->second); + + if (d.state == flushed) + d.state = event == odb::transaction::event_commit ? tracking : changed; + } +} diff --git a/odb-tests/common/session/custom/test.hxx b/odb-tests/common/session/custom/test.hxx new file mode 100644 index 0000000..3f2703f --- /dev/null +++ b/odb-tests/common/session/custom/test.hxx @@ -0,0 +1,118 @@ +// file : common/session/custom/test.hxx +// license : GNU GPL v2; see accompanying LICENSE file + +#ifndef TEST_HXX +#define TEST_HXX + +#include <string> +#include <memory> +#include <vector> + +#include <odb/core.hxx> +#include <odb/lazy-ptr.hxx> + +class employee; + +#pragma db object pointer(std::shared_ptr) session +class employer +{ +public: + employer (const std::string& name, const std::string& symbol) + : name_ (name), symbol_ (symbol) {} + + const std::string& + name () const {return name_;} + + const std::string& + symbol () const {return symbol_;} + + void + symbol (const std::string& symbol) {symbol_ = symbol;} + + // Employees of this employer. + // + typedef std::vector<odb::lazy_weak_ptr<employee>> employees_type; + + const employees_type& + employees () const {return employees_;} + + employees_type& + employees () {return employees_;} + + // Change tracking. + // +public: + bool + changed (const employer& orig) const + { + // Note that we don't need to track object ids, inverse pointers, nor + // readonly/const data members. + // + return symbol_ != orig.symbol_; + } + +private: + friend class odb::access; + employer () {} + + #pragma db id + std::string name_; + + std::string symbol_; + + #pragma db value_not_null inverse(employer_) + employees_type employees_; +}; + +#pragma db object pointer(std::shared_ptr) session +class employee +{ +public: + typedef ::employer employer_type; + + employee (const std::string& first, + const std::string& last, + std::shared_ptr<employer_type> employer) + : first_ (first), last_ (last), employer_ (employer) {} + + // Name. + // + const std::string& + first () const {return first_;} + + const std::string& + last () const {return last_;} + + // Employer. + // + std::shared_ptr<employer_type> + employer () const {return employer_;} + + void + employer (std::shared_ptr<employer_type> e) {employer_ = e;} + + // Change tracking. + // +public: + bool + changed (const employee& orig) const + { + return first_ != orig.first_ || last_ != orig.last_ || + employer_ != orig.employer_; + } + +private: + friend class odb::access; + employee () {} + + #pragma db id auto + unsigned long id_; + + std::string first_; + std::string last_; + + #pragma db not_null + std::shared_ptr<employer_type> employer_; +}; + +#endif // TEST_HXX diff --git a/odb-tests/common/session/custom/testscript b/odb-tests/common/session/custom/testscript new file mode 100644 index 0000000..39c281d --- /dev/null +++ b/odb-tests/common/session/custom/testscript @@ -0,0 +1,33 @@ +# file : common/session/custom/testscript +# license : GNU GPL v2; see accompanying LICENSE file + +.include ../../../database-options.testscript + +: mysql +: +if $mysql +{ + .include ../../../mysql.testscript + + $create_schema; + $* +} + +: sqlite +: +if $sqlite +{ + .include ../../../sqlite.testscript + + $* +} + +: pgsql +: +if $pgsql +{ + .include ../../../pgsql.testscript + + $create_schema; + $* +} |