diff options
Diffstat (limited to 'odb/odb/relational/schema.hxx')
-rw-r--r-- | odb/odb/relational/schema.hxx | 1606 |
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 |