summaryrefslogtreecommitdiff
path: root/odb/odb/relational/schema.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'odb/odb/relational/schema.hxx')
-rw-r--r--odb/odb/relational/schema.hxx1606
1 files changed, 1606 insertions, 0 deletions
diff --git a/odb/odb/relational/schema.hxx b/odb/odb/relational/schema.hxx
new file mode 100644
index 0000000..cd975b7
--- /dev/null
+++ b/odb/odb/relational/schema.hxx
@@ -0,0 +1,1606 @@
+// file : odb/relational/schema.hxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#ifndef ODB_RELATIONAL_SCHEMA_HXX
+#define ODB_RELATIONAL_SCHEMA_HXX
+
+#include <set>
+#include <vector>
+#include <cassert>
+
+#include <odb/emitter.hxx>
+#include <odb/relational/common.hxx>
+#include <odb/relational/context.hxx>
+
+namespace relational
+{
+ namespace schema
+ {
+ typedef std::set<qname> table_set;
+
+ struct common: virtual context
+ {
+ typedef ::emitter emitter_type;
+
+ common (emitter_type& e, ostream& os, schema_format f)
+ : e_ (e), os_ (os), format_ (f) {}
+
+ void
+ pre_statement ()
+ {
+ e_.pre ();
+ diverge (os_);
+ }
+
+ void
+ post_statement ()
+ {
+ restore ();
+ e_.post ();
+ }
+
+ emitter_type&
+ emitter () const
+ {
+ return e_;
+ }
+
+ ostream&
+ stream () const
+ {
+ return os_;
+ }
+
+ public:
+ // Find an entity corresponding to the drop node in alter_table.
+ //
+ template <typename T, typename D>
+ T&
+ find (D& d)
+ {
+ using sema_rel::model;
+ using sema_rel::changeset;
+ using sema_rel::table;
+ using sema_rel::alter_table;
+
+ alter_table& at (dynamic_cast<alter_table&> (d.scope ()));
+ changeset& cs (dynamic_cast<changeset&> (at.scope ()));
+ model& bm (cs.base_model ());
+ table* bt (bm.find<table> (at.name ()));
+ assert (bt != 0);
+ T* b (bt->find<T> (d.name ()));
+ assert (b != 0);
+ return *b;
+ }
+
+ protected:
+ emitter_type& e_;
+ ostream& os_;
+ schema_format format_;
+ };
+
+ //
+ // Drop.
+ //
+
+ // Only used in migration.
+ //
+ struct drop_column: trav_rel::drop_column, common
+ {
+ typedef drop_column base;
+
+ drop_column (common const& c, bool* first = 0)
+ : common (c),
+ first_ (first != 0 ? *first : first_data_),
+ first_data_ (true)
+ {
+ }
+
+ drop_column (drop_column const& c)
+ : root_context (), // @@ -Wextra
+ context (),
+ common (c),
+ first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_),
+ first_data_ (c.first_data_)
+ {
+ }
+
+ virtual void
+ drop_header ()
+ {
+ // By default ADD COLUMN though some databases use just ADD.
+ //
+ os << "DROP COLUMN ";
+ }
+
+ virtual void
+ traverse (sema_rel::drop_column& dc)
+ {
+ if (first_)
+ first_ = false;
+ else
+ os << ",";
+
+ os << endl
+ << " ";
+ drop_header ();
+ os << quote_id (dc.name ());
+ }
+
+ protected:
+ bool& first_;
+ bool first_data_;
+ };
+
+ // Normally only used in migration but some databases use it as a
+ // base to also drop foreign keys in schema.
+ //
+ struct drop_foreign_key: trav_rel::foreign_key,
+ trav_rel::drop_foreign_key,
+ trav_rel::add_foreign_key, // Override.
+ common
+ {
+ typedef drop_foreign_key base;
+
+ // Schema constructor.
+ //
+ drop_foreign_key (common const& c, table_set& dropped, bool* first = 0)
+ : common (c),
+ dropped_ (&dropped),
+ first_ (first != 0 ? *first : first_data_),
+ first_data_ (true)
+ {
+ }
+
+ // Migration constructor.
+ //
+ drop_foreign_key (common const& c, bool* first = 0)
+ : common (c),
+ dropped_ (0),
+ first_ (first != 0 ? *first : first_data_),
+ first_data_ (true)
+ {
+ }
+
+ drop_foreign_key (drop_foreign_key const& c)
+ : root_context (), // @@ -Wextra
+ context (),
+ common (c),
+ dropped_ (c.dropped_),
+ first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_),
+ first_data_ (c.first_data_)
+ {
+ }
+
+ virtual void
+ drop_header ()
+ {
+ os << "DROP CONSTRAINT ";
+ }
+
+ virtual void
+ traverse (sema_rel::foreign_key& fk)
+ {
+ // If the table which we reference is droped before us, then
+ // we need to drop the constraint first. Similarly, if the
+ // referenced table is not part if this model, then assume
+ // it is dropped before us. In migration we always do this
+ // first.
+ //
+ sema_rel::table& t (dynamic_cast<sema_rel::table&> (fk.scope ()));
+
+ if (dropped_ != 0)
+ {
+ sema_rel::qname const& rt (fk.referenced_table ());
+ sema_rel::model& m (dynamic_cast<sema_rel::model&> (t.scope ()));
+
+ if (dropped_->find (rt) == dropped_->end () &&
+ m.find (rt) != m.names_end ())
+ return;
+ }
+
+ drop (t, fk);
+ }
+
+ virtual void
+ drop (sema_rel::table& t, sema_rel::foreign_key& fk)
+ {
+ // When generating schema we would need to check if the key exists.
+ // So this implementation will need to be customize on the per-
+ // database level.
+ //
+ pre_statement ();
+
+ os << "ALTER TABLE " << quote_id (t.name ()) << endl
+ << " ";
+ drop_header ();
+ os << quote_id (fk.name ()) << endl;
+
+ post_statement ();
+ }
+
+ virtual void
+ traverse (sema_rel::drop_foreign_key& dfk)
+ {
+ if (first_)
+ first_ = false;
+ else
+ os << ",";
+
+ os << endl;
+ drop (dfk);
+ }
+
+ virtual void
+ drop (sema_rel::drop_foreign_key& dfk)
+ {
+ os << " ";
+ drop_header ();
+ os << quote_id (dfk.name ());
+ }
+
+ protected:
+ table_set* dropped_;
+ bool& first_;
+ bool first_data_;
+ };
+
+ // Currently only used in migration.
+ //
+ struct drop_index: trav_rel::drop_index, common
+ {
+ typedef drop_index base;
+
+ enum index_type {unique, non_unique, all};
+
+ drop_index (common const& c, index_type t = all)
+ : common (c), type_ (t) {}
+
+ virtual void
+ traverse (sema_rel::drop_index& di)
+ {
+ // Find the index we are dropping in the base model.
+ //
+ traverse (find<sema_rel::index> (di));
+ }
+
+ virtual void
+ traverse (sema_rel::index& in)
+ {
+ if (type_ == unique &&
+ in.type ().find ("UNIQUE") == string::npos &&
+ in.type ().find ("unique") == string::npos)
+ return;
+
+ if (type_ == non_unique && (
+ in.type ().find ("UNIQUE") != string::npos ||
+ in.type ().find ("unique") != string::npos))
+ return;
+
+ pre_statement ();
+ drop (in);
+ post_statement ();
+ }
+
+ virtual string
+ name (sema_rel::index& in)
+ {
+ return quote_id (in.name ());
+ }
+
+ virtual void
+ drop (sema_rel::index& in)
+ {
+ os << "DROP INDEX " << name (in) << endl;
+ }
+
+ protected:
+ index_type type_;
+ };
+
+ struct drop_table: trav_rel::table,
+ trav_rel::drop_table,
+ trav_rel::add_table, // Override.
+ trav_rel::alter_table, // Override.
+ common
+ {
+ typedef drop_table base;
+
+ drop_table (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os, f) {}
+
+ virtual void
+ drop (sema_rel::table& t, bool migration)
+ {
+ pre_statement ();
+ os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") <<
+ quote_id (t.name ()) << endl;
+ post_statement ();
+ }
+
+ virtual void
+ delete_ (sema_rel::qname const& rtable,
+ sema_rel::qname const& dtable,
+ sema_rel::primary_key& rkey,
+ sema_rel::primary_key& dkey)
+ {
+ pre_statement ();
+
+ // This might not be the most efficient way for every database.
+ //
+ os << "DELETE FROM " << quote_id (rtable) << endl
+ << " WHERE EXISTS (SELECT 1 FROM " << quote_id (dtable) << endl
+ << " WHERE ";
+
+ for (size_t i (0); i != rkey.contains_size (); ++i)
+ {
+ if (i != 0)
+ os << endl
+ << " AND ";
+
+ os << quote_id (rtable) << "." <<
+ quote_id (rkey.contains_at (i).column ().name ()) << " = " <<
+ quote_id (dtable) << "." <<
+ quote_id (dkey.contains_at (i).column ().name ());
+ }
+
+ os << ")" << endl;
+
+ post_statement ();
+ }
+
+ virtual void
+ traverse (sema_rel::table& t, bool migration)
+ {
+ // By default drop foreign keys referencing tables that would
+ // have already been dropped on the first pass.
+ //
+ if (pass_ == 1)
+ {
+ // Drop constraints. In migration this is always done on pass 1.
+ //
+ if (migration)
+ {
+ instance<drop_foreign_key> dfk (*this);
+ trav_rel::unames n (*dfk);
+ names (t, n);
+ }
+ else
+ {
+ dropped_.insert (t.name ()); // Add it before to cover self-refs.
+
+ instance<drop_foreign_key> dfk (*this, dropped_);
+ trav_rel::unames n (*dfk);
+ names (t, n);
+ }
+ }
+ else
+ {
+ if (migration && t.extra ()["kind"] == "polymorphic derived object")
+ {
+ // If we are dropping a polymorphic derived object, then we
+ // also have to clean the base tables. Note that this won't
+ // trigger cascade deletion since we have dropped all the
+ // keys on pass 1. But we still need to do this in the base
+ // to root order in order not to trigger other cascades.
+ //
+ using sema_rel::model;
+ using sema_rel::table;
+ using sema_rel::primary_key;
+ using sema_rel::foreign_key;
+
+ model& m (dynamic_cast<model&> (t.scope ()));
+
+ table* p (&t);
+ do
+ {
+ // The polymorphic link is the first primary key.
+ //
+ for (table::names_iterator i (p->names_begin ());
+ i != p->names_end (); ++i)
+ {
+ if (foreign_key* fk = dynamic_cast<foreign_key*> (
+ &i->nameable ()))
+ {
+ p = m.find<table> (fk->referenced_table ());
+ assert (p != 0); // Base table should be there.
+ break;
+ }
+ }
+
+ primary_key& rkey (*p->find<primary_key> (""));
+ primary_key& dkey (*t.find<primary_key> (""));
+ assert (rkey.contains_size () == dkey.contains_size ());
+ delete_ (p->name (), t.name (), rkey, dkey);
+ }
+ while (p->extra ()["kind"] != "polymorphic root object");
+ }
+
+ drop (t, migration);
+ }
+ }
+
+ virtual void
+ traverse (sema_rel::table& t)
+ {
+ traverse (t, false);
+ }
+
+ virtual void
+ traverse (sema_rel::drop_table& dt)
+ {
+ using sema_rel::model;
+ using sema_rel::changeset;
+ using sema_rel::table;
+
+ // Find the table we are dropping in the base model.
+ //
+ changeset& cs (dynamic_cast<changeset&> (dt.scope ()));
+ model& bm (cs.base_model ());
+ table* t (bm.find<table> (dt.name ()));
+ assert (t != 0);
+ traverse (*t, true);
+ }
+
+ using add_table::traverse; // Unhide.
+ using alter_table::traverse; // Unhide.
+
+ using table::names;
+
+ void
+ pass (unsigned short p)
+ {
+ pass_ = p;
+ }
+
+ protected:
+ unsigned short pass_;
+ table_set dropped_;
+ };
+
+ struct drop_model: trav_rel::model, common
+ {
+ typedef drop_model base;
+
+ drop_model (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os, f)
+ {
+ }
+
+ virtual void
+ traverse (sema_rel::model& m)
+ {
+ // Traverse named entities in the reverse order. This way we
+ // drop them in the order opposite to creating.
+ //
+ for (sema_rel::model::names_iterator begin (m.names_begin ()),
+ end (m.names_end ()); begin != end;)
+ dispatch (*--end);
+ }
+
+ void
+ pass (unsigned short p)
+ {
+ pass_ = p;
+ }
+
+ protected:
+ unsigned short pass_;
+ };
+
+ //
+ // Create.
+ //
+
+ struct create_column: trav_rel::column,
+ trav_rel::add_column,
+ trav_rel::alter_column, // Override.
+ common
+ {
+ typedef create_column base;
+
+ create_column (common const& c,
+ bool override_null = true,
+ bool* first = 0)
+ : common (c),
+ override_null_ (override_null),
+ first_ (first != 0 ? *first : first_data_),
+ first_data_ (true)
+ {
+ }
+
+ create_column (create_column const& c)
+ : root_context (), // @@ -Wextra
+ context (),
+ common (c),
+ override_null_ (c.override_null_),
+ first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_),
+ first_data_ (c.first_data_)
+ {
+ }
+
+ virtual void
+ traverse (sema_rel::column& c)
+ {
+ if (first_)
+ first_ = false;
+ else
+ os << ",";
+
+ os << endl
+ << " ";
+ create (c);
+ }
+
+ virtual void
+ add_header ()
+ {
+ // By default ADD COLUMN though some databases use just ADD.
+ //
+ os << "ADD COLUMN ";
+ }
+
+ virtual void
+ traverse (sema_rel::add_column& ac)
+ {
+ if (first_)
+ first_ = false;
+ else
+ os << ",";
+
+ os << endl
+ << " ";
+ add_header ();
+ create (ac);
+ }
+
+ virtual void
+ create (sema_rel::column& c)
+ {
+ using sema_rel::column;
+
+ // See if this column is (part of) a primary key.
+ //
+ sema_rel::primary_key* pk (0);
+
+ for (column::contained_iterator i (c.contained_begin ());
+ i != c.contained_end ();
+ ++i)
+ {
+ if ((pk = dynamic_cast<sema_rel::primary_key*> (&i->key ())))
+ break;
+ }
+
+ os << quote_id (c.name ()) << " ";
+
+ type (c, pk != 0 && pk->auto_ ());
+ constraints (c, pk);
+
+ if (!c.options ().empty ())
+ os << " " << c.options ();
+ }
+
+ virtual void
+ constraints (sema_rel::column& c, sema_rel::primary_key* pk)
+ {
+ null (c);
+
+ if (!c.default_ ().empty ())
+ os << " DEFAULT " << c.default_ ();
+
+ // If this is a single-column primary key, generate it inline.
+ //
+ if (pk != 0 && pk->contains_size () == 1)
+ primary_key ();
+
+ if (pk != 0 && pk->auto_ ())
+ auto_ (*pk);
+ }
+
+ virtual void
+ type (sema_rel::column& c, bool /*auto*/)
+ {
+ os << c.type ();
+ }
+
+ virtual void
+ null (sema_rel::column& c)
+ {
+ bool n (c.null ());
+
+ // If we are adding a new column that doesn't allow NULL nor has
+ // a default value, add it as NULL. Later, after migration, we
+ // will convert it to NOT NULL.
+ //
+ if (override_null_ && c.is_a<sema_rel::add_column> () &&
+ !n && c.default_ ().empty ())
+ n = true;
+
+ // Specify both cases explicitly for better readability,
+ // especially in ALTER COLUMN clauses.
+ //
+ os << (n ? " NULL" : " NOT NULL");
+ }
+
+ virtual void
+ primary_key ()
+ {
+ os << " PRIMARY KEY";
+ }
+
+ virtual void
+ auto_ (sema_rel::primary_key&)
+ {
+ }
+
+ protected:
+ bool override_null_; // Override NOT NULL in add_column.
+ bool& first_;
+ bool first_data_;
+ bool add_;
+ };
+
+ struct create_primary_key: trav_rel::primary_key, common
+ {
+ typedef create_primary_key base;
+
+ create_primary_key (common const& c): common (c) {}
+
+ virtual void
+ traverse (sema_rel::primary_key& pk)
+ {
+ // Single-column primary keys are generated inline in the
+ // column declaration.
+ //
+ if (pk.contains_size () == 1)
+ return;
+
+ // We will always follow a column.
+ //
+ os << "," << endl;
+
+ create (pk);
+ }
+
+ virtual void
+ create (sema_rel::primary_key& pk)
+ {
+ using sema_rel::primary_key;
+
+ // By default we create unnamed primary key constraint.
+ //
+
+ os << " PRIMARY KEY (";
+
+ for (primary_key::contains_iterator i (pk.contains_begin ());
+ i != pk.contains_end ();
+ ++i)
+ {
+ if (i != pk.contains_begin ())
+ os << "," << endl
+ << " ";
+
+ os << quote_id (i->column ().name ());
+ }
+
+ os << ")";
+ }
+ };
+
+ struct create_foreign_key: trav_rel::foreign_key,
+ trav_rel::add_foreign_key,
+ common
+ {
+ typedef create_foreign_key base;
+
+ // Schema constructor, pass 1.
+ //
+ create_foreign_key (common const& c, table_set& created, bool* first = 0)
+ : common (c),
+ created_ (&created),
+ first_ (first != 0 ? *first : first_data_),
+ first_data_ (true)
+ {
+ }
+
+ // Schema constructor, pass 2 and migration constructor.
+ //
+ create_foreign_key (common const& c, bool* first = 0)
+ : common (c),
+ created_ (0),
+ first_ (first != 0 ? *first : first_data_),
+ first_data_ (true)
+ {
+ }
+
+ create_foreign_key (create_foreign_key const& c)
+ : root_context (), // @@ -Wextra
+ context (),
+ common (c),
+ created_ (c.created_),
+ first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_),
+ first_data_ (c.first_data_)
+ {
+ }
+
+ virtual void
+ traverse (sema_rel::foreign_key& fk)
+ {
+ if (created_ != 0)
+ {
+ // Pass 1.
+ //
+ // If the referenced table has already been defined, do the
+ // foreign key definition in the table definition. Otherwise
+ // postpone it until pass 2 where we do it via ALTER TABLE.
+ //
+ if (created_->find (fk.referenced_table ()) != created_->end ())
+ {
+ traverse_create (fk);
+ fk.set (db.string () + "-fk-defined", true); // Mark it as defined.
+ }
+ }
+ else
+ {
+ // Pass 2.
+ //
+ if (!fk.count (db.string () + "-fk-defined"))
+ traverse_add (fk);
+ }
+ }
+
+ virtual void
+ traverse_create (sema_rel::foreign_key& fk)
+ {
+ if (first_)
+ first_ = false;
+ else
+ os << ",";
+
+ os << endl
+ << " CONSTRAINT ";
+ create (fk);
+ }
+
+ virtual void
+ traverse_add (sema_rel::foreign_key& fk)
+ {
+ if (first_)
+ first_ = false;
+ else
+ os << ",";
+
+ os << endl;
+ add (fk);
+ }
+
+ virtual void
+ traverse (sema_rel::add_foreign_key& afk)
+ {
+ traverse_add (afk);
+ }
+
+ virtual void
+ add_header ()
+ {
+ os << "ADD CONSTRAINT ";
+ }
+
+ virtual void
+ add (sema_rel::foreign_key& fk)
+ {
+ os << " ";
+ add_header ();
+ create (fk);
+ }
+
+ virtual void
+ create (sema_rel::foreign_key& fk)
+ {
+ using sema_rel::foreign_key;
+
+ os << name (fk) << endl
+ << " FOREIGN KEY (";
+
+ for (foreign_key::contains_iterator i (fk.contains_begin ());
+ i != fk.contains_end ();
+ ++i)
+ {
+ if (i != fk.contains_begin ())
+ os << "," << endl
+ << " ";
+
+ os << quote_id (i->column ().name ());
+ }
+
+ string tn (table_name (fk));
+ string tn_pad (tn.size (), ' ');
+
+ os << ")" << endl
+ << " REFERENCES " << tn << " (";
+
+ foreign_key::columns const& refs (fk.referenced_columns ());
+ for (foreign_key::columns::const_iterator i (refs.begin ());
+ i != refs.end ();
+ ++i)
+ {
+ if (i != refs.begin ())
+ os << "," << endl
+ << " " << tn_pad;
+
+ os << quote_id (*i);
+ }
+
+ os << ")";
+
+ if (fk.on_delete () != foreign_key::no_action)
+ on_delete (fk.on_delete ());
+
+ if (!fk.not_deferrable ())
+ deferrable (fk.deferrable ());
+ }
+
+ virtual string
+ name (sema_rel::foreign_key& fk)
+ {
+ return quote_id (fk.name ());
+ }
+
+ virtual string
+ table_name (sema_rel::foreign_key& fk)
+ {
+ return quote_id (fk.referenced_table ());
+ }
+
+ virtual void
+ on_delete (sema_rel::foreign_key::action_type a)
+ {
+ using sema_rel::foreign_key;
+
+ switch (a)
+ {
+ case foreign_key::no_action:
+ break;
+ case foreign_key::cascade:
+ {
+ os << endl
+ << " ON DELETE CASCADE";
+ break;
+ }
+ case foreign_key::set_null:
+ {
+ os << endl
+ << " ON DELETE SET NULL";
+ break;
+ }
+ }
+ }
+
+ virtual void
+ deferrable (sema_rel::deferrable d)
+ {
+ os << endl
+ << " DEFERRABLE INITIALLY " << d;
+ }
+
+ protected:
+ table_set* created_;
+ bool& first_;
+ bool first_data_;
+ };
+
+ struct create_index: trav_rel::index, common
+ {
+ typedef create_index base;
+
+ enum index_type {unique, non_unique, all};
+
+ create_index (common const& c, index_type t = all)
+ : common (c), type_ (t) {}
+
+ virtual void
+ traverse (sema_rel::index& in)
+ {
+ if (type_ == unique &&
+ in.type ().find ("UNIQUE") == string::npos &&
+ in.type ().find ("unique") == string::npos)
+ return;
+
+ if (type_ == non_unique && (
+ in.type ().find ("UNIQUE") != string::npos ||
+ in.type ().find ("unique") != string::npos))
+ return;
+
+ pre_statement ();
+ create (in);
+ post_statement ();
+ }
+
+ virtual string
+ name (sema_rel::index& in)
+ {
+ return quote_id (in.name ());
+ }
+
+ virtual string
+ table_name (sema_rel::index& in)
+ {
+ return quote_id (static_cast<sema_rel::table&> (in.scope ()).name ());
+ }
+
+ virtual void
+ columns (sema_rel::index& in)
+ {
+ using sema_rel::index;
+
+ for (index::contains_iterator i (in.contains_begin ());
+ i != in.contains_end ();
+ ++i)
+ {
+ if (in.contains_size () > 1)
+ {
+ if (i != in.contains_begin ())
+ os << ",";
+
+ os << endl
+ << " ";
+ }
+
+ os << quote_id (i->column ().name ());
+
+ if (!i->options ().empty ())
+ os << ' ' << i->options ();
+ }
+ }
+
+ virtual void
+ create (sema_rel::index& in)
+ {
+ // Default implementation that ignores the method.
+ //
+ os << "CREATE ";
+
+ if (!in.type ().empty ())
+ os << in.type () << ' ';
+
+ os << "INDEX " << name (in) << endl
+ << " ON " << table_name (in) << " (";
+
+ columns (in);
+
+ os << ")" << endl;
+
+ if (!in.options ().empty ())
+ os << ' ' << in.options () << endl;
+ }
+
+ protected:
+ index_type type_;
+ };
+
+ struct create_table: trav_rel::table,
+ trav_rel::alter_table, // Override.
+ common
+ {
+ typedef create_table base;
+
+ using trav_rel::table::names;
+
+ create_table (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os, f) {}
+
+ virtual void
+ create_pre (sema_rel::qname const& table)
+ {
+ os << "CREATE TABLE " << quote_id (table) << " (";
+ }
+
+ virtual void
+ create_post (sema_rel::table& t)
+ {
+ os << ")" << endl;
+
+ if (!t.options ().empty ())
+ os << " " << t.options () << endl;
+ }
+
+ virtual void
+ create (sema_rel::table& t)
+ {
+ pre_statement ();
+ create_pre (t.name ());
+
+ instance<create_column> c (*this);
+ instance<create_primary_key> pk (*this);
+
+ // We will always follow a column, so set first to false.
+ //
+ bool f (false); // (Im)perfect forwarding.
+ bool* pf (&f); // (Im)perfect forwarding.
+ instance<create_foreign_key> fk (*this, created_, pf);
+
+ trav_rel::unames n;
+ n >> c;
+ n >> pk;
+ n >> fk;
+
+ names (t, n);
+
+ create_post (t);
+ post_statement ();
+
+ // Create indexes.
+ //
+ {
+ instance<create_index> in (*this);
+ trav_rel::unames n (*in);
+ names (t, n);
+ }
+ }
+
+ // See if there are any undefined foreign keys that we need to
+ // add with ALTER TABLE.
+ //
+ bool
+ check_undefined_fk (sema_rel::table& t)
+ {
+ for (sema_rel::table::names_iterator i (t.names_begin ());
+ i != t.names_end (); ++i)
+ {
+ if (i->nameable ().is_a<sema_rel::foreign_key> () &&
+ !i->nameable ().count (db.string () + "-fk-defined"))
+ return true;
+ }
+ return false;
+ }
+
+ virtual void
+ traverse (sema_rel::table& t)
+ {
+ // By default add foreign keys referencing tables that haven't
+ // yet been defined on the second pass.
+ //
+ if (pass_ == 1)
+ {
+ // In migration we always add foreign keys on pass 2.
+ //
+ if (!t.is_a<sema_rel::add_table> ())
+ created_.insert (t.name ()); // Add it before to cover self-refs.
+
+ create (t);
+ }
+ else
+ {
+ // Add undefined foreign keys.
+ //
+ if (check_undefined_fk (t))
+ {
+ pre_statement ();
+ os << "ALTER TABLE " << quote_id (t.name ());
+
+ instance<create_foreign_key> cfk (*this);
+ trav_rel::unames n (*cfk);
+ names (t, n);
+ os << endl;
+
+ post_statement ();
+ }
+ }
+ }
+
+ void
+ pass (unsigned short p)
+ {
+ pass_ = p;
+ }
+
+ protected:
+ unsigned short pass_;
+ table_set created_;
+ };
+
+ struct create_model: trav_rel::model, common
+ {
+ typedef create_model base;
+
+ create_model (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os, f) {}
+
+ void
+ pass (unsigned short p)
+ {
+ pass_ = p;
+ }
+
+ protected:
+ unsigned short pass_;
+ };
+
+ //
+ // Alter.
+ //
+
+ struct alter_column: trav_rel::alter_column,
+ trav_rel::add_column,
+ common
+ {
+ typedef alter_column base;
+
+ alter_column (common const& c, bool pre, bool* first = 0)
+ : common (c),
+ pre_ (pre),
+ first_ (first != 0 ? *first : first_data_),
+ first_data_ (true),
+ fl_ (false),
+ def_ (c, fl_)
+ {
+ }
+
+ alter_column (alter_column const& c)
+ : root_context (), // @@ -Wextra
+ context (),
+ common (c),
+ pre_ (c.pre_),
+ first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_),
+ first_data_ (c.first_data_),
+ fl_ (false),
+ def_ (c, fl_)
+ {
+ }
+
+ virtual void
+ alter_header ()
+ {
+ os << "ALTER COLUMN ";
+ }
+
+ virtual void
+ alter (sema_rel::column& c)
+ {
+ // By default use the whole definition.
+ //
+ def_->create (c);
+ }
+
+ virtual void
+ traverse (sema_rel::column& c)
+ {
+ // Relax (NULL) in pre and tighten (NOT NULL) in post.
+ //
+ if (pre_ != c.null ())
+ return;
+
+ if (first_)
+ first_ = false;
+ else
+ os << ",";
+
+ os << endl
+ << " ";
+ alter_header ();
+ alter (c);
+ }
+
+ virtual void
+ traverse (sema_rel::alter_column& ac)
+ {
+ assert (ac.null_altered ());
+ traverse (static_cast<sema_rel::column&> (ac));
+ }
+
+ virtual void
+ traverse (sema_rel::add_column& ac)
+ {
+ // We initially add NOT NULL columns without default values as
+ // NULL. Now, after the migration, we convert them to NOT NULL.
+ //
+ if (!ac.null () && ac.default_ ().empty ())
+ traverse (static_cast<sema_rel::column&> (ac));
+ }
+
+ protected:
+ bool pre_;
+ bool& first_;
+ bool first_data_;
+ bool fl_; // (Im)perfect forwarding.
+ instance<create_column> def_;
+ };
+
+ struct alter_table_common: trav_rel::alter_table, common
+ {
+ alter_table_common (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os, f) {}
+
+ template <typename T>
+ T*
+ check (sema_rel::alter_table& at)
+ {
+ for (sema_rel::alter_table::names_iterator i (at.names_begin ());
+ i != at.names_end (); ++i)
+ {
+ if (T* x = dynamic_cast<T*> (&i->nameable ()))
+ return x;
+ }
+ return 0;
+ }
+
+ sema_rel::column*
+ check_alter_column_null (sema_rel::alter_table& at, bool v)
+ {
+ for (sema_rel::alter_table::names_iterator i (at.names_begin ());
+ i != at.names_end (); ++i)
+ {
+ using sema_rel::add_column;
+ using sema_rel::alter_column;
+
+ if (alter_column* ac = dynamic_cast<alter_column*> (&i->nameable ()))
+ {
+ if (ac->null_altered () && ac->null () == v)
+ return ac;
+ }
+
+ // If we are testing for NOT NULL, also look for new columns that
+ // we initially add as NULL and later convert to NOT NULL.
+ //
+ if (!v)
+ {
+ if (add_column* ac = dynamic_cast<add_column*> (&i->nameable ()))
+ {
+ if (!ac->null () && ac->default_ ().empty ())
+ return ac;
+ }
+ }
+ }
+ return 0;
+ }
+
+ void
+ pass (unsigned short p)
+ {
+ pass_ = p;
+ }
+
+ protected:
+ unsigned short pass_;
+ };
+
+ struct alter_table_pre: alter_table_common
+ {
+ typedef alter_table_pre base;
+
+ alter_table_pre (emitter_type& e, ostream& os, schema_format f)
+ : alter_table_common (e, os, f) {}
+
+ // Check if there will be any clauses in ALTER TABLE.
+ //
+ using alter_table_common::check;
+
+ virtual bool
+ check (sema_rel::alter_table& at)
+ {
+ // If changing the below test, make sure to also update tests
+ // in database-specific code.
+ //
+ return
+ check<sema_rel::drop_foreign_key> (at) ||
+ check<sema_rel::add_column> (at) ||
+ check_alter_column_null (at, true);
+ }
+
+ virtual void
+ alter (sema_rel::alter_table& at)
+ {
+ // By default we generate all the alterations in a single ALTER TABLE
+ // statement. Quite a few databases don't support this.
+ //
+ pre_statement ();
+ os << "ALTER TABLE " << quote_id (at.name ());
+
+ bool f (true); // Shared first flag.
+ bool* pf (&f); // (Im)perfect forwarding.
+ bool tl (true); // (Im)perfect forwarding.
+ instance<create_column> cc (*this, tl, pf);
+ instance<alter_column> ac (*this, tl, pf);
+ instance<drop_foreign_key> dfk (*this, pf);
+ trav_rel::unames n;
+ n >> cc;
+ n >> ac;
+ n >> dfk;
+ names (at, n);
+ os << endl;
+
+ post_statement ();
+ }
+
+ virtual void
+ traverse (sema_rel::alter_table& at)
+ {
+ if (pass_ == 1)
+ {
+ // Drop unique indexes.
+ //
+ {
+ drop_index::index_type it (drop_index::unique);
+ instance<drop_index> in (*this, it);
+ trav_rel::unames n (*in);
+ names (at, n);
+ }
+
+ if (check (at))
+ alter (at);
+ }
+ else
+ {
+ // Add non-unique indexes.
+ //
+ {
+ create_index::index_type it (create_index::non_unique);
+ instance<create_index> in (*this, it);
+ trav_rel::unames n (*in);
+ names (at, n);
+ }
+ }
+ }
+ };
+
+ struct changeset_pre: trav_rel::changeset, common
+ {
+ typedef changeset_pre base;
+
+ changeset_pre (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os, f) {}
+
+ void
+ pass (unsigned short p)
+ {
+ pass_ = p;
+ }
+
+ protected:
+ unsigned short pass_;
+ };
+
+ struct alter_table_post: alter_table_common
+ {
+ typedef alter_table_post base;
+
+ alter_table_post (emitter_type& e, ostream& os, schema_format f)
+ : alter_table_common (e, os, f) {}
+
+ // Check if there will be any clauses in ALTER TABLE.
+ //
+ using alter_table_common::check;
+
+ virtual bool
+ check (sema_rel::alter_table& at)
+ {
+ // If changing the below test, make sure to also update tests
+ // in database-specific code.
+ //
+ return
+ check<sema_rel::add_foreign_key> (at) ||
+ check<sema_rel::drop_column> (at) ||
+ check_alter_column_null (at, false);
+ }
+
+ virtual void
+ alter (sema_rel::alter_table& at)
+ {
+ // By default we generate all the alterations in a single ALTER TABLE
+ // statement. Quite a few databases don't support this.
+ //
+ pre_statement ();
+ os << "ALTER TABLE " << quote_id (at.name ());
+
+ bool f (true); // Shared first flag.
+ bool* pf (&f); // (Im)perfect forwarding.
+ bool fl (false); // (Im)perfect forwarding.
+ instance<drop_column> dc (*this, pf);
+ instance<alter_column> ac (*this, fl, pf);
+ instance<create_foreign_key> fk (*this, pf);
+
+ trav_rel::unames n;
+ n >> dc;
+ n >> ac;
+ n >> fk;
+ names (at, n);
+ os << endl;
+
+ post_statement ();
+ }
+
+ virtual void
+ traverse (sema_rel::alter_table& at)
+ {
+ if (pass_ == 1)
+ {
+ // Drop non-unique indexes.
+ //
+ {
+ drop_index::index_type it (drop_index::non_unique);
+ instance<drop_index> in (*this, it);
+ trav_rel::unames n (*in);
+ names (at, n);
+ }
+ }
+ else
+ {
+ if (check (at))
+ alter (at);
+
+ // Add unique indexes.
+ //
+ {
+ create_index::index_type it (create_index::unique);
+ instance<create_index> in (*this, it);
+ trav_rel::unames n (*in);
+ names (at, n);
+ }
+ }
+ }
+ };
+
+ struct changeset_post: trav_rel::changeset, common
+ {
+ typedef changeset_post base;
+
+ changeset_post (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os, f) {}
+
+ virtual void
+ traverse (sema_rel::changeset& m)
+ {
+ // Traverse named entities in the reverse order. This way we
+ // drop them in the order opposite to creating.
+ //
+ for (sema_rel::changeset::names_iterator begin (m.names_begin ()),
+ end (m.names_end ()); begin != end;)
+ dispatch (*--end);
+ }
+
+ void
+ pass (unsigned short p)
+ {
+ pass_ = p;
+ }
+
+ protected:
+ unsigned short pass_;
+ };
+
+ //
+ // Schema version table.
+ //
+
+ struct version_table: common
+ {
+ typedef version_table base;
+
+ version_table (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os, f),
+ table_ (options.schema_version_table ()[db]),
+ qt_ (quote_id (table_)),
+ qs_ (quote_string (options.schema_name ()[db])),
+ qn_ (quote_id ("name")),
+ qv_ (quote_id ("version")),
+ qm_ (quote_id ("migration"))
+ {
+ }
+
+ // Create the version table if it doesn't exist.
+ //
+ virtual void
+ create_table () {}
+
+ // Remove the version entry. Called after the DROP statements.
+ //
+ virtual void
+ drop ()
+ {
+ pre_statement ();
+
+ os << "DELETE FROM " << qt_ << endl
+ << " WHERE " << qn_ << " = " << qs_ << endl;
+
+ post_statement ();
+ }
+
+ // Set the version. Called after the CREATE statements.
+ //
+ virtual void
+ create (sema_rel::version) {}
+
+ // Set the version and migration state to true. Called after
+ // the pre migration statements.
+ //
+ virtual void
+ migrate_pre (sema_rel::version v)
+ {
+ pre_statement ();
+
+ os << "UPDATE " << qt_ << endl
+ << " SET " << qv_ << " = " << v << ", " << qm_ << " = 1" << endl
+ << " WHERE " << qn_ << " = " << qs_ << endl;
+
+ post_statement ();
+ }
+
+ // Set migration state to false. Called after the post migration
+ // statements.
+ //
+ virtual void
+ migrate_post ()
+ {
+ pre_statement ();
+
+ os << "UPDATE " << qt_ << endl
+ << " SET " << qm_ << " = 0" << endl
+ << " WHERE " << qn_ << " = " << qs_ << endl;
+
+ post_statement ();
+ }
+
+ protected:
+ sema_rel::qname table_;
+ string qt_; // Quoted table.
+ string qs_; // Quoted schema name string.
+ string qn_; // Quoted name column.
+ string qv_; // Quoted version column.
+ string qm_; // Quoted migration column.
+ };
+
+ //
+ // SQL output.
+ //
+
+ struct sql_emitter: emitter, virtual context
+ {
+ typedef sql_emitter base;
+
+ virtual void
+ pre ()
+ {
+ first_ = true;
+ }
+
+ virtual void
+ line (const std::string& l)
+ {
+ if (first_ && !l.empty ())
+ first_ = false;
+ else
+ os << endl;
+
+ os << l;
+ }
+
+ virtual void
+ post ()
+ {
+ if (!first_) // Ignore empty statements.
+ os << ';' << endl
+ << endl;
+ }
+
+ protected:
+ bool first_;
+ };
+
+ struct sql_file: virtual context
+ {
+ typedef sql_file base;
+
+ virtual void
+ prologue ()
+ {
+ }
+
+ virtual void
+ epilogue ()
+ {
+ }
+ };
+ }
+}
+
+#endif // ODB_RELATIONAL_SCHEMA_HXX