summaryrefslogtreecommitdiff
path: root/odb-tests/common/session/custom
diff options
context:
space:
mode:
Diffstat (limited to 'odb-tests/common/session/custom')
-rw-r--r--odb-tests/common/session/custom/buildfile43
-rw-r--r--odb-tests/common/session/custom/driver.cxx231
-rw-r--r--odb-tests/common/session/custom/session.cxx57
-rw-r--r--odb-tests/common/session/custom/session.hxx191
-rw-r--r--odb-tests/common/session/custom/session.txx159
-rw-r--r--odb-tests/common/session/custom/test.hxx118
-rw-r--r--odb-tests/common/session/custom/testscript33
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;
+ $*
+}