aboutsummaryrefslogtreecommitdiff
path: root/common/session
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2013-01-09 14:50:26 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2013-01-16 07:42:56 +0200
commit5cf30ccfe764701f549e4152ad312187221f5285 (patch)
treef3e558a08ab9d2d8fb00855ab1c0ef42d4d9af91 /common/session
parent9bd664e4cb39f6654e8754c8cfd4c28295ee2d90 (diff)
Implement two-phase session insertion
On the first step an uninitialized object is inserted into the cache as before (this is necessary to handle recursive loading). The second step is to notify the session that the object has been initialized. On this second step the session can perform change tracking preparations, such as make a copy of the object or reset the modification flag. New test: common/session/custom (implements a custom session that uses copies to track changes).
Diffstat (limited to 'common/session')
-rw-r--r--common/session/custom/driver.cxx168
-rw-r--r--common/session/custom/makefile114
-rw-r--r--common/session/custom/session.cxx50
-rw-r--r--common/session/custom/session.hxx139
-rw-r--r--common/session/custom/session.txx118
-rw-r--r--common/session/custom/test.hxx119
-rw-r--r--common/session/custom/test.std0
7 files changed, 708 insertions, 0 deletions
diff --git a/common/session/custom/driver.cxx b/common/session/custom/driver.cxx
new file mode 100644
index 0000000..1ec7cbb
--- /dev/null
+++ b/common/session/custom/driver.cxx
@@ -0,0 +1,168 @@
+// file : common/session/custom/driver.cxx
+// copyright : Copyright (c) 2009-2012 Code Synthesis Tools CC
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test custom session (C++11 only).
+//
+
+#include <memory>
+#include <cstddef> // std::size_t
+#include <cassert>
+#include <iostream>
+
+#include <odb/tracer.hxx>
+#include <odb/database.hxx>
+#include <odb/session.hxx>
+#include <odb/transaction.hxx>
+
+#include <common/common.hxx>
+
+#include "session.hxx"
+
+#include "test.hxx"
+#include "test-odb.hxx"
+
+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;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ auto_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");
+ ste = db->load<employee> (st->employees ()[0].object_id ());
+
+ // 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");
+ cse = db->load<employee> (cs->employees ()[0].object_id ());
+ 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); // Flush all the changes.
+ assert (tracer.count == 3);
+ t.commit ();
+ s.mark (); // Mark all the changed objects as unchanged.
+ }
+
+ {
+ 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); // Flush all the changes.
+ assert (tracer.count == 2);
+ t.commit ();
+ s.mark (); // Mark all the changed objects as unchanged.
+ }
+
+ {
+ transaction t (db->begin ());
+ tracer.count = 0;
+ t.tracer (tracer);
+ s.flush (*db);
+ assert (tracer.count == 0);
+ t.commit ();
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/common/session/custom/makefile b/common/session/custom/makefile
new file mode 100644
index 0000000..6fb6c95
--- /dev/null
+++ b/common/session/custom/makefile
@@ -0,0 +1,114 @@
+# file : common/session/custom/makefile
+# copyright : Copyright (c) 2009-2012 Code Synthesis Tools CC
+# license : GNU GPL v2; see accompanying LICENSE file
+
+include $(dir $(lastword $(MAKEFILE_LIST)))../../../build/bootstrap.make
+
+cxx_tun := driver.cxx session.cxx
+odb_hdr := test.hxx
+cxx_obj := $(addprefix $(out_base)/,$(cxx_tun:.cxx=.o) $(odb_hdr:.hxx=-odb.o))
+cxx_od := $(cxx_obj:.o=.o.d)
+
+common.l := $(out_root)/libcommon/common/common.l
+common.l.cpp-options := $(out_root)/libcommon/common/common.l.cpp-options
+
+driver := $(out_base)/driver
+dist := $(out_base)/.dist
+test := $(out_base)/.test
+clean := $(out_base)/.clean
+
+# Import.
+#
+$(call import,\
+ $(scf_root)/import/odb/stub.make,\
+ odb: odb,odb-rules: odb_rules)
+
+# Build.
+#
+$(driver): $(cxx_obj) $(common.l)
+$(cxx_obj) $(cxx_od): cpp_options := -I$(out_base) -I$(src_base)
+$(cxx_obj) $(cxx_od): $(common.l.cpp-options)
+
+genf := $(addprefix $(odb_hdr:.hxx=-odb),.hxx .ixx .cxx) $(odb_hdr:.hxx=.sql)
+gen := $(addprefix $(out_base)/,$(genf))
+
+$(gen): $(odb)
+$(gen): odb := $(odb)
+$(gen) $(dist): export odb_options += --database $(db_id) --generate-schema \
+--generate-session --session-type ::session --hxx-prologue \
+'\#include "session.hxx"' --table-prefix session_custom_
+$(gen): cpp_options := -I$(src_base)
+$(gen): $(common.l.cpp-options)
+
+$(call include-dep,$(cxx_od),$(cxx_obj),$(gen))
+
+# Alias for default target.
+#
+$(out_base)/: $(driver)
+
+# Dist
+#
+name := $(subst /,-,$(subst $(src_root)/common/,,$(src_base)))
+
+$(dist): db_id := @database@
+$(dist): sources := $(cxx_tun)
+$(dist): headers := $(odb_hdr)
+$(dist): data_dist := test.std
+$(dist): export name := $(name)
+$(dist): export extra_dist := $(data_dist) $(call vc10projs,$(name)) \
+$(call vc11projs,$(name))
+$(dist):
+ $(call dist-data,$(sources) $(headers) $(data_dist))
+ $(call meta-automake,../../template/Makefile.am)
+ $(call meta-vc10projs,../../template/template,$(name))
+ $(call meta-vc11projs,../../template/template,$(name))
+
+# Test.
+#
+$(test): $(driver) $(src_base)/test.std
+ $(call schema)
+ $(call message,test $<,$< --options-file $(dcf_root)/db.options \
+>$(out_base)/test.out)
+ $(call message,,diff -u $(src_base)/test.std $(out_base)/test.out)
+ $(call message,,rm -f $(out_base)/test.out)
+
+# Clean.
+#
+$(clean): \
+ $(driver).o.clean \
+ $(addsuffix .cxx.clean,$(cxx_obj)) \
+ $(addsuffix .cxx.clean,$(cxx_od)) \
+ $(addprefix $(out_base)/,$(odb_hdr:.hxx=-odb.cxx.hxx.clean))
+ $(call message,,rm -f $(out_base)/test.out)
+
+# Generated .gitignore.
+#
+ifeq ($(out_base),$(src_base))
+$(driver): | $(out_base)/.gitignore
+
+$(out_base)/.gitignore: files := driver $(genf)
+$(clean): $(out_base)/.gitignore.clean
+
+$(call include,$(bld_root)/git/gitignore.make)
+endif
+
+# How to.
+#
+$(call include,$(bld_root)/dist.make)
+$(call include,$(bld_root)/meta/vc10proj.make)
+$(call include,$(bld_root)/meta/vc11proj.make)
+$(call include,$(bld_root)/meta/automake.make)
+
+$(call include,$(bld_root)/cxx/standard.make) # cxx_standard
+ifdef cxx_standard
+$(gen): odb_options += --std $(cxx_standard)
+$(call include,$(odb_rules))
+endif
+
+$(call include,$(bld_root)/cxx/cxx-d.make)
+$(call include,$(bld_root)/cxx/cxx-o.make)
+$(call include,$(bld_root)/cxx/o-e.make)
+
+# Dependencies.
+#
+$(call import,$(src_root)/libcommon/makefile)
diff --git a/common/session/custom/session.cxx b/common/session/custom/session.cxx
new file mode 100644
index 0000000..b50493f
--- /dev/null
+++ b/common/session/custom/session.cxx
@@ -0,0 +1,50 @@
+// file : common/session/custom/session.cxx
+// copyright : Copyright (c) 2009-2012 Code Synthesis Tools CC
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#include <cassert>
+
+#include "session.hxx"
+
+static session* current_; // Use TLS in multi-threaded applications.
+
+session::
+session ()
+{
+ assert (current_ == 0);
+ current_ = this;
+}
+
+session::
+~session ()
+{
+ assert (current_ == this);
+ current_ = 0;
+}
+
+bool session::
+has_current ()
+{
+ return current_ != 0;
+}
+
+session& session::
+current ()
+{
+ assert (current_ != 0);
+ return *current_;
+}
+
+void session::
+flush (odb::database& db)
+{
+ for (type_map::iterator i (map_.begin ()), e (map_.end ()); i != e; ++i)
+ i->second->flush (db);
+}
+
+void session::
+mark ()
+{
+ for (type_map::iterator i (map_.begin ()), e (map_.end ()); i != e; ++i)
+ i->second->mark ();
+}
diff --git a/common/session/custom/session.hxx b/common/session/custom/session.hxx
new file mode 100644
index 0000000..bb60a4b
--- /dev/null
+++ b/common/session/custom/session.hxx
@@ -0,0 +1,139 @@
+// file : common/session/custom/session.hxx
+// copyright : Copyright (c) 2009-2012 Code Synthesis Tools CC
+// 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/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&);
+
+ // Current session interface.
+ //
+public:
+ static bool
+ has_current ();
+
+ static session&
+ current ();
+
+ // Change tracking interface.
+ //
+ // Call flush() within a transaction to apply the changes to the
+ // database. Then after successfully committing the transaction,
+ // call mark() to mark all the changed objects as again unchanged.
+ //
+public:
+ void
+ flush (odb::database&);
+
+ void
+ mark ();
+
+private:
+ struct object_map_base
+ {
+ virtual
+ ~object_map_base () {}
+
+ virtual void
+ flush (odb::database&) = 0;
+
+ virtual void
+ mark () = 0;
+ };
+
+ template <typename T>
+ struct object_state
+ {
+ typedef typename odb::object_traits<T>::pointer_type pointer_type;
+
+ explicit
+ object_state (pointer_type o): obj (o), flushed_ (false) {}
+
+ pointer_type obj;
+ pointer_type orig;
+ bool flushed_;
+ };
+
+ template <typename T>
+ struct object_map: object_map_base,
+ std::map<typename odb::object_traits<T>::id_type,
+ object_state<T> >
+ {
+ virtual void
+ flush (odb::database&);
+
+ virtual void
+ mark ();
+ };
+
+ // Object cache interface.
+ //
+public:
+ template <typename T>
+ struct position
+ {
+ typedef object_map<T> map;
+ typedef typename map::iterator iterator;
+
+ position () {}
+ position (map& m, const iterator& p): map_ (&m), pos_ (p) {}
+
+ map* map_;
+ iterator pos_;
+ };
+
+ template <typename T>
+ position<T>
+ insert (odb::database&,
+ const typename odb::object_traits<T>::id_type&,
+ const typename odb::object_traits<T>::pointer_type&);
+
+ template <typename T>
+ static void
+ initialize (const position<T>&);
+
+ template <typename T>
+ typename odb::object_traits<T>::pointer_type
+ find (odb::database&, const typename odb::object_traits<T>::id_type&) const;
+
+ template <typename T>
+ void
+ erase (odb::database&, const typename odb::object_traits<T>::id_type&);
+
+ template <typename T>
+ static void
+ erase (const position<T>& p)
+ {
+ p.map_->erase (p.pos_);
+ }
+
+private:
+ typedef std::map<const std::type_info*,
+ std::shared_ptr<object_map_base>,
+ odb::details::type_info_comparator> type_map;
+ type_map map_;
+};
+
+#include "session.txx"
+
+#endif // SESSION_HXX
diff --git a/common/session/custom/session.txx b/common/session/custom/session.txx
new file mode 100644
index 0000000..203b4e3
--- /dev/null
+++ b/common/session/custom/session.txx
@@ -0,0 +1,118 @@
+// file : common/session/custom/session.txx
+// copyright : Copyright (c) 2009-2012 Code Synthesis Tools CC
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#include <cassert>
+
+template <typename T>
+typename session::position<T> session::
+insert (odb::database&,
+ const typename odb::object_traits<T>::id_type& id,
+ const typename odb::object_traits<T>::pointer_type& obj)
+{
+ typedef odb::object_traits<T> object_traits;
+
+ std::shared_ptr<object_map_base>& pm (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_state<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 position<T> (m, r.first);
+}
+
+template <typename T>
+void session::
+initialize (const position<T>& p)
+{
+ typedef typename odb::object_traits<T>::pointer_type pointer_type;
+
+ // 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>
+typename odb::object_traits<T>::pointer_type session::
+find (odb::database&, const typename odb::object_traits<T>::id_type& id) const
+{
+ typedef typename odb::object_traits<T>::pointer_type pointer_type;
+
+ type_map::const_iterator ti (map_.find (&typeid (T)));
+
+ if (ti == 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::
+erase (odb::database&, const typename odb::object_traits<T>::id_type& id)
+{
+ type_map::iterator ti (map_.find (&typeid (T)));
+
+ if (ti == 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 ())
+ map_.erase (ti);
+}
+
+template <typename T>
+void session::object_map<T>::
+flush (odb::database& db)
+{
+ for (typename object_map<T>::iterator i (this->begin ()), e (this->end ());
+ i != e; ++i)
+ {
+ const T& obj (*i->second.obj);
+
+ if (obj.changed (*i->second.orig))
+ {
+ db.update (obj);
+ i->second.flushed_ = true;
+ }
+ }
+}
+
+template <typename T>
+void session::object_map<T>::
+mark ()
+{
+ for (typename object_map<T>::iterator i (this->begin ()), e (this->end ());
+ i != e; ++i)
+ {
+ if (i->second.flushed_)
+ {
+ i->second.orig.reset (new T (*i->second.obj));
+ i->second.flushed_ = false;
+ }
+ }
+}
diff --git a/common/session/custom/test.hxx b/common/session/custom/test.hxx
new file mode 100644
index 0000000..1c4a713
--- /dev/null
+++ b/common/session/custom/test.hxx
@@ -0,0 +1,119 @@
+// file : common/session/custom/test.hxx
+// copyright : Copyright (c) 2009-2012 Code Synthesis Tools CC
+// 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/common/session/custom/test.std b/common/session/custom/test.std
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/common/session/custom/test.std