From 8101ed727f48216e887183dc3c7b5d96e37c2650 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 17 Jul 2012 09:10:03 +0200 Subject: Implement multi-pass table dropping for SQL Server We have to first drop constraints before dropping tables in case the tables are dropped in a wrong order or there are circular dependencies. --- odb/relational/mssql/schema.cxx | 76 ++++++++++++++++++++++++++++++++++++-- odb/relational/schema.hxx | 46 +++++++++++++++++------ odb/semantics/relational/table.hxx | 5 +++ 3 files changed, 113 insertions(+), 14 deletions(-) diff --git a/odb/relational/mssql/schema.cxx b/odb/relational/mssql/schema.cxx index 52d36ed..91bb891 100644 --- a/odb/relational/mssql/schema.cxx +++ b/odb/relational/mssql/schema.cxx @@ -45,18 +45,88 @@ namespace relational drop_table (base const& x): base (x) {} virtual void - drop (sema_rel::qname const& table) + traverse (sema_rel::table&); + + private: + friend class drop_foreign_key; + set tables_; // Set of tables we would have already dropped. + }; + entry drop_table_; + + struct drop_foreign_key: trav_rel::foreign_key, relational::common + { + drop_foreign_key (drop_table& dt) + : common (dt.emitter (), dt.stream ()), dt_ (dt) + { + } + + virtual void + traverse (sema_rel::foreign_key& fk) + { + // Deferred constraints are not supported by SQL Server. + // + if (fk.deferred ()) + return; + + // 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. + // + sema_rel::qname const& rt (fk.referenced_table ()); + sema_rel::table& t (dynamic_cast (fk.scope ())); + sema_rel::model& m (dynamic_cast (t.scope ())); + + if (dt_.tables_.find (rt) != dt_.tables_.end () || + m.find (rt) == m.names_end ()) + { + + // In SQL Server, foreign key names are schema-global. Make them + // unique by prefixing the key name with table name. Note, however, + // that they cannot have a schema. + // + string n (t.name ().uname () + "_" + fk.name ()); + + pre_statement (); + os << "IF OBJECT_ID(" << quote_string (n) << ", " << + quote_string ("F") << ") IS NOT NULL" << endl + << " ALTER TABLE " << quote_id (t.name ()) << " DROP" << endl + << " CONSTRAINT " << quote_id (n) << endl; + post_statement (); + } + } + + private: + drop_table& dt_; + }; + + void drop_table:: + traverse (sema_rel::table& t) + { + qname const& table (t.name ()); + + if (pass_ == 1) + { + // Drop constraints. + // + tables_.insert (table); // Add it before to cover self-refs. + drop_foreign_key fk (*this); + trav_rel::unames n (fk); + names (t, n); + } + else if (pass_ == 2) { // SQL Server has no IF EXISTS conditional for dropping table. // The following approach appears to be the recommended way to // drop a table if it exists. // + pre_statement (); os << "IF OBJECT_ID(" << quote_string (table.string ()) << ", " << quote_string ("U") << ") IS NOT NULL" << endl << " DROP TABLE " << quote_id (table) << endl; + post_statement (); } - }; - entry drop_table_; + } // // Create. diff --git a/odb/relational/schema.hxx b/odb/relational/schema.hxx index b0ac01f..4f31dca 100644 --- a/odb/relational/schema.hxx +++ b/odb/relational/schema.hxx @@ -690,6 +690,13 @@ namespace relational empty_ = true; pass_ = p; new_pass_ = true; + + if (pass_ == 1) + empty_passes_ = 0; // New set of passes. + + // Assume this pass is empty. + // + empty_passes_++; } // Did this pass produce anything? @@ -716,28 +723,44 @@ namespace relational { first_ = false; - // If this line starts a new pass, then output the - // switch/case blocks. + // If this line starts a new pass, then output the switch/case + // blocks. // if (new_pass_) { new_pass_ = false; empty_ = false; + empty_passes_--; // This pass is not empty. - if (pass_ == 1) + // Output case statements for empty preceeding passes, if any. + // + if (empty_passes_ != 0) { + unsigned short s (pass_ - empty_passes_); + + if (s == 1) + os << "switch (pass)" + << "{"; + else + os << "return true;" // One more pass. + << "}"; + + for (; s != pass_; ++s) + os << "case " << s << ":" << endl; + + os << "{"; + empty_passes_ = 0; + } + + if (pass_ == 1) os << "switch (pass)" - << "{" - << "case 1:" << endl << "{"; - } else - { os << "return true;" // One more pass. - << "}" - << "case " << pass_ << ":" << endl - << "{"; - } + << "}"; + + os << "case " << pass_ << ":" << endl + << "{"; } os << "db.execute ("; @@ -761,6 +784,7 @@ namespace relational bool empty_; bool new_pass_; unsigned short pass_; + unsigned short empty_passes_; // Number of preceding empty passes. }; struct cxx_object: virtual context diff --git a/odb/semantics/relational/table.hxx b/odb/semantics/relational/table.hxx index d499ccc..49476bb 100644 --- a/odb/semantics/relational/table.hxx +++ b/odb/semantics/relational/table.hxx @@ -13,6 +13,11 @@ namespace semantics { class table: public qnameable, public uscope { + public: + // Resolve ambiguity. + // + using qnameable::scope; + protected: table (string const& id) : qnameable (id) -- cgit v1.1