diff options
Diffstat (limited to 'odb/relational/changelog.cxx')
-rw-r--r-- | odb/relational/changelog.cxx | 1240 |
1 files changed, 0 insertions, 1240 deletions
diff --git a/odb/relational/changelog.cxx b/odb/relational/changelog.cxx deleted file mode 100644 index 0e6d580..0000000 --- a/odb/relational/changelog.cxx +++ /dev/null @@ -1,1240 +0,0 @@ -// file : odb/relational/changelog.cxx -// copyright : Copyright (c) 2009-2019 Code Synthesis Tools CC -// license : GNU GPL v3; see accompanying LICENSE file - -#include <map> - -#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; - - typedef map<qname, semantics::node*> deleted_table_map; - typedef map<uname, semantics::data_member*> deleted_column_map; - - namespace - { - // - // diff - // - - struct diff_table: trav_rel::column, - trav_rel::primary_key, - trav_rel::foreign_key, - trav_rel::index - { - enum mode_type {mode_add, mode_drop}; - - diff_table (table& o, - mode_type m, - alter_table& a, - graph& gr, - options const& op, - model_version const* v) - : other (o), mode (m), at (a), g (gr), ops (op), version (v) {} - - virtual void - traverse (sema_rel::column& c) - { - using sema_rel::column; - - if (mode == mode_add) - { - if (column* oc = other.find<column> (c.name ())) - { - if (c.type () != oc->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_column ( - c, "default value", oc->default_ (), c.default_ ()); - - if (c.options () != oc->options ()) - diagnose_column (c, "options", oc->options (), c.options ()); - } - else - { - if (version != 0) - { - data_member_path const& mp ( - c.get<data_member_path> ("member-path")); - - semantics::data_member* m (context::added_member (mp)); - if (m != 0) - { - // Make sure the addition version is the current version. - // - if (context::added (*m) != version->current) - { - location l (m->get<location_t> ("added-location")); - error (l) << "member addition version is not the same " << - "as the current model version" << endl; - throw operation_failed (); - } - } - // Warn about hard additions. If the version is closed, then - // we have already seen these warnings so no need to repeat - // them. - // - else if (ops.warn_hard_add () && version->open) - { - // Issue nicer diagnostics for the direct data member case. - // - if (mp.size () == 1) - { - location l (mp.back ()->location ()); - warn (l) << "data member is hard-added" << endl; - } - else - { - semantics::class_& s ( - dynamic_cast<semantics::class_&> ( - mp.front ()->scope ())); - - warn (s.location ()) << "column '" << c.name () << "' " << - "in class '" << s.name () << "' is hard-added" << endl; - - for (data_member_path::const_iterator i (mp.begin ()); - i != mp.end (); ++i) - { - info ((*i)->location ()) << "corresponding hard-" << - "added data member could be '" << (*i)->name () << - "'" << endl; - } - } - } - } - - add_column& ac (g.new_node<add_column> (c, at, g)); - g.new_edge<unames> (at, ac, c.name ()); - } - } - else - { - if (other.find<column> (c.name ()) == 0) - { - if (version != 0) - { - // See if we have an entry for this column in the soft- - // deleted map. - // - deleted_column_map const& dm ( - other.get<deleted_column_map> ("deleted-map")); - deleted_column_map::const_iterator i (dm.find (c.name ())); - - if (i != dm.end ()) - { - if (context::deleted (*i->second) != version->current) - { - location l (i->second->get<location_t> ("deleted-location")); - error (l) << "member deletion version is not the same " << - "as the current model version" << endl; - throw operation_failed (); - } - } - // Warn about hard deletions. If the version is closed, then - // we have already seen these warnings so no need to repeat - // them. - // - else if (ops.warn_hard_delete () && version->open) - { - // Here all we have is a column name and a class (object) - // or data member (container) corresponding to the table. - // - if (semantics::class_* s = - other.get<semantics::class_*> ("class", 0)) - { - warn (s->location ()) << "column '" << c.name () << "' " << - "in class '" << s->name () << "' is hard-deleted" << endl; - } - else - { - data_member_path const& mp ( - other.get<data_member_path> ("member-path")); - - warn (mp.back ()->location ()) << "column '" << - c.name () << "' in container '" << - mp.back ()->name () << "' is hard-deleted" << endl; - } - } - } - - drop_column& dc (g.new_node<drop_column> (c.id ())); - g.new_edge<unames> (at, dc, c.name ()); - } - } - } - - 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"); - - // Database-specific information. - // - for (primary_key::extra_map::const_iterator i ( - pk.extra ().begin ()); i != pk.extra ().end (); ++i) - { - if (opk->extra ().count (i->first) == 0 || - opk->extra ()[i->first] != i->second) - diagnose_primary_key (pk, i->first.c_str ()); - } - - for (primary_key::extra_map::const_iterator i ( - opk->extra ().begin ()); i != opk->extra ().end (); ++i) - { - if (pk.extra ().count (i->first) == 0 || - pk.extra ()[i->first] != i->second) - diagnose_primary_key (pk, i->first.c_str ()); - } - - 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.deferrable () != ofk->deferrable ()) - diagnose_foreign_key (fk, "deferrable mode"); - - if (fk.on_delete () != ofk->on_delete ()) - diagnose_foreign_key (fk, "on delete action"); - - if (fk.referenced_table () != ofk->referenced_table ()) - // See diagnose_foreign_key() if changing this name. - // - 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_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; - - 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 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) - { - // This can be an object pointer or a polymorphic base link. - // The latter will trigger this call if we change one base - // to another. - // - using sema_rel::table; - using sema_rel::foreign_key; - - // Polymorphic base link is the first foreign key. - // - table& t (fk.table ()); - table::names_iterator p (t.find (fk.name ())); - - if (t.extra ()["kind"] == "polymorphic derived object" && - (p == t.names_begin () || !(--p)->is_a<foreign_key> ())) - { - location const& l (t.get<location> ("cxx-location")); - - if (name == string ("pointed-to class")) - { - error (l) << "changing polymorphic base is not " << - "supported" << endl; - info (l) << "consider re-implementing this change by adding " << - "a new derived class with the desired base, migrating the " << - "data, and deleteing the old class" << endl; - } - else - { - error (l) << "changing polymorphic base " << name << - " is not supported" << endl; - info (l) << "consider re-implementing this change by adding " << - "a new derived class with the desired " << name << ", " << - "migrating the data, and deleteing the old class" << endl; - } - } - else - { - 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; - alter_table& at; - graph& g; - options const& ops; - model_version const* version; - }; - - struct diff_model: trav_rel::table - { - enum mode_type {mode_add, mode_drop}; - - diff_model (model& o, - mode_type m, - changeset& s, - graph& gr, - string const& in, - options const& op, - model_version const* v) - : other (o), - mode (m), - cs (s), - g (gr), - in_name (in), - ops (op), - version (v) {} - - virtual void - traverse (sema_rel::table& t) - { - using sema_rel::table; - - if (mode == mode_add) - { - if (table* ot = other.find<table> (t.name ())) - { - // See if there are any changes to the table. - // - 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)); - - if (t.options () != ot->options ()) - diagnose_table (t, "options", ot->options (), t.options ()); - - if (ot->extra ()["kind"] != t.extra ()["kind"]) - diagnose_table (t, "kind", - ot->extra ()["kind"], t.extra ()["kind"]); - - { - trav_rel::table table; - trav_rel::unames names; - diff_table dtable ( - *ot, diff_table::mode_add, at, g, ops, version); - table >> names >> dtable; - table.traverse (t); - } - - { - trav_rel::table table; - trav_rel::unames names; - diff_table dtable ( - t, diff_table::mode_drop, at, g, ops, version); - table >> names >> dtable; - table.traverse (*ot); - } - - if (!at.names_empty ()) - g.new_edge<qnames> (cs, at, t.name ()); - else - { - g.delete_edge (at, *bt, ae); - g.delete_node (at); - } - } - else - { - // Soft-add is only applicable to containers. - // - if (version != 0 && t.count ("member-path")) - { - data_member_path const& mp ( - t.get<data_member_path> ("member-path")); - - // Make sure the addition version is the current version. - // - semantics::data_member* m (context::added_member (mp)); - if (m != 0) - { - if (context::added (*m) != version->current) - { - location l (m->get<location_t> ("added-location")); - error (l) << "member addition version is not the same " << - "as the current model version" << endl; - throw operation_failed (); - } - } - // Warn about hard additions. If the version is closed, then - // we have already seen these warnings so no need to repeat - // them. - // - else if (ops.warn_hard_add () && version->open) - { - // Issue nicer diagnostics for the direct data member case. - // - if (mp.size () == 1) - { - location l (mp.back ()->location ()); - warn (l) << "data member is hard-added" << endl; - } - else - { - semantics::class_& s ( - dynamic_cast<semantics::class_&> ( - mp.front ()->scope ())); - - warn (s.location ()) << "container table '" << - t.name () << "' in class '" << s.name () << "' is " << - "hard-added" << endl; - - for (data_member_path::const_iterator i (mp.begin ()); - i != mp.end (); ++i) - { - info ((*i)->location ()) << "corresponding hard-" << - "added data member could be '" << (*i)->name () << - "'" << endl; - } - } - } - } - - add_table& at (g.new_node<add_table> (t, cs, g)); - g.new_edge<qnames> (cs, at, t.name ()); - } - } - else - { - if (other.find<table> (t.name ()) == 0) - { - if (version != 0) - { - // See if we have an entry for this table in the soft- - // deleted map. - // - deleted_table_map const& dm ( - other.get<deleted_table_map> ("deleted-map")); - deleted_table_map::const_iterator i (dm.find (t.name ())); - - if (i != dm.end ()) - { - // This table could be derived either from a class (object) - // or data member (container). - // - semantics::class_* c ( - dynamic_cast<semantics::class_*> (i->second)); - if (c != 0 && context::deleted (*c) != version->current) - { - location l (c->get<location_t> ("deleted-location")); - error (l) << "class deletion version is not the same " << - "as the current model version" << endl; - throw operation_failed (); - } - - semantics::data_member* m ( - dynamic_cast<semantics::data_member*> (i->second)); - if (m != 0 && context::deleted (*m) != version->current) - { - location l (m->get<location_t> ("deleted-location")); - error (l) << "member deletion version is not the same " << - "as the current model version" << endl; - throw operation_failed (); - } - } - // Warn about hard deletions. If the version is closed, then - // we have already seen these warnings so no need to repeat - // them. - // - // At first, it may seem like a good idea not to warn about - // class deletions since it is not possible to do anything - // useful with a class without referencing it from the code - // (in C++ sense). However, things get tricky once we consider - // polymorphism since the migration code could be "working" - // with the hard-deleted derived class via its base. So we - // are going to warn about polymorphic derived tables. - // - else if (ops.warn_hard_delete () && version->open) - { - string k (t.extra ()["kind"]); - - // We don't have any sensible location. - // - if (k == "container") - { - // We will issue a useless warning if the object table - // that this container references is also deleted. - // While we could detect this, it is going to require - // some effort since we would have to delay the warning - // and check later once we know all the deleted tables. - // - cerr << in_name << ": warning: container table '" << - t.name () << "' is hard-deleted" << endl; - } - else if (k == "polymorphic derived object") - { - // The same as above: the warning will be useless if - // we are dropping the whole hierarchy. - // - cerr << in_name << ": warning: polymorphic derived " << - "object table '" << t.name () << "' is hard-deleted" << - endl; - } - } - } - - drop_table& dt (g.new_node<drop_table> (t.id ())); - g.new_edge<qnames> (cs, dt, t.name ()); - } - } - } - - void - diagnose_table (sema_rel::table& t, - char const* name, - string const& ov, - string const& nv) - { - location const& tl (t.get<location> ("cxx-location")); - - error (tl) << "change to object or container member results in " - "the change of the corresponding table " << name; - - if (!ov.empty () || !nv.empty ()) - cerr << " (old: '" << ov << "', new: '" << nv << "')"; - - cerr << endl; - - error (tl) << "this change is not yet handled automatically" << endl; - info (tl) << "consider re-implementing this change by adding a " << - "new object or container member with the desired " << name << - ", migrating the data, and deleting the old object or member" << - endl; - - throw operation_failed (); - } - - protected: - model& other; - mode_type mode; - changeset& cs; - graph& g; - string in_name; - options const& ops; - model_version const* version; - }; - - // Assumes the new model has cxx-location set. If version is not 0, - // then assume it is the current model version and the new model is - // the current model which has member paths and deleted maps set. - // - changeset& - diff (model& o, - model& n, - changelog& l, - string const& in_name, - options const& ops, - model_version const* version) - { - changeset& r (l.new_node<changeset> (n.version ())); - - // Set the alters edge for lookup. If we are diff'ing two models of - // the same version, then use the old model as a base. Otherwise use - // the tip of changelog (it should correspond to the old model). - // - if (o.version () == n.version ()) - l.new_edge<alters> (r, o); - else - { - if (l.contains_changeset_empty ()) - { - model& m (l.model ()); - - // The changelog model version may also be equal to the new model - // version if the new base model version is greater than the - // latest changeset. - // - assert (m.version () == o.version () || - m.version () == n.version ()); - - l.new_edge<alters> (r, m); - } - else - { - changeset& c (l.contains_changeset_back ().changeset ()); - assert (o.version () == c.version ()); - l.new_edge<alters> (r, c); - } - } - - { - trav_rel::model model; - trav_rel::qnames names; - diff_model dmodel ( - o, diff_model::mode_add, r, l, in_name, ops, version); - model >> names >> dmodel; - model.traverse (n); - } - - { - trav_rel::model model; - trav_rel::qnames names; - diff_model dmodel ( - n, diff_model::mode_drop, r, l, in_name, ops, version); - model >> names >> dmodel; - model.traverse (o); - } - - return r; - } - - // - // patch - // - - struct patch_table: trav_rel::add_column, - trav_rel::drop_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) {} - - virtual void - traverse (sema_rel::add_column& ac) - { - try - { - column& c (g.new_node<column> (ac, t, g)); - g.new_edge<unames> (t, c, ac.name ()); - } - catch (duplicate_name const&) - { - cerr << "error: invalid changelog: column '" << ac.name () << - "' already exists in table '" << t.name () << "'" << endl; - throw operation_failed (); - } - } - - virtual void - traverse (sema_rel::drop_column& dc) - { - table::names_iterator i (t.find (dc.name ())); - - if (i == t.names_end () || !i->nameable ().is_a<column> ()) - { - cerr << "error: invalid changelog: column '" << dc.name () << - "' does not exist in table '" << t.name () << "'" << endl; - throw operation_failed (); - } - - g.delete_edge (t, i->nameable (), *i); - } - - virtual void - traverse (sema_rel::alter_column& ac) - { - if (column* c = t.find<column> (ac.name ())) - { - if (ac.null_altered ()) - c->null (ac.null ()); - } - else - { - cerr << "error: invalid changelog: column '" << ac.name () << - "' does not exist in table '" << t.name () << "'" << endl; - throw operation_failed (); - } - } - - 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; - }; - - struct patch_model: trav_rel::add_table, - trav_rel::drop_table, - trav_rel::alter_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); - } - - virtual void - traverse (sema_rel::alter_table& at) - { - if (table* t = m.find<table> (at.name ())) - { - trav_rel::alter_table atable; - trav_rel::unames names; - patch_table ptable (*t, g); - atable >> names >> ptable; - atable.traverse (at); - } - else - { - cerr << "error: invalid changelog: table '" << at.name () << - "' does not exist in model version " << m.version () << endl; - throw operation_failed (); - } - } - - 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& in_name, - string const& out_name, - options const& ops) - { - database db (ops.database ()[0]); - cutl::shared_ptr<changelog> cl ( - new (shared) changelog (db.string (), ops.schema_name ()[db])); - graph& g (*cl); - - if (old == 0) - { - // Don't allow changelog initialization if the version is closed. - // This will prevent adding new files to an existing object model - // with a closed version. - // - if (!mv.open) - { - cerr << out_name << ": error: unable to initialize changelog " << - "because current version is closed" << endl; - throw operation_failed (); - } - - if (!ops.init_changelog ()) - cerr << out_name << ": info: initializing changelog with base " << - "version " << mv.base << endl; - - if (mv.base == mv.current) - g.new_edge<contains_model> (*cl, g.new_node<model> (m, g)); - else - { - // In this case we have to create an empty model at the base - // version and a changeset. We do it this way instead of putting - // everything into the base model in order to support adding new - // header files to the project. - // - cerr << out_name << ": warning: base and current versions " << - "differ; assuming base model is empty" << endl; - - model& nm (g.new_node<model> (mv.base)); - g.new_edge<contains_model> (*cl, nm); - changeset& c (diff (nm, m, *cl, in_name, ops, &mv)); - - if (!c.names_empty ()) - { - g.new_edge<alters_model> (c, nm); - g.new_edge<contains_changeset> (*cl, c); - } - } - - 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.base < bver) - { - cerr << in_name << ": error: latest changelog base version is " << - "greater than model base version" << endl; - throw operation_failed (); - } - - if (mv.current < cver) - { - cerr << in_name << ": error: latest changelog current version is " << - "greater than model current version" << endl; - throw operation_failed (); - } - - // Build the new changelog. - // - model& oldm (old->model ()); - - // 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* last (&g.new_node<model> (oldm, g)); - model* base (bver == mv.base && mv.base != mv.current ? last : 0); - if (base != 0) - g.new_edge<contains_model> (*cl, *base); - - 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) - break; - - model& prev (*last); - last = &patch (prev, cs, g); - - if (base == 0) - { - if (last->version () == mv.base) - { - base = last; - g.new_edge<contains_model> (*cl, *base); - } - else if (last->version () > mv.base) - { - // We have a gap. Plug it with an empty base model. We will - // also need to create a new changeset for this step. - // - base = &g.new_node<model> (mv.base); - g.new_edge<contains_model> (*cl, *base); - - changeset& c (diff (*base, *last, *cl, in_name, ops, 0)); - if (!c.names_empty ()) - { - g.new_edge<alters_model> (c, *base); - g.new_edge<contains_changeset> (*cl, c); - } - - continue; - } - } - - // Copy the changeset unless it is below or at our base version. - // - if (last->version () <= mv.base) - continue; - - 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<alters_model> (c, prev); - g.new_edge<contains_changeset> (*cl, c); - } - - // If we still haven't found the new base model, then it means it - // has version greater than any changeset we have seen. - // - if (base == 0) - { - if (mv.base == mv.current) - base = &g.new_node<model> (m, g); - else - { - // Fast-forward the latest model to the new base. - // - base = last; - base->version (mv.base); - } - - g.new_edge<contains_model> (*cl, *base); - } - - // If the current version is closed, make sure the model hasn't - // changed. - // - if (!mv.open) - { - // If the last changeset has the current version, then apply it. - // - model* om (last); - if (!old->contains_changeset_empty ()) - { - changeset& c (old->contains_changeset_back ().changeset ()); - if (c.version () == mv.current) - om = &patch (*last, c, g); - } - - changeset& c (diff (*om, m, *cl, in_name, ops, &mv)); - - if (!c.names_empty ()) - { - qnames& n (*c.names_begin ()); - - cerr << out_name << ": error: current version is closed" << endl; - cerr << out_name << ": info: first new change is " << - n.nameable ().kind () << " '" << n.name () << "'" << endl; - - throw operation_failed (); - } - } - - // Add a changeset for the current version unless it is the same - // as the base version. - // - if (mv.base != mv.current) - { - // Add it even if it is empty. This can be useful, for example, - // for data-only migrations were the user relies on the database - // version being updated in the version table. - // - changeset& c (diff (*last, m, *cl, in_name, ops, &mv)); - g.new_edge<alters_model> (c, *last); - g.new_edge<contains_changeset> (*cl, c); - } - - return cl; - } - } -} |