From 51956f409ec7ebea8b6790b0c5d4f0b51513d683 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 15 Sep 2013 12:46:09 +0200 Subject: Cleanup polymorphic base tables when dropping derived one --- odb/relational/model.hxx | 12 ++++- odb/relational/mssql/schema.cxx | 8 ++-- odb/relational/oracle/schema.cxx | 29 +++++++++--- odb/relational/pgsql/schema.cxx | 11 ++++- odb/relational/schema.hxx | 81 ++++++++++++++++++++++++++++++-- odb/relational/sqlite/schema.cxx | 43 ++++++++++++++++- odb/semantics/relational/primary-key.hxx | 2 +- odb/semantics/relational/table.cxx | 17 ++++++- odb/semantics/relational/table.hxx | 12 +++++ 9 files changed, 196 insertions(+), 19 deletions(-) (limited to 'odb') diff --git a/odb/relational/model.hxx b/odb/relational/model.hxx index 7b5fb78..325e614 100644 --- a/odb/relational/model.hxx +++ b/odb/relational/model.hxx @@ -536,6 +536,7 @@ namespace relational model_.new_edge (model_, t, name); t.options (table_options (m, ct)); + t.extra ()["kind"] = "container"; // object_id // @@ -545,7 +546,9 @@ namespace relational oc->traverse (m, container_idt (m), "id", "object_id"); } - // Foreign key and index for the object id. + // Foreign key and index for the object id. Keep this foreign + // key first since we reply on this information to lookup the + // corresponding object table. // { // Derive the name prefix. See the comment for the other foreign @@ -734,7 +737,9 @@ namespace relational if (!object (c)) return; - if (abstract (c) && !polymorphic (c)) + semantics::class_* poly (polymorphic (c)); + + if (abstract (c) && poly == 0) return; qname const& name (table_name (c)); @@ -761,6 +766,9 @@ namespace relational t.options (table_options (c)); + if (poly != 0 && poly != &c) + t.extra ()["kind"] = "polymorphic"; + // Add columns. // { diff --git a/odb/relational/mssql/schema.cxx b/odb/relational/mssql/schema.cxx index 18c1f87..2480e75 100644 --- a/odb/relational/mssql/schema.cxx +++ b/odb/relational/mssql/schema.cxx @@ -183,20 +183,22 @@ namespace relational drop_table (base const& x): base (x) {} virtual void - drop (sema_rel::qname const& table, bool migration) + drop (sema_rel::table& t, bool migration) { // SQL Server has no IF EXISTS conditional for dropping tables. // The following approach appears to be the recommended way to // drop a table if it exists. // + sema_rel::qname const& name (t.name ()); + pre_statement (); if (!migration) - os << "IF OBJECT_ID(" << quote_string (table.string ()) << + os << "IF OBJECT_ID(" << quote_string (name.string ()) << ", " << quote_string ("U") << ") IS NOT NULL" << endl << " "; - os << "DROP TABLE " << quote_id (table) << endl; + os << "DROP TABLE " << quote_id (name) << endl; post_statement (); } diff --git a/odb/relational/oracle/schema.cxx b/odb/relational/oracle/schema.cxx index 5856dd2..829a37a 100644 --- a/odb/relational/oracle/schema.cxx +++ b/odb/relational/oracle/schema.cxx @@ -140,13 +140,8 @@ namespace relational drop_table (base const& x): base (x) {} virtual void - traverse (sema_rel::table& t, bool migration) + drop (sema_rel::table& t, bool migration) { - // For Oracle we use the CASCADE clause to drop foreign keys. - // - if (pass_ != 2) - return; - using sema_rel::primary_key; sema_rel::table::names_iterator i (t.find ("")); // Special name. @@ -162,7 +157,7 @@ namespace relational if (migration) { pre_statement (); - os << "DROP TABLE " << qt << " CASCADE CONSTRAINTS" << endl; + os << "DROP TABLE " << qt << endl; post_statement (); // Drop the sequence if we have auto primary key. @@ -207,6 +202,26 @@ namespace relational post_statement (); } } + + virtual void + traverse (sema_rel::table& t, bool migration) + { + // For migration drop foreign keys explicitly in pre-migration. + // + if (migration) + { + base::traverse (t, migration); + return; + } + + // For schema creation we use the CASCADE clause to drop foreign + // keys. + // + if (pass_ != 2) + return; + + drop (t, migration); + } }; entry drop_table_; diff --git a/odb/relational/pgsql/schema.cxx b/odb/relational/pgsql/schema.cxx index f127b95..1cf57f1 100644 --- a/odb/relational/pgsql/schema.cxx +++ b/odb/relational/pgsql/schema.cxx @@ -29,7 +29,16 @@ namespace relational virtual void traverse (sema_rel::table& t, bool migration) { - // For PostgreSQL we use the CASCADE clause to drop foreign keys. + // For migration drop foreign keys explicitly in pre-migration. + // + if (migration) + { + base::traverse (t, migration); + return; + } + + // For schema creation we use the CASCADE clause to drop foreign + // keys. // if (pass_ != 2) return; diff --git a/odb/relational/schema.hxx b/odb/relational/schema.hxx index 85dca09..3edb6b2 100644 --- a/odb/relational/schema.hxx +++ b/odb/relational/schema.hxx @@ -311,11 +311,42 @@ namespace relational : common (e, os, f) {} virtual void - drop (sema_rel::qname const& table, bool migration) + drop (sema_rel::table& t, bool migration) { pre_statement (); os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") << - quote_id (table) << endl; + 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 (); } @@ -345,7 +376,51 @@ namespace relational } } else - drop (t.name (), migration); + { + if (migration && + t.extra ().count ("kind") != 0 && + t.extra ()["kind"] == "polymorphic") + { + // 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 (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 ( + &i->nameable ())) + { + p = m.find (fk->referenced_table ()); + assert (p != 0); // Base table should be there. + break; + } + } + + primary_key& rkey (*p->find ("")); + primary_key& dkey (*t.find ("")); + assert (rkey.contains_size () == dkey.contains_size ()); + delete_ (p->name (), t.name (), rkey, dkey); + } + while (p->extra ().count ("kind") != 0); + } + + drop (t, migration); + } } virtual void diff --git a/odb/relational/sqlite/schema.cxx b/odb/relational/sqlite/schema.cxx index c4d852d..24bc2ef 100644 --- a/odb/relational/sqlite/schema.cxx +++ b/odb/relational/sqlite/schema.cxx @@ -49,7 +49,48 @@ namespace relational if (pass_ != 2) return; - drop (t.name (), migration); + // Polymorphic base cleanup code. Because we cannot drop foreign + // keys, we will trigger cascade deletion. The only way to work + // around this problem is to delete from the root table and rely + // on the cascade to clean up the rest. + // + if (migration && + t.extra ().count ("kind") != 0 && + t.extra ()["kind"] == "polymorphic") + { + using sema_rel::model; + using sema_rel::table; + using sema_rel::primary_key; + using sema_rel::foreign_key; + + model& m (dynamic_cast (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 ( + &i->nameable ())) + { + p = m.find
(fk->referenced_table ()); + assert (p != 0); // Base table should be there. + break; + } + } + } + while (p->extra ().count ("kind") != 0); + + primary_key& rkey (*p->find ("")); + primary_key& dkey (*t.find ("")); + assert (rkey.contains_size () == dkey.contains_size ()); + delete_ (p->name (), t.name (), rkey, dkey); + } + + drop (t, migration); } }; entry drop_table_; diff --git a/odb/semantics/relational/primary-key.hxx b/odb/semantics/relational/primary-key.hxx index 0f28966..e41ffc3 100644 --- a/odb/semantics/relational/primary-key.hxx +++ b/odb/semantics/relational/primary-key.hxx @@ -20,7 +20,7 @@ namespace semantics bool auto_ () const {return auto__;} - // Extra database information. + // Extra information. // public: typedef std::map extra_map; diff --git a/odb/semantics/relational/table.cxx b/odb/semantics/relational/table.cxx index 80add9f..8346d4c 100644 --- a/odb/semantics/relational/table.cxx +++ b/odb/semantics/relational/table.cxx @@ -16,7 +16,8 @@ namespace semantics table (table const& t, qscope& s, graph& g, bool b) : qnameable (t, g), uscope (t, (b ? s.lookup (t.name ()) : 0), g), - options_ (t.options_) + options_ (t.options_), + extra_map_ (t.extra_map_) { } @@ -30,6 +31,16 @@ namespace semantics g), options_ (p.attribute ("options", string ())) { + // All unhandled attributes go into the extra map. + // + typedef xml::parser::attribute_map_type attr_map; + attr_map const& am (p.attribute_map ()); + + for (attr_map::const_iterator i (am.begin ()); i != am.end (); ++i) + { + if (!i->second.handled) + extra_map_[i->first.name ()] = i->second.value; + } } table& table:: @@ -45,6 +56,10 @@ namespace semantics if (!options_.empty ()) s.attribute ("options", options_); + + for (extra_map::const_iterator i (extra_map_.begin ()); + i != extra_map_.end (); ++i) + s.attribute (i->first, i->second); } void table:: diff --git a/odb/semantics/relational/table.hxx b/odb/semantics/relational/table.hxx index f46eec1..99c65b7 100644 --- a/odb/semantics/relational/table.hxx +++ b/odb/semantics/relational/table.hxx @@ -20,6 +20,17 @@ namespace semantics virtual void options (string const& o) {options_ = o;} + // Extra information. + // + public: + typedef std::map extra_map; + + extra_map& + extra () {return extra_map_;} + + extra_map const& + extra () const {return extra_map_;} + public: table (string const& id): qnameable (id) {} table (table const&, qscope&, graph&, bool base = false); @@ -44,6 +55,7 @@ namespace semantics protected: string options_; + extra_map extra_map_; }; class add_table: public table -- cgit v1.1