summaryrefslogtreecommitdiff
path: root/odb/relational/changelog.cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2013-03-28 16:04:48 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2013-04-10 18:46:44 +0200
commit2aa3cabf1b737e225178230882ee9aadfd817ce0 (patch)
tree85a6c7a90f8483ca11a4bc825cd2ef22114c3d97 /odb/relational/changelog.cxx
parente999b1e7295acd8cdb646c2db7db1f5059f10c7d (diff)
Add changelog support for add/drop index/foreign key
Also diagnose changes to primary keys and establish the 'alters' association.
Diffstat (limited to 'odb/relational/changelog.cxx')
-rw-r--r--odb/relational/changelog.cxx357
1 files changed, 340 insertions, 17 deletions
diff --git a/odb/relational/changelog.cxx b/odb/relational/changelog.cxx
index 530c3a0..2208078 100644
--- a/odb/relational/changelog.cxx
+++ b/odb/relational/changelog.cxx
@@ -26,7 +26,10 @@ namespace relational
// diff
//
- struct diff_table: trav_rel::column
+ struct diff_table: trav_rel::column,
+ trav_rel::primary_key,
+ trav_rel::foreign_key,
+ trav_rel::index
{
enum mode_type {mode_add, mode_drop};
@@ -43,20 +46,28 @@ namespace relational
if (column* oc = other.find<column> (c.name ()))
{
if (c.type () != oc->type ())
- diagnose_unsupported (c, "type");
+ diagnose_column (c, "type", oc->type (), c.type ());
if (c.null () != oc->null ())
{
alter_column& ac (g.new_node<alter_column> (c.id ()));
+
+ // Set the alters edge.
+ //
+ column* b (at.lookup<column, drop_column> (c.name ()));
+ assert (b != 0);
+ g.new_edge<alters> (ac, *b);
+
ac.null (c.null ());
g.new_edge<unames> (at, ac, c.name ());
}
if (c.default_ () != oc->default_ ())
- diagnose_unsupported (c, "default value");
+ diagnose_column (
+ c, "default value", oc->default_ (), c.default_ ());
if (c.options () != oc->options ())
- diagnose_unsupported (c, "options");
+ diagnose_column (c, "options", oc->options (), c.options ());
}
else
{
@@ -66,7 +77,7 @@ namespace relational
}
else
{
- if (other.find<sema_rel::column> (c.name ()) == 0)
+ if (other.find<column> (c.name ()) == 0)
{
drop_column& dc (g.new_node<drop_column> (c.id ()));
g.new_edge<unames> (at, dc, c.name ());
@@ -74,27 +85,244 @@ namespace relational
}
}
+ virtual void
+ traverse (sema_rel::primary_key& pk)
+ {
+ using sema_rel::primary_key;
+
+ if (mode == mode_add)
+ {
+ if (primary_key* opk = other.find<primary_key> (pk.name ()))
+ {
+ if (pk.auto_ () != opk->auto_ ())
+ diagnose_primary_key (pk, "auto kind");
+
+ if (pk.contains_size () != opk->contains_size ())
+ diagnose_primary_key (pk, "member set");
+
+ for (primary_key::contains_size_type i (0);
+ i != pk.contains_size (); ++i)
+ {
+ sema_rel::contains& c (pk.contains_at (i));
+ sema_rel::contains& oc (opk->contains_at (i));
+
+ if (c.column ().name () != oc.column ().name ())
+ diagnose_primary_key (pk, "member set");
+ }
+ }
+ else
+ {
+ location const& l (pk.get<location> ("cxx-location"));
+ error (l) << "adding object id to an existing class is " <<
+ "not supported" << endl;
+ info (l) << "consider re-implementing this change by adding " <<
+ "a new class with the object id, migrating the data, and " <<
+ "deleteing the old class" << endl;
+ throw operation_failed ();
+ }
+ }
+ else
+ {
+ if (other.find<primary_key> (pk.name ()) == 0)
+ {
+ location const& l (other.get<location> ("cxx-location"));
+ error (l) << "deleting object id from an existing class is " <<
+ "not supported" << endl;
+ info (l) << "consider re-implementing this change by adding " <<
+ "a new class without the object id, migrating the data, " <<
+ "and deleteing the old class" << endl;
+ throw operation_failed ();
+ }
+ }
+ }
+
+ virtual void
+ traverse (sema_rel::foreign_key& fk)
+ {
+ using sema_rel::foreign_key;
+
+ if (mode == mode_add)
+ {
+ if (foreign_key* ofk = other.find<foreign_key> (fk.name ()))
+ {
+ if (fk.deferred () != ofk->deferred ())
+ diagnose_foreign_key (fk, "deferred kind");
+
+ if (fk.on_delete () != ofk->on_delete ())
+ diagnose_foreign_key (fk, "on delete action");
+
+ if (fk.referenced_table () != ofk->referenced_table ())
+ diagnose_foreign_key (fk, "pointed-to class");
+
+ if (fk.referenced_columns () != ofk->referenced_columns ())
+ diagnose_foreign_key (fk, "id member set");
+
+ if (fk.contains_size () != ofk->contains_size ())
+ diagnose_foreign_key (fk, "id member set");
+
+ for (foreign_key::contains_size_type i (0);
+ i != fk.contains_size (); ++i)
+ {
+ sema_rel::contains& c (fk.contains_at (i));
+ sema_rel::contains& oc (ofk->contains_at (i));
+
+ if (c.column ().name () != oc.column ().name ())
+ diagnose_foreign_key (fk, "id member set");
+ }
+ }
+ else
+ {
+ add_foreign_key& afk (g.new_node<add_foreign_key> (fk, at, g));
+ g.new_edge<unames> (at, afk, fk.name ());
+ }
+ }
+ else
+ {
+ if (other.find<foreign_key> (fk.name ()) == 0)
+ {
+ drop_foreign_key& dfk (g.new_node<drop_foreign_key> (fk.id ()));
+ g.new_edge<unames> (at, dfk, fk.name ());
+ }
+ }
+ }
+
+ virtual void
+ traverse (sema_rel::index& i)
+ {
+ using sema_rel::index;
+
+ if (mode == mode_add)
+ {
+ if (index* oi = other.find<index> (i.name ()))
+ {
+ if (i.type () != oi->type ())
+ diagnose_index (i, "type", oi->type (), i.type ());
+
+ if (i.method () != oi->method ())
+ diagnose_index (i, "method", oi->method (), i.method ());
+
+ if (i.options () != oi->options ())
+ diagnose_index (i, "options", oi->options (), i.options ());
+
+ if (i.contains_size () != oi->contains_size ())
+ diagnose_index (i, "member set", "", "");
+
+ for (index::contains_size_type j (0);
+ j != i.contains_size (); ++j)
+ {
+ sema_rel::contains& c (i.contains_at (j));
+ sema_rel::contains& oc (oi->contains_at (j));
+
+ if (c.column ().name () != oc.column ().name ())
+ diagnose_index (i, "member set", "", "");
+
+ if (c.options () != oc.options ())
+ diagnose_index (
+ i, "member options", oc.options (), c.options ());
+ }
+ }
+ else
+ {
+ add_index& ai (g.new_node<add_index> (i, at, g));
+ g.new_edge<unames> (at, ai, i.name ());
+ }
+ }
+ else
+ {
+ if (other.find<index> (i.name ()) == 0)
+ {
+ drop_index& di (g.new_node<drop_index> (i.id ()));
+ g.new_edge<unames> (at, di, i.name ());
+ }
+ }
+ }
+
void
- diagnose_unsupported (sema_rel::column& c, char const* name)
+ diagnose_column (sema_rel::column& c,
+ char const* name,
+ string const& ov,
+ string const& nv)
{
table& t (c.table ());
location const& tl (t.get<location> ("cxx-location"));
location const& cl (c.get<location> ("cxx-location"));
error (cl) << "change to data member results in the change of " <<
- "the corresponding column " << name << endl;
+ "the corresponding column " << name;
+
+ if (!ov.empty () || !nv.empty ())
+ cerr << " (old: '" << ov << "', new: '" << nv << "')";
+
+ cerr << endl;
+
error (cl) << "this change is not yet handled automatically" << endl;
info (cl) << "corresponding column '" << c.name () << "' " <<
"originates here" << endl;
info (tl) << "corresponding table '" << t.name () << "' " <<
"originates here" << endl;
- info (cl) << "consider re-implementing this change by creating " <<
+ info (cl) << "consider re-implementing this change by adding " <<
"a new data member with the desired " << name << ", migrating " <<
"the data, and deleting the old data member" << endl;
throw operation_failed ();
}
+ void
+ diagnose_primary_key (sema_rel::primary_key& pk, char const* name)
+ {
+ location const& l (pk.get<location> ("cxx-location"));
+
+ error (l) << "changing object id " << name << " in an existing " <<
+ "class is not supported" << endl;
+ info (l) << "consider re-implementing this change by adding " <<
+ "a new class with the desired object id " << name << ", " <<
+ "migrating the data, and deleteing the old class" << endl;
+
+ throw operation_failed ();
+ }
+
+ void
+ diagnose_foreign_key (sema_rel::foreign_key& fk, char const* name)
+ {
+ location const& l (fk.get<location> ("cxx-location"));
+
+ error (l) << "changing object pointer " << name << " is not " <<
+ "supported" << endl;
+ info (l) << "consider re-implementing this change by adding " <<
+ "a new object pointer with the desired " << name << ", " <<
+ "migrating the data, and deleteing the old pointer" << endl;
+
+ throw operation_failed ();
+ }
+
+ void
+ diagnose_index (sema_rel::index& i,
+ char const* name,
+ string const& ov,
+ string const& nv)
+ {
+ table& t (i.table ());
+ location const& tl (t.get<location> ("cxx-location"));
+ location const& il (i.get<location> ("cxx-location"));
+
+ error (il) << "change to index " << name;
+
+ if (!ov.empty () || !nv.empty ())
+ cerr << " (old: '" << ov << "', new: '" << nv << "')";
+
+ cerr << " is not yet handled automatically" << endl;
+
+ info (il) << "corresponding index '" << i.name () << "' " <<
+ "originates here" << endl;
+ info (tl) << "corresponding table '" << t.name () << "' " <<
+ "originates here" << endl;
+ info (il) << "consider re-implementing this change by adding " <<
+ "a new index with the desired " << name << " and deleting the " <<
+ "old one" << endl;
+
+ throw operation_failed ();
+ }
+
protected:
table& other;
mode_type mode;
@@ -122,6 +350,12 @@ namespace relational
//
alter_table& at (g.new_node<alter_table> (t.id ()));
+ // Set the alters edge for lookup.
+ //
+ table* bt (cs.lookup<table, drop_table> (t.name ()));
+ assert (bt != 0);
+ alters& ae (g.new_edge<alters> (at, *bt));
+
{
trav_rel::table table;
trav_rel::unames names;
@@ -141,7 +375,10 @@ namespace relational
if (!at.names_empty ())
g.new_edge<qnames> (cs, at, t.name ());
else
+ {
+ g.delete_edge (at, *bt, ae);
g.delete_node (at);
+ }
}
else
{
@@ -171,14 +408,21 @@ namespace relational
// Assumes the new model has cxx-location set.
//
changeset&
- diff (model& o, model& n, graph& g)
+ diff (model& o, model& n, changelog& l)
{
- changeset& r (g.new_node<changeset> (n.version ()));
+ changeset& r (l.new_node<changeset> (n.version ()));
+
+ // Set the alters edge for lookup.
+ //
+ l.new_edge<alters> (r,
+ l.contains_changeset_empty ()
+ ? static_cast<qscope&> (l.model ())
+ : l.contains_changeset_back ().changeset ());
{
trav_rel::model model;
trav_rel::qnames names;
- diff_model dmodel (o, diff_model::mode_add, r, g);
+ diff_model dmodel (o, diff_model::mode_add, r, l);
model >> names >> dmodel;
model.traverse (n);
}
@@ -186,7 +430,7 @@ namespace relational
{
trav_rel::model model;
trav_rel::qnames names;
- diff_model dmodel (n, diff_model::mode_drop, r, g);
+ diff_model dmodel (n, diff_model::mode_drop, r, l);
model >> names >> dmodel;
model.traverse (o);
}
@@ -200,7 +444,11 @@ namespace relational
struct patch_table: trav_rel::add_column,
trav_rel::drop_column,
- trav_rel::alter_column
+ trav_rel::alter_column,
+ trav_rel::add_index,
+ trav_rel::drop_index,
+ trav_rel::add_foreign_key,
+ trav_rel::drop_foreign_key
{
patch_table (table& tl, graph& gr): t (tl), g (gr) {}
@@ -251,6 +499,74 @@ namespace relational
}
}
+ virtual void
+ traverse (sema_rel::add_index& ai)
+ {
+ using sema_rel::index;
+
+ try
+ {
+ index& i (g.new_node<index> (ai, t, g));
+ g.new_edge<unames> (t, i, ai.name ());
+ }
+ catch (duplicate_name const&)
+ {
+ cerr << "error: invalid changelog: index '" << ai.name () <<
+ "' already exists in table '" << t.name () << "'" << endl;
+ throw operation_failed ();
+ }
+ }
+
+ virtual void
+ traverse (sema_rel::drop_index& di)
+ {
+ using sema_rel::index;
+ table::names_iterator i (t.find (di.name ()));
+
+ if (i == t.names_end () || !i->nameable ().is_a<index> ())
+ {
+ cerr << "error: invalid changelog: index '" << di.name () <<
+ "' does not exist in table '" << t.name () << "'" << endl;
+ throw operation_failed ();
+ }
+
+ g.delete_edge (t, i->nameable (), *i);
+ }
+
+ virtual void
+ traverse (sema_rel::add_foreign_key& afk)
+ {
+ using sema_rel::foreign_key;
+
+ try
+ {
+ foreign_key& fk (g.new_node<foreign_key> (afk, t, g));
+ g.new_edge<unames> (t, fk, afk.name ());
+ }
+ catch (duplicate_name const&)
+ {
+ cerr << "error: invalid changelog: foreign key '" << afk.name () <<
+ "' already exists in table '" << t.name () << "'" << endl;
+ throw operation_failed ();
+ }
+ }
+
+ virtual void
+ traverse (sema_rel::drop_foreign_key& dfk)
+ {
+ using sema_rel::foreign_key;
+ table::names_iterator i (t.find (dfk.name ()));
+
+ if (i == t.names_end () || !i->nameable ().is_a<foreign_key> ())
+ {
+ cerr << "error: invalid changelog: foreign key '" << dfk.name () <<
+ "' does not exist in table '" << t.name () << "'" << endl;
+ throw operation_failed ();
+ }
+
+ g.delete_edge (t, i->nameable (), *i);
+ }
+
protected:
table& t;
graph& g;
@@ -389,7 +705,7 @@ namespace relational
//
if (!mv.open)
{
- changeset& cs (diff (oldm, m, g));
+ changeset& cs (diff (oldm, m, *cl));
if (!cs.names_empty ())
{
@@ -432,7 +748,7 @@ namespace relational
if (!mv.open)
{
model& old (patch (*last, cs, g));
- changeset& cs (diff (old, m, g));
+ changeset& cs (diff (old, m, *cl));
if (!cs.names_empty ())
{
@@ -458,7 +774,14 @@ namespace relational
if (last->version () <= mv.base)
continue;
- g.new_edge<contains_changeset> (*cl, g.new_node<changeset> (cs, g));
+ changeset& c (
+ g.new_node<changeset> (
+ cs,
+ cl->contains_changeset_empty ()
+ ? static_cast<qscope&> (*base) // Cannot be NULL.
+ : cl->contains_changeset_back ().changeset (),
+ g));
+ g.new_edge<contains_changeset> (*cl, c);
}
// If we still haven't found the new base model, then take the
@@ -473,7 +796,7 @@ namespace relational
// Add a changeset for the current version.
//
- changeset& cs (diff (*last, m, g));
+ changeset& cs (diff (*last, m, *cl));
if (!cs.names_empty ())
g.new_edge<contains_changeset> (*cl, cs);