summaryrefslogtreecommitdiff
path: root/odb/relational/changelog.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2013-03-20 13:09:45 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2013-04-10 18:46:44 +0200
commitc67c06030fb1ac622c96211bffc054a85efe0aa8 (patch)
tree07a85e0c3b0f3a22b58405ff5582870f2b09f424 /odb/relational/changelog.cxx
parent3b457daf6b252ef31ec0611e7375aa4badd8e63d (diff)
Add support for maintaining log of database model changes
Diffstat (limited to 'odb/relational/changelog.cxx')
-rw-r--r--odb/relational/changelog.cxx288
1 files changed, 288 insertions, 0 deletions
diff --git a/odb/relational/changelog.cxx b/odb/relational/changelog.cxx
new file mode 100644
index 0000000..de5e6f7
--- /dev/null
+++ b/odb/relational/changelog.cxx
@@ -0,0 +1,288 @@
+// file : odb/relational/changelog.cxx
+// copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#include <odb/diagnostics.hxx>
+
+#include <odb/semantics/relational.hxx>
+#include <odb/traversal/relational.hxx>
+
+#include <odb/relational/context.hxx>
+#include <odb/relational/generate.hxx>
+
+using namespace std;
+
+namespace relational
+{
+ namespace changelog
+ {
+ using namespace sema_rel;
+ using sema_rel::model;
+ using sema_rel::changelog;
+
+ namespace
+ {
+ struct diff_model: trav_rel::table
+ {
+ enum mode_type {mode_add, mode_drop};
+
+ diff_model (model& o, mode_type m, changeset& s, graph& gr)
+ : other (o), mode (m), cs (s), g (gr) {}
+
+ virtual void
+ traverse (sema_rel::table& t)
+ {
+ if (mode == mode_add)
+ {
+ if (other.find<sema_rel::table> (t.name ()) == 0)
+ {
+ add_table& at (g.new_node<add_table> (t, cs, g));
+ g.new_edge<qnames> (cs, at, t.name ());
+ }
+ }
+ else
+ {
+ if (other.find<sema_rel::table> (t.name ()) == 0)
+ {
+ drop_table& dt (g.new_node<drop_table> (t.id ()));
+ g.new_edge<qnames> (cs, dt, t.name ());
+ }
+ }
+ }
+
+ protected:
+ model& other;
+ mode_type mode;
+ changeset& cs;
+ graph& g;
+ };
+
+ changeset&
+ diff (model& o, model& n, graph& g)
+ {
+ changeset& r (g.new_node<changeset> (n.version ()));
+
+ {
+ trav_rel::model model;
+ trav_rel::qnames names;
+ diff_model dmodel (o, diff_model::mode_add, r, g);
+ model >> names >> dmodel;
+ model.traverse (n);
+ }
+
+ {
+ trav_rel::model model;
+ trav_rel::qnames names;
+ diff_model dmodel (n, diff_model::mode_drop, r, g);
+ model >> names >> dmodel;
+ model.traverse (o);
+ }
+
+ return r;
+ }
+
+ struct patch_model: trav_rel::add_table,
+ trav_rel::drop_table
+ {
+ patch_model (model& ml, graph& gr): m (ml), g (gr) {}
+
+ virtual void
+ traverse (sema_rel::add_table& at)
+ {
+ try
+ {
+ table& t (g.new_node<table> (at, m, g));
+ g.new_edge<qnames> (m, t, at.name ());
+ }
+ catch (duplicate_name const&)
+ {
+ cerr << "error: invalid changelog: table '" << at.name () <<
+ "' already exists in model version " << m.version () << endl;
+ throw operation_failed ();
+ }
+ }
+
+ virtual void
+ traverse (sema_rel::drop_table& dt)
+ {
+ model::names_iterator i (m.find (dt.name ()));
+
+ if (i == m.names_end () || !i->nameable ().is_a<table> ())
+ {
+ cerr << "error: invalid changelog: table '" << dt.name () <<
+ "' does not exist in model version " << m.version () << endl;
+ throw operation_failed ();
+ }
+
+ g.delete_edge (m, i->nameable (), *i);
+ }
+
+ protected:
+ model& m;
+ graph& g;
+ };
+
+ model&
+ patch (model& m, changeset& c, graph& g)
+ {
+ model& r (g.new_node<model> (m, g));
+
+ trav_rel::changeset changeset;
+ trav_rel::qnames names;
+ patch_model pmodel (r, g);
+ changeset >> names >> pmodel;
+ changeset.traverse (c);
+
+ r.version (c.version ());
+ return r;
+ }
+ }
+
+ cutl::shared_ptr<changelog>
+ generate (model& m,
+ model_version const& mv,
+ changelog* old,
+ string const& name)
+ {
+ cutl::shared_ptr<changelog> cl (new (shared) changelog);
+ graph& g (*cl);
+
+ if (old == 0)
+ {
+ if (!mv.open)
+ {
+ cerr << name << ": error: unable to initialize changelog because " <<
+ "current version is closed" << endl;
+ throw operation_failed ();
+ }
+
+ cerr << name << ": info: initializing changelog with base version " <<
+ m.version () << endl;
+
+ g.new_edge<contains_model> (*cl, g.new_node<model> (m, g));
+ return cl;
+ }
+
+ // Get the changelog base and current versions and do some sanity
+ // checks.
+ //
+ version bver (old->model ().version ());
+ version cver (
+ old->contains_changeset_empty ()
+ ? bver
+ : old->contains_changeset_back ().changeset ().version ());
+
+ if (mv.current < cver)
+ {
+ cerr << name << ": error: latest changelog version is greater " <<
+ "than current version" << endl;
+ throw operation_failed ();
+ }
+
+ // Build the new changelog.
+ //
+ model& oldm (old->model ());
+
+ // Handle the cases where we just override the log with the current
+ // model.
+ //
+ if (mv.base == mv.current || bver == mv.current || oldm.names_empty ())
+ {
+ // If the current version is closed, make sure the model hasn't
+ // changed.
+ //
+ if (!mv.open)
+ {
+ changeset& cs (diff (oldm, m, g));
+
+ if (!cs.names_empty ())
+ {
+ qnames& n (*cs.names_begin ());
+
+ cerr << name << ": error: current version is closed" << endl;
+ cerr << name << ": info: first new change is " <<
+ n.nameable ().kind () << " '" << n.name () << "'" << endl;
+
+ throw operation_failed ();
+ }
+ }
+
+ g.new_edge<contains_model> (*cl, g.new_node<model> (m, g));
+ return cl;
+ }
+
+ // Now we have a case with a "real" old model (i.e., non-empty
+ // and with version older than current) as well as zero or more
+ // changeset.
+ //
+ //
+ model* base (bver >= mv.base ? &g.new_node<model> (oldm, g) : 0);
+ model* last (&oldm);
+
+ for (changelog::contains_changeset_iterator i (
+ old->contains_changeset_begin ());
+ i != old->contains_changeset_end (); ++i)
+ {
+ changeset& cs (i->changeset ());
+
+ // Don't copy the changeset for the current version. Instead, we
+ // will re-create it from scratch.
+ //
+ if (cs.version () == mv.current)
+ {
+ // If the current version is closed, make sure the model hasn't
+ // changed.
+ //
+ if (!mv.open)
+ {
+ model& old (patch (*last, cs, g));
+ changeset& cs (diff (old, m, g));
+
+ if (!cs.names_empty ())
+ {
+ qnames& n (*cs.names_begin ());
+
+ cerr << name << ": error: current version is closed" << endl;
+ cerr << name << ": info: first new change is " <<
+ n.nameable ().kind () << " '" << n.name () << "'" << endl;
+
+ throw operation_failed ();
+ }
+ }
+ break;
+ }
+
+ last = &patch (*last, cs, g);
+
+ if (base == 0 && last->version () >= mv.base)
+ base = last;
+
+ // Copy the changeset unless it is below or at our base version.
+ //
+ if (last->version () <= mv.base)
+ continue;
+
+ g.new_edge<contains_changeset> (*cl, g.new_node<changeset> (cs, g));
+ }
+
+ // If we still haven't found the new base model, then take the
+ // latest and update its version.
+ //
+ if (base == 0)
+ {
+ base = last != &oldm ? last : &g.new_node<model> (oldm, g);
+ base->version (mv.base);
+ }
+ g.new_edge<contains_model> (*cl, *base);
+
+ // Add a changeset for the current version.
+ //
+ changeset& cs (diff (*last, m, g));
+
+ if (!cs.names_empty ())
+ g.new_edge<contains_changeset> (*cl, cs);
+
+ return cl;
+ }
+ }
+}