From 2aa3cabf1b737e225178230882ee9aadfd817ce0 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 28 Mar 2013 16:04:48 +0200 Subject: Add changelog support for add/drop index/foreign key Also diagnose changes to primary keys and establish the 'alters' association. --- odb/relational/changelog.cxx | 357 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 340 insertions(+), 17 deletions(-) (limited to 'odb/relational') 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 (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 (c.id ())); + + // Set the alters edge. + // + column* b (at.lookup (c.name ())); + assert (b != 0); + g.new_edge (ac, *b); + ac.null (c.null ()); g.new_edge (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 (c.name ()) == 0) + if (other.find (c.name ()) == 0) { drop_column& dc (g.new_node (c.id ())); g.new_edge (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 (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 ("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 (pk.name ()) == 0) + { + location const& l (other.get ("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 (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 (fk, at, g)); + g.new_edge (at, afk, fk.name ()); + } + } + else + { + if (other.find (fk.name ()) == 0) + { + drop_foreign_key& dfk (g.new_node (fk.id ())); + g.new_edge (at, dfk, fk.name ()); + } + } + } + + virtual void + traverse (sema_rel::index& i) + { + using sema_rel::index; + + if (mode == mode_add) + { + if (index* oi = other.find (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 (i, at, g)); + g.new_edge (at, ai, i.name ()); + } + } + else + { + if (other.find (i.name ()) == 0) + { + drop_index& di (g.new_node (i.id ())); + g.new_edge (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 ("cxx-location")); location const& cl (c.get ("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 ("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 ("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 ("cxx-location")); + location const& il (i.get ("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 (t.id ())); + // Set the alters edge for lookup. + // + table* bt (cs.lookup (t.name ())); + assert (bt != 0); + alters& ae (g.new_edge (at, *bt)); + { trav_rel::table table; trav_rel::unames names; @@ -141,7 +375,10 @@ namespace relational if (!at.names_empty ()) g.new_edge (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 (n.version ())); + changeset& r (l.new_node (n.version ())); + + // Set the alters edge for lookup. + // + l.new_edge (r, + l.contains_changeset_empty () + ? static_cast (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 (ai, t, g)); + g.new_edge (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 ()) + { + 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 (afk, t, g)); + g.new_edge (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 ()) + { + 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 (*cl, g.new_node (cs, g)); + changeset& c ( + g.new_node ( + cs, + cl->contains_changeset_empty () + ? static_cast (*base) // Cannot be NULL. + : cl->contains_changeset_back ().changeset (), + g)); + g.new_edge (*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 (*cl, cs); -- cgit v1.1