summaryrefslogtreecommitdiff
path: root/odb/relational/changelog.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'odb/relational/changelog.cxx')
-rw-r--r--odb/relational/changelog.cxx1240
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;
- }
- }
-}