From a4f25daf17392c9c4b90de60b9d777290706f667 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 8 Apr 2013 11:13:51 +0200 Subject: Generate add/drop foreign key migration statements Also add the --fkeys-deferrable-mode option. General schemas generation rework. --- NEWS | 8 + doc/manual.xhtml | 70 +++- odb/context.cxx | 2 + odb/context.hxx | 9 +- odb/makefile | 3 +- odb/option-functions.cxx | 5 + odb/option-types.hxx | 2 + odb/options.cli | 16 + odb/relational/changelog.cxx | 4 +- odb/relational/model.hxx | 10 +- odb/relational/mssql/schema.cxx | 439 +++++++++++++--------- odb/relational/mysql/schema.cxx | 429 ++++++++++++---------- odb/relational/oracle/schema.cxx | 187 +++++----- odb/relational/pgsql/schema.cxx | 100 +---- odb/relational/processor.cxx | 3 +- odb/relational/schema.cxx | 8 +- odb/relational/schema.hxx | 602 ++++++++++++++++++++++--------- odb/relational/source.cxx | 2 +- odb/relational/sqlite/schema.cxx | 118 +++++- odb/semantics/relational/deferrable.cxx | 56 +++ odb/semantics/relational/deferrable.hxx | 42 +++ odb/semantics/relational/foreign-key.cxx | 8 +- odb/semantics/relational/foreign-key.hxx | 23 +- 23 files changed, 1365 insertions(+), 781 deletions(-) create mode 100644 odb/semantics/relational/deferrable.cxx create mode 100644 odb/semantics/relational/deferrable.hxx diff --git a/NEWS b/NEWS index c6b2db3..899267c 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,14 @@ Version 2.3.0 queries. For more information, refer to Section 4.1, "ODB Query Language" in the ODB manual. + * New option, --fkeys-deferrable-mode, specifies the alternative deferrable + mode for foreign keys. By default, the ODB compiler generates deferred + foreign keys for databases that support them (SQLite, PostgreSQL, and + Oracle) and comments the foreign keys out for database that don't (MySQL + and SQL Server). This option can be used to override this behavior. Refer + to the ODB compiler command line interface documentation (man pages) for + details. + * New SQLite-specific exception, odb::sqlite::forced_rollback, which is thrown if SQLite forces a transaction to roll back. For more information, refer to Section 16.5.6, "Forced Rollback". diff --git a/doc/manual.xhtml b/doc/manual.xhtml index e9471fd..dad3c90 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -16046,9 +16046,16 @@ namespace odb committed. The only behaviors supported by MySQL are to either check such constraints immediately (InnoDB engine) or to ignore foreign key constraints altogether (all other engines). As a - result, schemas generated by the ODB compiler for MySQL have - foreign key definitions commented out. They are retained only - for documentation.

+ result, by default, schemas generated by the ODB compiler for + MySQL have foreign key definitions commented out. They are + retained only for documentation.

+ +

You can override the default behavior and instruct the ODB + compiler to generate non-deferrable foreign keys by specifying + the --fkeys-deferrable-mode not_deferrable ODB + compiler option. Note, however, that in this case the order in + which you persist, update, and erase objects within a transaction + becomes important.

15.6 MySQL Index Definitions

@@ -16909,14 +16916,14 @@ odb::database& db = ... } -

Finally, ODB relies on standard SQL behavior which requires +

Finally, ODB assumes the standard SQL behavior which requires that foreign key constraints checking is deferred until the transaction is committed. Default SQLite behavior is to check such constraints immediately. As a result, when used with ODB, a custom - database schema that defines foreign key constraints must declare - such constraints as DEFERRABLE INITIALLY DEFERRED, as - shown in the following example. Schemas generated by the ODB compiler - meet this requirement automatically.

+ database schema that defines foreign key constraints may need to + declare such constraints as DEFERRABLE INITIALLY DEFERRED, + as shown in the following example. By default, schemas generated by + the ODB compiler meet this requirement automatically.

 CREATE TABLE Employee (
@@ -16925,6 +16932,12 @@ CREATE TABLE Employee (
            DEFERRABLE INITIALLY DEFERRED);
   
+

You can override the default behavior and instruct the ODB + compiler to generate non-deferrable foreign keys by specifying + the --fkeys-deferrable-mode not_deferrable ODB + compiler option. Note, however, that in this case the order in + which you persist, update, and erase objects within a transaction + becomes important.

16.5.4 Constraint Violations

@@ -17646,14 +17659,14 @@ namespace odb

17.5.2 Foreign Key Constraints

-

ODB relies on standard SQL behavior which requires that +

ODB assumes the standard SQL behavior which requires that foreign key constraints checking is deferred until the transaction is committed. Default PostgreSQL behavior is to check such constraints immediately. As a result, when used with ODB, a custom database schema that defines foreign - key constraints must declare such constraints as + key constraints may need to declare such constraints as INITIALLY DEFERRED, as shown in the following example. - Schemas generated by the ODB compiler meet this requirement + By default, schemas generated by the ODB compiler meet this requirement automatically.

@@ -17662,6 +17675,13 @@ CREATE TABLE Employee (
   employer BIGINT REFERENCES Employer(id) INITIALLY DEFERRED);
   
+

You can override the default behavior and instruct the ODB + compiler to generate non-deferrable foreign keys by specifying + the --fkeys-deferrable-mode not_deferrable ODB + compiler option. Note, however, that in this case the order in + which you persist, update, and erase objects within a transaction + becomes important.

+

17.5.3 Unique Constraint Violations

Due to the granularity of the PostgreSQL error codes, it is impossible @@ -18533,14 +18553,14 @@ class long_class_name

18.5.3 Foreign Key Constraints

-

ODB relies on standard SQL behavior which requires that +

ODB assumes the standard SQL behavior which requires that foreign key constraints checking is deferred until the transaction is committed. Default Oracle behavior is to check such constraints immediately. As a result, when used with ODB, a custom database schema that defines foreign - key constraints must declare such constraints as + key constraints may need to declare such constraints as INITIALLY DEFERRED, as shown in the following example. - Schemas generated by the ODB compiler meet this requirement + By default, schemas generated by the ODB compiler meet this requirement automatically.

@@ -18550,6 +18570,13 @@ CREATE TABLE Employee (
            DEFERRABLE INITIALLY DEFERRED);
   
+

You can override the default behavior and instruct the ODB + compiler to generate non-deferrable foreign keys by specifying + the --fkeys-deferrable-mode not_deferrable ODB + compiler option. Note, however, that in this case the order in + which you persist, update, and erase objects within a transaction + becomes important.

+

18.5.4 Unique Constraint Violations

Due to the granularity of the Oracle error codes, it is impossible @@ -19667,12 +19694,19 @@ namespace odb

19.5.2 Foreign Key Constraints

-

ODB relies on standard SQL behavior which requires that foreign +

ODB assumes the standard SQL behavior which requires that foreign key constraints checking is deferred until the transaction is committed. The only behavior supported by SQL Server is to check - such constraints immediately. As a result, schemas generated by - the ODB compiler for SQL Server have foreign key definitions - commented out. They are retained only for documentation.

+ such constraints immediately. As a result, by default, schemas + generated by the ODB compiler for SQL Server have foreign key + definitions commented out. They are retained only for documentation.

+ +

You can override the default behavior and instruct the ODB + compiler to generate non-deferrable foreign keys by specifying + the --fkeys-deferrable-mode not_deferrable ODB + compiler option. Note, however, that in this case the order in + which you persist, update, and erase objects within a transaction + becomes important.

19.5.3 Unique Constraint Violations

diff --git a/odb/context.cxx b/odb/context.cxx index 9b8cb3c..760d653 100644 --- a/odb/context.cxx +++ b/odb/context.cxx @@ -442,6 +442,7 @@ context (ostream& os_, options (ops), features (f), db (options.database ()[0]), + in_comment (data_->in_comment_), exp (data_->exp_), ext (data_->ext_), keyword_set (data_->keyword_set_), @@ -546,6 +547,7 @@ context () options (current ().options), features (current ().features), db (current ().db), + in_comment (current ().in_comment), exp (current ().exp), ext (current ().ext), keyword_set (current ().keyword_set), diff --git a/odb/context.hxx b/odb/context.hxx index d148a97..c06a9c4 100644 --- a/odb/context.hxx +++ b/odb/context.hxx @@ -1050,7 +1050,10 @@ protected: ~data () {} data (std::ostream& os) - : os_ (os.rdbuf ()), top_object_ (0), cur_object_ (0), + : os_ (os.rdbuf ()), + in_comment_ (false), + top_object_ (0), + cur_object_ (0), sql_name_upper_ ("(.+)", "\\U$1"), sql_name_lower_ ("(.+)", "\\L$1") { @@ -1060,6 +1063,8 @@ protected: std::ostream os_; std::stack os_stack_; + bool in_comment_; + semantics::class_* top_object_; semantics::class_* cur_object_; @@ -1090,6 +1095,8 @@ public: features_type& features; database const db; + bool& in_comment; + string& exp; // Export symbol (with trailing space if specified). string& ext; // Extern symbol. diff --git a/odb/makefile b/odb/makefile index 104bc4a..02be483 100644 --- a/odb/makefile +++ b/odb/makefile @@ -156,7 +156,8 @@ cxx_ctun := \ option-types.cxx \ option-functions.cxx \ profile.cxx \ -semantics/relational/name.cxx +semantics/relational/name.cxx \ +semantics/relational/deferrable.cxx # Options file. # diff --git a/odb/option-functions.cxx b/odb/option-functions.cxx index 016b729..61db175 100644 --- a/odb/option-functions.cxx +++ b/odb/option-functions.cxx @@ -53,6 +53,11 @@ process_options (options& o) if (o.schema_name ().count (db) == 0) o.schema_name ()[db] = ""; + // Set default --fkeys-deferrable-mode value. + // + if (o.fkeys_deferrable_mode ().count (db) == 0) + o.fkeys_deferrable_mode ()[db] = deferrable::deferred; + // Set default --{export,extern}-symbol values. // if (o.export_symbol ().count (db) == 0) diff --git a/odb/option-types.hxx b/odb/option-types.hxx index 9a0cad5..2f8825d 100644 --- a/odb/option-types.hxx +++ b/odb/option-types.hxx @@ -12,8 +12,10 @@ #include +#include using semantics::relational::qname; +using semantics::relational::deferrable; struct cxx_version { diff --git a/odb/options.cli b/odb/options.cli index 457c06f..569566d 100644 --- a/odb/options.cli +++ b/odb/options.cli @@ -169,6 +169,22 @@ class options schema name, is used." }; + database_map --fkeys-deferrable-mode + { + "", + "Use constraint checking mode in foreign keys generated for object + relationships. Valid values for this option are \cb{not_deferrable}, + \cb{immediate}, and \cb{deferred} (default). MySQL and SQL Server do + not support deferrable foreign keys and for these databases such keys + are generated commented out. Other foreign keys generated by the ODB + compiler (such as the ones used to support containers and polymorphic + hierarchies) are always generated as not deferrable. + + Note also that if you use either \cb{not_deferrable} or \cb{immediate} + mode, then the order in which you persist, update, and erase objects + within a transaction becomes important." + }; + std::string --default-pointer = "*" { "", diff --git a/odb/relational/changelog.cxx b/odb/relational/changelog.cxx index f576c79..af6371a 100644 --- a/odb/relational/changelog.cxx +++ b/odb/relational/changelog.cxx @@ -145,8 +145,8 @@ namespace relational { if (foreign_key* ofk = other.find (fk.name ())) { - if (fk.deferred () != ofk->deferred ()) - diagnose_foreign_key (fk, "deferred kind"); + 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"); diff --git a/odb/relational/model.hxx b/odb/relational/model.hxx index 4c4bba6..44f01c9 100644 --- a/odb/relational/model.hxx +++ b/odb/relational/model.hxx @@ -228,14 +228,16 @@ namespace relational string id (id_prefix_ + (key_prefix_.empty () ? m.name () : key_prefix_)); - bool deferred (m.get ("deferred", true)); + deferrable def ( + m.get ("deferrable", + options.fkeys_deferrable_mode ()[db])); + foreign_key::action_type on_delete ( m.get ( "on-delete", foreign_key::no_action)); foreign_key& fk ( - model_.new_node ( - id, table_name (c), deferred, on_delete)); + model_.new_node (id, table_name (c), def, on_delete)); fk.set ("cxx-location", m.location ()); @@ -522,7 +524,7 @@ namespace relational model_.new_node ( id + ".id", table_name (*context::top_object), - false, // immediate + sema_rel::deferrable::not_deferrable, sema_rel::foreign_key::cascade)); fk.set ("cxx-location", m.location ()); model_.new_edge ( diff --git a/odb/relational/mssql/schema.cxx b/odb/relational/mssql/schema.cxx index d1d892a..61e7e68 100644 --- a/odb/relational/mssql/schema.cxx +++ b/odb/relational/mssql/schema.cxx @@ -2,8 +2,6 @@ // copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file -#include - #include #include @@ -18,6 +16,7 @@ namespace relational namespace schema { namespace relational = relational::schema; + using relational::table_set; struct sql_emitter: relational::sql_emitter { @@ -58,89 +57,98 @@ namespace relational }; entry drop_column_; - struct drop_table: relational::drop_table, context + struct drop_foreign_key: relational::drop_foreign_key, context { - drop_table (base const& x): base (x) {} + drop_foreign_key (base const& x): base (x) {} virtual void - traverse (sema_rel::table&, bool); + drop (sema_rel::table& t, sema_rel::foreign_key& fk) + { + bool migration (dropped_ == 0); - private: - friend class drop_foreign_key; - set tables_; // Set of tables we would have already dropped. - }; - entry drop_table_; + if (migration) + { + if (fk.not_deferrable ()) + pre_statement (); + else + { + if (format_ != schema_format::sql) + return; - struct drop_foreign_key: trav_rel::foreign_key, relational::common - { - drop_foreign_key (drop_table& dt, bool m) - : common (dt.emitter (), dt.stream ()), dt_ (dt), migration_ (m) - { + os << "/*" << endl; + } + } + else + { + // Here we drop potentially deferrable keys and also need to + // test if the key exists. + // + pre_statement (); + + os << "IF OBJECT_ID(" << quote_string (fk.name ()) << ", " << + quote_string ("F") << ") IS NOT NULL" << endl + << " "; + } + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << (migration ? " " : " ") << "DROP CONSTRAINT " << + quote_id (fk.name ()) << endl; + + + if (!migration || fk.not_deferrable ()) + post_statement (); + else + os << "*/" << endl + << endl; } virtual void - traverse (sema_rel::foreign_key& fk) + traverse (sema_rel::drop_foreign_key& dfk) { - // Deferred constraints are not supported by SQL Server. + // Find the foreign key we are dropping in the base model. // - if (fk.deferred ()) - return; + sema_rel::foreign_key& fk (find (dfk)); - // 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 (fk.scope ())); + bool c (!fk.not_deferrable () && !in_comment); - if (!migration_) - { - sema_rel::qname const& rt (fk.referenced_table ()); - sema_rel::model& m (dynamic_cast (t.scope ())); + if (c && format_ != schema_format::sql) + return; - if (dt_.tables_.find (rt) == dt_.tables_.end () && - m.find (rt) != m.names_end ()) - return; - } + if (!first_) + os << (c ? "" : ",") << endl + << " "; - pre_statement (); + if (c) + os << "/* "; - if (!migration_) - os << "IF OBJECT_ID(" << quote_string (fk.name ()) << ", " << - quote_string ("F") << ") IS NOT NULL" << endl - << " "; + os << quote_id (fk.name ()); - os << "ALTER TABLE " << quote_id (t.name ()) << " DROP" << endl - << (!migration_ ? " " : "") << " CONSTRAINT " << - quote_id (fk.name ()) << endl; + if (c) + os << " */"; - post_statement (); + if (first_) + { + if (c) + // There has to be a real name otherwise the whole statement + // would have been commented out. + // + os << endl + << " "; + else + first_ = false; + } } - - private: - drop_table& dt_; - bool migration_; }; + entry drop_foreign_key_; - void drop_table:: - traverse (sema_rel::table& t, bool migration) + struct drop_table: relational::drop_table, context { - qname const& table (t.name ()); + drop_table (base const& x): base (x) {} - if (pass_ == 1) - { - // Drop constraints. In migration this is always done on pass 1. - // - if (!migration) - tables_.insert (table); // Add it before to cover self-refs. - drop_foreign_key fk (*this, migration); - trav_rel::unames n (fk); - names (t, n); - } - else if (pass_ == 2) + virtual void + drop (sema_rel::qname const& table, bool migration) { - // SQL Server has no IF EXISTS conditional for dropping table. + // 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. // @@ -155,7 +163,8 @@ namespace relational post_statement (); } - } + }; + entry drop_table_; // // Create. @@ -185,141 +194,107 @@ namespace relational }; entry create_column_; - struct create_foreign_key; - - struct create_table: relational::create_table, context - { - create_table (base const& x): base (x) {} - - void - traverse (sema_rel::table&); - - private: - friend class create_foreign_key; - set tables_; // Set of tables we have already defined. - }; - entry create_table_; - struct create_foreign_key: relational::create_foreign_key, context { - create_foreign_key (schema_format f, relational::create_table& ct) - : base (f, ct) - { - } - create_foreign_key (base const& x): base (x) {} virtual void - traverse (sema_rel::foreign_key& fk) + generate (sema_rel::foreign_key& fk) { - // 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 - // (see add_foreign_key below). + // SQL Server does not support deferrable constraint checking. + // Output such foreign keys as comments, for documentation, + // unless we are generating embedded schema. // - create_table& ct (static_cast (create_table_)); - - if (ct.tables_.find (fk.referenced_table ()) != ct.tables_.end ()) + if (fk.not_deferrable ()) + base::generate (fk); + else { - // SQL Server does not support deferred constraint checking. - // Output such foreign keys as comments, for documentation, - // unless we are generating embedded schema. + // Don't bloat C++ code with comment strings if we are + // generating embedded schema. // - if (fk.deferred ()) - { - // Don't bloat C++ code with comment strings if we are - // generating embedded schema. - // - if (format_ != schema_format::embedded) - { - os << endl - << endl - << " /*" << endl; + if (format_ != schema_format::sql) + return; - base::create (fk); + os << endl + << " /*" << endl + << " CONSTRAINT "; + create (fk); + os << endl + << " */"; + } + } - os << endl - << " */"; - } - } - else - base::traverse (fk); + virtual void + traverse (sema_rel::add_foreign_key& afk) + { + bool c (!afk.not_deferrable () && !in_comment); - fk.set ("mssql-fk-defined", true); // Mark it as defined. + if (c && format_ != schema_format::sql) + return; + + if (!first_) + os << (c ? "" : ",") << endl + << " "; + + if (c) + os << "/*" << endl + << " "; + + os << "CONSTRAINT "; + create (afk); + + if (c) + os << endl + << " */"; + + if (first_) + { + if (c) + // There has to be a real key otherwise the whole statement + // would have been commented out. + // + os << endl + << " "; + else + first_ = false; } } virtual void - deferred () + deferrable (sema_rel::deferrable) { - // SQL Server doesn't support deferred. + // This will still be called to output the comment. } }; entry create_foreign_key_; - struct add_foreign_key: create_foreign_key, relational::common + struct add_foreign_key: relational::add_foreign_key, context { - add_foreign_key (schema_format f, relational::create_table& ct) - : create_foreign_key (f, ct), common (ct.emitter (), ct.stream ()) - { - } + add_foreign_key (base const& x): base (x) {} virtual void - traverse (sema_rel::foreign_key& fk) + generate (sema_rel::table& t, sema_rel::foreign_key& fk) { - if (!fk.count ("mssql-fk-defined")) + // SQL Server has no deferrable constraints. + // + if (fk.not_deferrable ()) + base::generate (t, fk); + else { - sema_rel::table& t (dynamic_cast (fk.scope ())); - - // SQL Server has no deferred constraints. - // - if (fk.deferred ()) - { - if (format_ != schema_format::embedded) - { - os << "/*" << endl; - - os << "ALTER TABLE " << quote_id (t.name ()) << " ADD" << endl; - base::create (fk); - - os << endl - << "*/" << endl - << endl; - } - } - else - { - pre_statement (); - - os << "ALTER TABLE " << quote_id (t.name ()) << " ADD" << endl; - base::create (fk); - os << endl; + if (format_ != schema_format::sql) + return; - post_statement (); - } + os << "/*" << endl; + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " ADD CONSTRAINT "; + def_->create (fk); + os << endl + << "*/" << endl + << endl; } } }; - - void create_table:: - traverse (sema_rel::table& t) - { - if (pass_ == 1) - { - // In migration we always add foreign keys on pass 2. - // - if (!t.is_a ()) - tables_.insert (t.name ()); // Add it before to cover self-refs. - base::traverse (t); - return; - } - - // Add foreign keys. - // - add_foreign_key fk (format_, *this); - trav_rel::unames n (fk); - names (t, n); - } + entry add_foreign_key_; struct drop_index: relational::drop_index, context { @@ -367,22 +342,76 @@ namespace relational { alter_table_pre (base const& x): base (x) {} + // Check if we are only dropping deferrable foreign keys. + // + bool + check_drop_deferrable_only (sema_rel::alter_table& at) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::foreign_key; + using sema_rel::drop_foreign_key; + + if (drop_foreign_key* dfk = + dynamic_cast (&i->nameable ())) + { + foreign_key& fk (find (*dfk)); + + if (fk.not_deferrable ()) + return false; + } + } + return true; + } + virtual void alter (sema_rel::alter_table& at) { // SQL Server can only alter one kind of thing at a time. // + if (check (at)) + { + bool deferrable (check_drop_deferrable_only (at)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + alter_header (at.name ()); + os << endl + << " DROP CONSTRAINT "; + instance dfc (*this); + trav_rel::unames n (*dfc); + names (at, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + if (check (at)) { pre_statement (); alter_header (at.name ()); - os << " ADD "; + os << endl + << " ADD "; - instance cc (emitter (), stream (), format_); - trav_rel::alter_column ac; // Override. - trav_rel::unames n; - n >> cc; - n >> ac; + instance cc (*this); + trav_rel::unames n (*cc); names (at, n); os << endl; @@ -393,7 +422,7 @@ namespace relational // { bool tl (true); // (Im)perfect forwarding. - instance ac (emitter (), stream (), format_, tl); + instance ac (*this, tl); trav_rel::unames n (*ac); names (at, n); } @@ -405,6 +434,26 @@ namespace relational { alter_table_post (base const& x): base (x) {} + // Check if we are only adding deferrable foreign keys. + // + bool + check_add_deferrable_only (sema_rel::alter_table& at) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::add_foreign_key; + + if (add_foreign_key* afk = + dynamic_cast (&i->nameable ())) + { + if (afk->not_deferrable ()) + return false; + } + } + return true; + } + virtual void alter (sema_rel::alter_table& at) { @@ -414,9 +463,10 @@ namespace relational { pre_statement (); alter_header (at.name ()); - os << " DROP COLUMN "; + os << endl + << " DROP COLUMN "; - instance dc (emitter (), stream (), format_); + instance dc (*this); trav_rel::unames n (*dc); names (at, n); os << endl; @@ -428,10 +478,43 @@ namespace relational // { bool fl (false); // (Im)perfect forwarding. - instance ac (emitter (), stream (), format_, fl); + instance ac (*this, fl); trav_rel::unames n (*ac); names (at, n); } + + if (check (at)) + { + bool deferrable (check_add_deferrable_only (at)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + alter_header (at.name ()); + os << endl + << " ADD "; + instance cfc (*this); + trav_rel::unames n (*cfc); + names (at, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } } }; entry alter_table_post_; diff --git a/odb/relational/mysql/schema.cxx b/odb/relational/mysql/schema.cxx index 2ae47ae..be058ce 100644 --- a/odb/relational/mysql/schema.cxx +++ b/odb/relational/mysql/schema.cxx @@ -2,8 +2,6 @@ // copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file -#include - #include #include @@ -18,58 +16,18 @@ namespace relational namespace schema { namespace relational = relational::schema; + using relational::table_set; // // Drop. // - struct drop_table: relational::drop_table, context + struct drop_foreign_key: relational::drop_foreign_key, context { - drop_table (base const& x): base (x) {} + drop_foreign_key (base const& x): base (x) {} virtual void - traverse (sema_rel::table&, bool migration); - - 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, bool m) - : common (dt.emitter (), dt.stream ()), dt_ (dt), migration_ (m) + drop (sema_rel::table& t, sema_rel::foreign_key& fk) { - } - - virtual void - traverse (sema_rel::foreign_key& fk) - { - // Deferred constraints are not supported by MySQL. - // - 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. In migration we always do this - // first. - // - sema_rel::table& t (dynamic_cast (fk.scope ())); - - if (!migration_) - { - sema_rel::qname const& rt (fk.referenced_table ()); - sema_rel::model& m (dynamic_cast (t.scope ())); - - if (dt_.tables_.find (rt) == dt_.tables_.end () && - m.find (rt) != m.names_end ()) - return; - } - - pre_statement (); - /* // @@ This does not work: in MySQL control statements can only // be used in stored procedures. It seems the only way to @@ -91,48 +49,65 @@ namespace relational << "END IF;" << endl; */ - os << "ALTER TABLE " << quote_id (t.name ()) << " DROP FOREIGN " << - "KEY " << quote_id (fk.name ()) << endl; + // So for now we only do this in migration. + // + if (dropped_ == 0) + { + if (fk.not_deferrable ()) + pre_statement (); + else + { + if (format_ != schema_format::sql) + return; - post_statement (); - } + os << "/*" << endl; + } - private: - drop_table& dt_; - bool migration_; - }; + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " DROP FOREIGN KEY " << quote_id (fk.name ()) << endl; - void drop_table:: - traverse (sema_rel::table& t, bool migration) - { - // Only enabled for migration support for now (see above). - // - if (!migration) - { - base::traverse (t, migration); - return; + if (fk.not_deferrable ()) + post_statement (); + else + os << "*/" << endl + << endl; + } } - qname const& table (t.name ()); + using base::drop; - if (pass_ == 1) + virtual void + traverse (sema_rel::drop_foreign_key& dfk) { - // Drop constraints. In migration this is always done on pass 1. + // Find the foreign key we are dropping in the base model. // - if (!migration) - tables_.insert (table); // Add it before to cover self-refs. - drop_foreign_key fk (*this, migration); - trav_rel::unames n (fk); - names (t, n); + sema_rel::foreign_key& fk (find (dfk)); + + if (fk.not_deferrable () || in_comment) + base::traverse (dfk); + else + { + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" + << endl; + + drop (dfk); + + os << endl + << " */"; + } } - else if (pass_ == 2) + + virtual void + drop_header () { - pre_statement (); - os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") << - quote_id (table) << endl; - post_statement (); + os << "DROP FOREIGN KEY "; } - } + }; + entry drop_foreign_key_; // // Create. @@ -150,155 +125,112 @@ namespace relational }; entry create_column_; - struct create_foreign_key; - - struct create_table: relational::create_table, context + struct create_foreign_key: relational::create_foreign_key, context { - create_table (base const& x): base (x) {} - - void - traverse (sema_rel::table&); + create_foreign_key (base const& x): base (x) {} virtual void - create_post () + generate (sema_rel::foreign_key& fk) { - os << ")"; - - string const& engine (options.mysql_engine ()); + // MySQL does not support deferrable constraint checking. Output + // such foreign keys as comments, for documentation, unless we + // are generating embedded schema. + // + if (fk.not_deferrable ()) + base::generate (fk); + else + { + // Don't bloat C++ code with comment strings if we are + // generating embedded schema. + // + if (format_ != schema_format::sql) + return; - if (engine != "default") os << endl - << " ENGINE=" << engine; - - os << endl; - } - - private: - friend class create_foreign_key; - set tables_; // Set of tables we have already defined. - }; - entry create_table_; - - struct create_foreign_key: relational::create_foreign_key, context - { - create_foreign_key (schema_format f, relational::create_table& ct) - : base (f, ct) - { + << " /*" << endl + << " CONSTRAINT "; + base::create (fk); + os << endl + << " */"; + } } - create_foreign_key (base const& x): base (x) {} - virtual void - traverse (sema_rel::foreign_key& fk) + traverse (sema_rel::add_foreign_key& afk) { - // 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 - // (see add_foreign_key below). - // - create_table& ct (static_cast (create_table_)); - - if (ct.tables_.find (fk.referenced_table ()) != ct.tables_.end ()) + if (afk.not_deferrable () || in_comment) + base::traverse (afk); + else { - // MySQL does not support deferred constraint checking. Output - // such foreign keys as comments, for documentation, unless we - // are generating embedded schema. - // - if (fk.deferred ()) - { - // Don't bloat C++ code with comment strings if we are - // generating embedded schema. - // - if (format_ != schema_format::embedded) - { - os << endl - << endl - << " /*" << endl; - - base::create (fk); - - os << endl - << " */"; - } - } - else - base::traverse (fk); + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" + << endl; + + add (afk); - fk.set ("mysql-fk-defined", true); // Mark it as defined. + os << endl + << " */"; } } virtual void - deferred () + deferrable (sema_rel::deferrable) { - // MySQL doesn't support deferred. + // This will still be called to output the comment. } }; entry create_foreign_key_; - struct add_foreign_key: create_foreign_key, relational::common + struct add_foreign_key: relational::add_foreign_key, context { - add_foreign_key (schema_format f, relational::create_table& ct) - : create_foreign_key (f, ct), common (ct.emitter (), ct.stream ()) - { - } + add_foreign_key (base const& x): base (x) {} virtual void - traverse (sema_rel::foreign_key& fk) + generate (sema_rel::table& t, sema_rel::foreign_key& fk) { - if (!fk.count ("mysql-fk-defined")) + // MySQL has no deferrable constraints. + // + if (fk.not_deferrable ()) + base::generate (t, fk); + else { - sema_rel::table& t (dynamic_cast (fk.scope ())); - - // MySQL has no deferred constraints. - // - if (fk.deferred ()) - { - if (format_ != schema_format::embedded) - { - os << "/*" << endl; - - os << "ALTER TABLE " << quote_id (t.name ()) << " ADD" << endl; - base::create (fk); - - os << endl - << "*/" << endl - << endl; - } - } - else - { - pre_statement (); - - os << "ALTER TABLE " << quote_id (t.name ()) << " ADD" << endl; - base::create (fk); - os << endl; + if (format_ != schema_format::sql) + return; - post_statement (); - } + os << "/*" << endl; + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " ADD CONSTRAINT "; + def_->create (fk); + os << endl + << "*/" << endl + << endl; } } }; + entry add_foreign_key_; - void create_table:: - traverse (sema_rel::table& t) + struct create_table: relational::create_table, context { - if (pass_ == 1) + create_table (base const& x): base (x) {} + + virtual void + create_post () { - // In migration we always add foreign keys on pass 2. - // - if (!t.is_a ()) - tables_.insert (t.name ()); // Add it before to cover self-refs. - base::traverse (t); - return; - } + os << ")"; - // Add foreign keys. - // - add_foreign_key fk (format_, *this); - trav_rel::unames n (fk); - names (t, n); - } + string const& engine (options.mysql_engine ()); + + if (engine != "default") + os << endl + << " ENGINE=" << engine; + + os << endl; + } + }; + entry create_table_; struct create_index: relational::create_index, context { @@ -356,6 +288,119 @@ namespace relational } }; entry alter_column_; + + struct alter_table_pre: relational::alter_table_pre, context + { + alter_table_pre (base const& x): base (x) {} + + // Check if we are only dropping deferrable foreign keys. + // + bool + check_drop_deferrable_only (sema_rel::alter_table& at) + { + if (check (at) || + check_alter_column_null (at, true)) + return false; + + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::foreign_key; + using sema_rel::drop_foreign_key; + + if (drop_foreign_key* dfk = + dynamic_cast (&i->nameable ())) + { + foreign_key& fk (find (*dfk)); + + if (fk.not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + if (check_drop_deferrable_only (at)) + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + in_comment = true; + + alter_header (at.name ()); + instance dfk (*this); + trav_rel::unames n (*dfk); + names (at, n); + os << endl; + + in_comment = false; + os << "*/" << endl + << endl; + } + else + base::alter (at); + } + }; + entry alter_table_pre_; + + struct alter_table_post: relational::alter_table_post, context + { + alter_table_post (base const& x): base (x) {} + + // Check if we are only adding deferrable foreign keys. + // + bool + check_add_deferrable_only (sema_rel::alter_table& at) + { + if (check (at) || + check_alter_column_null (at, false)) + return false; + + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::add_foreign_key; + + if (add_foreign_key* afk = + dynamic_cast (&i->nameable ())) + { + if (afk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + if (check_add_deferrable_only (at)) + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + in_comment = true; + + alter_header (at.name ()); + instance cfk (*this); + trav_rel::unames n (*cfk); + names (at, n); + os << endl; + + in_comment = false; + os << "*/" << endl + << endl; + } + else + base::alter (at); + } + }; + entry alter_table_post_; } } } diff --git a/odb/relational/oracle/schema.cxx b/odb/relational/oracle/schema.cxx index 9dc1921..298280e 100644 --- a/odb/relational/oracle/schema.cxx +++ b/odb/relational/oracle/schema.cxx @@ -2,8 +2,6 @@ // copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file -#include - #include #include @@ -18,6 +16,7 @@ namespace relational namespace schema { namespace relational = relational::schema; + using relational::table_set; struct sql_emitter: relational::sql_emitter { @@ -106,6 +105,19 @@ namespace relational }; entry drop_column_; + struct drop_foreign_key: relational::drop_foreign_key, context + { + drop_foreign_key (base const& x): base (x) {} + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + os << endl; + drop (dfk); + } + }; + entry drop_foreign_key_; + struct drop_table: relational::drop_table, context { drop_table (base const& x): base (x) {} @@ -113,6 +125,8 @@ namespace relational virtual void traverse (sema_rel::table& t, bool migration) { + // For Oracle we use the CASCADE clause to drop foreign keys. + // if (pass_ != 2) return; @@ -203,112 +217,52 @@ namespace relational }; entry create_column_; - struct create_foreign_key; - - struct create_table: relational::create_table, context - { - create_table (base const& x): base (x) {} - - void - traverse (sema_rel::table&); - - private: - friend class create_foreign_key; - set tables_; // Set of tables we have already defined. - }; - entry create_table_; - struct create_foreign_key: relational::create_foreign_key, context { - create_foreign_key (schema_format f, relational::create_table& ct) - : base (f, ct) - { - } - create_foreign_key (base const& x): base (x) {} virtual void - traverse (sema_rel::foreign_key& fk) + traverse (sema_rel::add_foreign_key& afk) { - // 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 - // (see add_foreign_key below). - // - create_table& ct (static_cast (create_table_)); - - if (ct.tables_.find (fk.referenced_table ()) != ct.tables_.end ()) - { - base::traverse (fk); - fk.set ("oracle-fk-defined", true); // Mark it as defined. - } + os << endl + << " ADD CONSTRAINT "; + create (afk); } }; entry create_foreign_key_; - struct add_foreign_key: create_foreign_key, relational::common + struct create_table: relational::create_table, context { - add_foreign_key (schema_format f, relational::create_table& ct) - : create_foreign_key (f, ct), common (ct.emitter (), ct.stream ()) - { - } - - virtual void - traverse (sema_rel::foreign_key& fk) - { - if (!fk.count ("oracle-fk-defined")) - { - sema_rel::table& t (dynamic_cast (fk.scope ())); - - pre_statement (); - - os << "ALTER TABLE " << quote_id (t.name ()) << " ADD" << endl; - base::create (fk); - os << endl; - - post_statement (); - } - } - }; + create_table (base const& x): base (x) {} - void create_table:: - traverse (sema_rel::table& t) - { - if (pass_ == 1) + void + traverse (sema_rel::table& t) { - // In migration we always add foreign keys on pass 2. - // - if (!t.is_a ()) - tables_.insert (t.name ()); // Add it before to cover self-refs. base::traverse (t); - // Create the sequence if we have auto primary key. - // - using sema_rel::primary_key; + if (pass_ == 1) + { + // Create the sequence if we have auto primary key. + // + using sema_rel::primary_key; - sema_rel::table::names_iterator i (t.find ("")); // Special name. - primary_key* pk (i != t.names_end () - ? &dynamic_cast (i->nameable ()) - : 0); + sema_rel::table::names_iterator i (t.find ("")); // Special name. + primary_key* pk (i != t.names_end () + ? &dynamic_cast (i->nameable ()) + : 0); - if (pk != 0 && pk->auto_ ()) - { - pre_statement (); - os_ << "CREATE SEQUENCE " << - quote_id (sequence_name (t.name ())) << endl - << " START WITH 1 INCREMENT BY 1" << endl; - post_statement (); + if (pk != 0 && pk->auto_ ()) + { + pre_statement (); + os_ << "CREATE SEQUENCE " << + quote_id (sequence_name (t.name ())) << endl + << " START WITH 1 INCREMENT BY 1" << endl; + post_statement (); + } } - - return; } - - // Add foreign keys. - // - add_foreign_key fk (format_, *this); - trav_rel::unames n (fk); - names (t, n); - } + }; + entry create_table_; struct create_index: relational::create_index, context { @@ -377,31 +331,43 @@ namespace relational // Oracle can only alter certain kinds of things together but // grouped one at a time. // + if (check (at)) + { + pre_statement (); + alter_header (at.name ()); + + instance dfc (*this); + trav_rel::unames n (*dfc); + names (at, n); + os << endl; + + post_statement (); + } + if (check (at)) { pre_statement (); alter_header (at.name ()); - os << " ADD ("; + os << endl + << " ADD ("; - instance cc (emitter (), stream (), format_); - trav_rel::alter_column ac; // Override. - trav_rel::unames n; - n >> cc; - n >> ac; + instance cc (*this); + trav_rel::unames n (*cc); names (at, n); os << ")" << endl; post_statement (); } - if (check_alter_null (at, true)) + if (check_alter_column_null (at, true)) { pre_statement (); alter_header (at.name ()); - os << " MODIFY ("; + os << endl + << " MODIFY ("; bool tl (true); // (Im)perfect forwarding. - instance ac (emitter (), stream (), format_, tl); + instance ac (*this, tl); trav_rel::unames n (*ac); names (at, n); os << ")" << endl; @@ -426,9 +392,10 @@ namespace relational { pre_statement (); alter_header (at.name ()); - os << " DROP ("; + os << endl + << " DROP ("; - instance dc (emitter (), stream (), format_); + instance dc (*this); trav_rel::unames n (*dc); names (at, n); os << ")" << endl; @@ -436,20 +403,34 @@ namespace relational post_statement (); } - if (check_alter_null (at, false)) + if (check_alter_column_null (at, false)) { pre_statement (); alter_header (at.name ()); - os << " MODIFY ("; + os << endl + << " MODIFY ("; bool fl (false); // (Im)perfect forwarding. - instance ac (emitter (), stream (), format_, fl); + instance ac (*this, fl); trav_rel::unames n (*ac); names (at, n); os << ")" << endl; post_statement (); } + + if (check (at)) + { + pre_statement (); + alter_header (at.name ()); + + instance cfc (*this); + trav_rel::unames n (*cfc); + names (at, n); + os << endl; + + post_statement (); + } } }; entry alter_table_post_; diff --git a/odb/relational/pgsql/schema.cxx b/odb/relational/pgsql/schema.cxx index 575f8e4..6d44139 100644 --- a/odb/relational/pgsql/schema.cxx +++ b/odb/relational/pgsql/schema.cxx @@ -2,8 +2,6 @@ // copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file -#include - #include #include @@ -18,6 +16,7 @@ namespace relational namespace schema { namespace relational = relational::schema; + using relational::table_set; // // Drop. @@ -28,10 +27,17 @@ namespace relational drop_table (base const& x): base (x) {} virtual void - drop (sema_rel::qname const& table, bool migration) + traverse (sema_rel::table& t, bool migration) { + // For PostgreSQL we use the CASCADE clause to drop foreign keys. + // + if (pass_ != 2) + return; + + pre_statement (); os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") << - quote_id (table) << " CASCADE" << endl; + quote_id (t.name ()) << " CASCADE" << endl; + post_statement (); } }; entry drop_table_; @@ -40,21 +46,6 @@ namespace relational // Create. // - struct create_foreign_key; - - struct create_table: relational::create_table, context - { - create_table (base const& x): base (x) {} - - void - traverse (sema_rel::table&); - - private: - friend class create_foreign_key; - set tables_; // Set of tables we have already defined. - }; - entry create_table_; - struct create_column: relational::create_column, context { create_column (base const& x): base (x) {} @@ -83,84 +74,17 @@ namespace relational struct create_foreign_key: relational::create_foreign_key, context { - create_foreign_key (schema_format f, relational::create_table& ct) - : base (f, ct) - { - } - create_foreign_key (base const& x): base (x) {} virtual void - traverse (sema_rel::foreign_key& fk) - { - // 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 - // (see add_foreign_key below). - // - create_table& ct (static_cast (create_table_)); - - if (ct.tables_.find (fk.referenced_table ()) != ct.tables_.end ()) - { - base::traverse (fk); - fk.set ("pgsql-fk-defined", true); // Mark it as defined. - } - } - - virtual void - deferred () + deferrable (sema_rel::deferrable d) { os << endl - << " INITIALLY DEFERRED"; + << " INITIALLY " << d; } }; entry create_foreign_key_; - struct add_foreign_key: create_foreign_key, relational::common - { - add_foreign_key (schema_format f, relational::create_table& ct) - : create_foreign_key (f, ct), common (ct.emitter (), ct.stream ()) - { - } - - virtual void - traverse (sema_rel::foreign_key& fk) - { - if (!fk.count ("pgsql-fk-defined")) - { - sema_rel::table& t (dynamic_cast (fk.scope ())); - - pre_statement (); - - os << "ALTER TABLE " << quote_id (t.name ()) << " ADD" << endl; - base::create (fk); - os << endl; - - post_statement (); - } - } - }; - - void create_table:: - traverse (sema_rel::table& t) - { - if (pass_ == 1) - { - // In migration we always add foreign keys on pass 2. - // - if (!t.is_a ()) - tables_.insert (t.name ()); // Add it before to cover self-refs. - base::traverse (t); - return; - } - - // Add foreign keys. - // - add_foreign_key fk (format_, *this); - trav_rel::unames n (fk); - names (t, n); - } - struct create_index: relational::create_index, context { create_index (base const& x): base (x) {} diff --git a/odb/relational/processor.cxx b/odb/relational/processor.cxx index 1a57205..34c8e2d 100644 --- a/odb/relational/processor.cxx +++ b/odb/relational/processor.cxx @@ -209,7 +209,8 @@ namespace relational if (m.count ("polymorphic-ref")) { m.set ("not-null", true); - m.set ("deferred", false); + m.set ("deferrable", + sema_rel::deferrable (sema_rel::deferrable::not_deferrable)); m.set ("on-delete", sema_rel::foreign_key::cascade); } diff --git a/odb/relational/schema.cxx b/odb/relational/schema.cxx index 56fb7c0..0586e06 100644 --- a/odb/relational/schema.cxx +++ b/odb/relational/schema.cxx @@ -128,14 +128,12 @@ namespace relational instance changeset (*em, emos, f); instance dtable (*em, emos, f); - instance altable (*em, emos, f); - trav_rel::add_table adtable; // Override. + instance atable (*em, emos, f); trav_rel::qnames names; changeset >> names; names >> dtable; - names >> altable; - names >> adtable; + names >> atable; // Pass 1 and 2. // @@ -143,7 +141,7 @@ namespace relational { changeset->pass (pass); dtable->pass (pass); - altable->pass (pass); + atable->pass (pass); changeset->traverse (cs); } diff --git a/odb/relational/schema.hxx b/odb/relational/schema.hxx index 32cac41..247dca2 100644 --- a/odb/relational/schema.hxx +++ b/odb/relational/schema.hxx @@ -17,11 +17,14 @@ namespace relational { namespace schema { + typedef std::set table_set; + struct common: virtual context { typedef ::emitter emitter_type; - common (emitter_type& e, ostream& os): e_ (e), os_ (os) {} + common (emitter_type& e, ostream& os, schema_format f) + : e_ (e), os_ (os), format_ (f) {} void pre_statement () @@ -49,9 +52,32 @@ namespace relational return os_; } + public: + // Find an entity corresponding to the drop node in alter_table. + // + template + 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 (d.scope ())); + changeset& cs (dynamic_cast (at.scope ())); + model& bm (cs.base_model ()); + table* bt (bm.find (at.name ())); + assert (bt != 0); + T* b (bt->find (d.name ())); + assert (b != 0); + return *b; + } + protected: emitter_type& e_; ostream& os_; + schema_format format_; }; // @@ -64,12 +90,8 @@ namespace relational { typedef drop_column base; - drop_column (emitter_type& e, - ostream& os, - schema_format f, - bool* first = 0) - : common (e, os), - format_ (f), + drop_column (common const& c, bool* first = 0) + : common (c), first_ (first != 0 ? *first : first_data_), first_data_ (true) { @@ -79,7 +101,6 @@ namespace relational : root_context (), // @@ -Wextra context (), common (c), - format_ (c.format_), first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), first_data_ (c.first_data_) { @@ -99,15 +120,128 @@ namespace relational if (first_) first_ = false; else - os << "," << endl; + os << ","; - os << " "; + os << endl + << " "; drop_header (); os << quote_id (dc.name ()); } protected: - schema_format format_; + 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 (fk.scope ())); + + if (dropped_ != 0) + { + sema_rel::qname const& rt (fk.referenced_table ()); + sema_rel::model& m (dynamic_cast (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_; }; @@ -120,33 +254,15 @@ namespace relational enum index_type {unique, non_unique, all}; - drop_index (emitter_type& e, - ostream& os, - schema_format f, - index_type t = all) - : common (e, os), format_ (f), type_ (t) - { - } + drop_index (common const& c, index_type t = all) + : common (c), type_ (t) {} virtual void traverse (sema_rel::drop_index& di) { - using sema_rel::model; - using sema_rel::changeset; - using sema_rel::table; - using sema_rel::alter_table; - using sema_rel::index; - // Find the index we are dropping in the base model. // - alter_table& at (dynamic_cast (di.scope ())); - changeset& cs (dynamic_cast (at.scope ())); - model& bm (cs.base_model ()); - table* bt (bm.find
(at.name ())); - assert (bt != 0); - index* bi (bt->find (di.name ())); - assert (bi != 0); - traverse (*bi); + traverse (find (di)); } virtual void @@ -180,40 +296,56 @@ namespace relational } protected: - schema_format format_; 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), format_ (f) - { - } + : common (e, os, f) {} virtual void drop (sema_rel::qname const& table, bool migration) { + pre_statement (); os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") << quote_id (table) << endl; + post_statement (); } virtual void traverse (sema_rel::table& t, bool migration) { - // By default we do everything in a single, last pass. But some - // databases may require two passes. + // By default drop foreign keys referencing tables that would + // have already been dropped on the first pass. // - if (pass_ != 2) - return; + if (pass_ == 1) + { + // Drop constraints. In migration this is always done on pass 1. + // + if (migration) + { + instance dfk (*this); + trav_rel::unames n (*dfk); + names (t, n); + } + else + { + dropped_.insert (t.name ()); // Add it before to cover self-refs. - pre_statement (); - drop (t.name (), migration); - post_statement (); + instance dfk (*this, dropped_); + trav_rel::unames n (*dfk); + names (t, n); + } + } + else + drop (t.name (), migration); } virtual void @@ -247,8 +379,8 @@ namespace relational } protected: - schema_format format_; unsigned short pass_; + table_set dropped_; }; struct drop_model: trav_rel::model, common @@ -256,7 +388,7 @@ namespace relational typedef drop_model base; drop_model (emitter_type& e, ostream& os, schema_format f) - : common (e, os), format_ (f) + : common (e, os, f) { } @@ -294,31 +426,27 @@ namespace relational } protected: - schema_format format_; unsigned short pass_; }; // // Create. // - struct create_table; struct create_column: trav_rel::column, trav_rel::add_column, + trav_rel::alter_column, // Override. common { typedef create_column base; - create_column (emitter_type& e, - ostream& os, - schema_format f, + create_column (common const& c, bool override_null = true, bool* first = 0) - : common (e, os), - format_ (f), + : common (c), + override_null_ (override_null), first_ (first != 0 ? *first : first_data_), - first_data_ (true), - override_null_ (override_null) + first_data_ (true) { } @@ -326,10 +454,9 @@ namespace relational : root_context (), // @@ -Wextra context (), common (c), - format_ (c.format_), + override_null_ (c.override_null_), first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), - first_data_ (c.first_data_), - override_null_ (c.override_null_) + first_data_ (c.first_data_) { } @@ -339,9 +466,10 @@ namespace relational if (first_) first_ = false; else - os << "," << endl; + os << ","; - os << " "; + os << endl + << " "; create (c); } @@ -359,9 +487,10 @@ namespace relational if (first_) first_ = false; else - os << "," << endl; + os << ","; - os << " "; + os << endl + << " "; add_header (); create (ac); } @@ -441,21 +570,17 @@ namespace relational } protected: - schema_format format_; + bool override_null_; // Override NOT NULL in add_column. bool& first_; bool first_data_; bool add_; - bool override_null_; // Override NOT NULL in add_column. }; - struct create_primary_key: trav_rel::primary_key, virtual context + struct create_primary_key: trav_rel::primary_key, common { typedef create_primary_key base; - create_primary_key (schema_format f, create_table& ct) - : format_ (f), create_table_ (ct) - { - } + create_primary_key (common const& c): common (c) {} virtual void traverse (sema_rel::primary_key& pk) @@ -468,8 +593,7 @@ namespace relational // We will always follow a column. // - os << "," << endl - << endl; + os << "," << endl; create (pk); } @@ -502,38 +626,104 @@ namespace relational os << ")"; } - - protected: - schema_format format_; - create_table& create_table_; }; - struct create_foreign_key: trav_rel::foreign_key, virtual context + struct create_foreign_key: trav_rel::foreign_key, + trav_rel::add_foreign_key, + common { typedef create_foreign_key base; - create_foreign_key (schema_format f, create_table& ct) - : format_ (f), create_table_ (ct) + // Schema constructor. + // + 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) + { + } + + // 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) { - // We will always follow a column or another key. + // 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 + // (see add_foreign_key below). // - os << "," << endl - << endl; + if (created_->find (fk.referenced_table ()) != created_->end ()) + { + generate (fk); + fk.set (db.string () + "-fk-defined", true); // Mark it as defined. + } + } + + virtual void + generate (sema_rel::foreign_key& fk) + { + if (first_) + first_ = false; + else + os << ","; + os << endl + << " CONSTRAINT "; create (fk); } virtual void + add_header () + { + os << "ADD CONSTRAINT "; + } + + virtual void + traverse (sema_rel::add_foreign_key& afk) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl; + add (afk); + } + + virtual void + add (sema_rel::add_foreign_key& afk) + { + os << " "; + add_header (); + create (afk); + } + + virtual void create (sema_rel::foreign_key& fk) { using sema_rel::foreign_key; - os << " CONSTRAINT " << name (fk) << endl + os << name (fk) << endl << " FOREIGN KEY ("; for (foreign_key::contains_iterator i (fk.contains_begin ()); @@ -578,8 +768,8 @@ namespace relational if (fk.on_delete () != foreign_key::no_action) on_delete (fk.on_delete ()); - if (fk.deferred ()) - deferred (); + if (!fk.not_deferrable ()) + deferrable (fk.deferrable ()); } virtual string @@ -613,15 +803,69 @@ namespace relational } virtual void - deferred () + deferrable (sema_rel::deferrable d) { os << endl - << " DEFERRABLE INITIALLY DEFERRED"; + << " DEFERRABLE INITIALLY " << d; } protected: - schema_format format_; - create_table& create_table_; + table_set* created_; + bool& first_; + bool first_data_; + }; + + // Add foreign key via ALTER TABLE. + // + struct add_foreign_key: trav_rel::foreign_key, common + { + typedef add_foreign_key base; + + add_foreign_key (common const& c, table_set& created, bool* first = 0) + : common (c), + created_ (created), + first_ (first != 0 ? *first : first_data_), + first_data_ (true), + def_ (c, created_) + { + } + + add_foreign_key (add_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_), + def_ (c, created_) + { + } + + virtual void + traverse (sema_rel::foreign_key& fk) + { + if (!fk.count (db.string () + "-fk-defined")) + generate (dynamic_cast (fk.scope ()), fk); + } + + virtual void + generate (sema_rel::table& t, sema_rel::foreign_key& fk) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " ADD CONSTRAINT "; + def_->create (fk); + os << endl; + + post_statement (); + } + + protected: + table_set& created_; + bool& first_; + bool first_data_; + instance def_; }; struct create_index: trav_rel::index, common @@ -630,13 +874,8 @@ namespace relational enum index_type {unique, non_unique, all}; - create_index (emitter_type& e, - ostream& os, - schema_format f, - index_type t = all) - : common (e, os), format_ (f), type_ (t) - { - } + create_index (common const& c, index_type t = all) + : common (c), type_ (t) {} virtual void traverse (sema_rel::index& in) @@ -715,23 +954,24 @@ namespace relational } protected: - schema_format format_; index_type type_; }; - struct create_table: trav_rel::table, common + 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), format_ (f) - { - } + : common (e, os, f) {} virtual void create_pre (sema_rel::qname const& table) { - os << "CREATE TABLE " << quote_id (table) << " (" << endl; + os << "CREATE TABLE " << quote_id (table) << " ("; } virtual void @@ -741,22 +981,21 @@ namespace relational } virtual void - traverse (sema_rel::table& t) + create (sema_rel::table& t) { - // By default we do everything in a single pass. But some - // databases may require the second pass. - // - if (pass_ != 1) - return; - pre_statement (); create_pre (t.name ()); - instance c (emitter (), stream (), format_); - instance pk (format_, *this); - instance fk (format_, *this); - trav_rel::unames n; + instance c (*this); + instance 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 fk (*this, created_, pf); + + trav_rel::unames n; n >> c; n >> pk; n >> fk; @@ -769,12 +1008,37 @@ namespace relational // Create indexes. // { - instance in (emitter (), stream (), format_); + instance in (*this); trav_rel::unames n (*in); names (t, n); } } + 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 ()) + created_.insert (t.name ()); // Add it before to cover self-refs. + + create (t); + } + else + { + // Add foreign keys. + // + instance fk (*this, created_); + trav_rel::unames n (*fk); + names (t, n); + } + } + void pass (unsigned short p) { @@ -782,8 +1046,8 @@ namespace relational } protected: - schema_format format_; unsigned short pass_; + table_set created_; }; struct create_model: trav_rel::model, common @@ -791,9 +1055,7 @@ namespace relational typedef create_model base; create_model (emitter_type& e, ostream& os, schema_format f) - : common (e, os), format_ (f) - { - } + : common (e, os, f) {} // This version is only called for file schema. // @@ -818,7 +1080,6 @@ namespace relational } protected: - schema_format format_; unsigned short pass_; }; @@ -832,18 +1093,13 @@ namespace relational { typedef alter_column base; - alter_column (emitter_type& e, - ostream& os, - schema_format f, - bool pre, - bool* first = 0) - : common (e, os), - format_ (f), + 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_ (e, os, f, fl_) + def_ (c, fl_) { } @@ -851,12 +1107,11 @@ namespace relational : root_context (), // @@ -Wextra context (), common (c), - format_ (c.format_), pre_ (c.pre_), first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), first_data_ (c.first_data_), fl_ (false), - def_ (common::e_, common::os_, c.format_, fl_) + def_ (c, fl_) { } @@ -885,9 +1140,10 @@ namespace relational if (first_) first_ = false; else - os << "," << endl; + os << ","; - os << " "; + os << endl + << " "; alter_header (); alter (c); } @@ -910,7 +1166,6 @@ namespace relational } protected: - schema_format format_; bool pre_; bool& first_; bool first_data_; @@ -921,9 +1176,7 @@ namespace relational struct alter_table_common: trav_rel::alter_table, common { alter_table_common (emitter_type& e, ostream& os, schema_format f) - : common (e, os), format_ (f) - { - } + : common (e, os, f) {} template T* @@ -935,32 +1188,43 @@ namespace relational if (T* x = dynamic_cast (&i->nameable ())) return x; } - return 0; } - sema_rel::alter_column* - check_alter_null (sema_rel::alter_table& at, bool v) + sema_rel::column* + check_alter_column_null (sema_rel::alter_table& at, bool v) { - using sema_rel::alter_column; - 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 (&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 (&i->nameable ())) + { + if (!ac->null () && ac->default_ ().empty ()) + return ac; + } + } + } return 0; } virtual void alter_header (sema_rel::qname const& table) { - os << "ALTER TABLE " << quote_id (table) << endl; + os << "ALTER TABLE " << quote_id (table); } void @@ -970,7 +1234,6 @@ namespace relational } protected: - schema_format format_; unsigned short pass_; }; @@ -979,9 +1242,7 @@ namespace relational typedef alter_table_pre base; alter_table_pre (emitter_type& e, ostream& os, schema_format f) - : alter_table_common (e, os, f) - { - } + : alter_table_common (e, os, f) {} // Check if there will be any clauses in ALTER TABLE. // @@ -990,8 +1251,13 @@ namespace relational virtual bool check (sema_rel::alter_table& at) { - return check (at) || - check_alter_null (at, true); + // If changing the below test, make sure to also update tests + // in database-specific code. + // + return + check (at) || + check (at) || + check_alter_column_null (at, true); } virtual void @@ -1006,11 +1272,13 @@ namespace relational bool f (true); // Shared first flag. bool* pf (&f); // (Im)perfect forwarding. bool tl (true); // (Im)perfect forwarding. - instance cc (emitter (), stream (), format_, tl, pf); - instance ac (emitter (), stream (), format_, tl, pf); + instance cc (*this, tl, pf); + instance ac (*this, tl, pf); + instance dfk (*this, pf); trav_rel::unames n; n >> cc; n >> ac; + n >> dfk; names (at, n); os << endl; @@ -1026,7 +1294,7 @@ namespace relational // { drop_index::index_type it (drop_index::unique); - instance in (emitter (), stream (), format_, it); + instance in (*this, it); trav_rel::unames n (*in); names (at, n); } @@ -1040,7 +1308,7 @@ namespace relational // { create_index::index_type it (create_index::non_unique); - instance in (emitter (), stream (), format_, it); + instance in (*this, it); trav_rel::unames n (*in); names (at, n); } @@ -1053,9 +1321,7 @@ namespace relational typedef changeset_pre base; changeset_pre (emitter_type& e, ostream& os, schema_format f) - : common (e, os), format_ (f) - { - } + : common (e, os, f) {} // This version is only called for file migration. // @@ -1080,7 +1346,6 @@ namespace relational } protected: - schema_format format_; unsigned short pass_; }; @@ -1089,9 +1354,7 @@ namespace relational typedef alter_table_post base; alter_table_post (emitter_type& e, ostream& os, schema_format f) - : alter_table_common (e, os, f) - { - } + : alter_table_common (e, os, f) {} // Check if there will be any clauses in ALTER TABLE. // @@ -1100,8 +1363,13 @@ namespace relational virtual bool check (sema_rel::alter_table& at) { - return check (at) || - check_alter_null (at, false); + // If changing the below test, make sure to also update tests + // in database-specific code. + // + return + check (at) || + check (at) || + check_alter_column_null (at, false); } virtual void @@ -1116,11 +1384,14 @@ namespace relational bool f (true); // Shared first flag. bool* pf (&f); // (Im)perfect forwarding. bool fl (false); // (Im)perfect forwarding. - instance dc (emitter (), stream (), format_, pf); - instance ac (emitter (), stream (), format_, fl, pf); + instance dc (*this, pf); + instance ac (*this, fl, pf); + instance fk (*this, pf); + trav_rel::unames n; n >> dc; n >> ac; + n >> fk; names (at, n); os << endl; @@ -1136,7 +1407,7 @@ namespace relational // { drop_index::index_type it (drop_index::non_unique); - instance in (emitter (), stream (), format_, it); + instance in (*this, it); trav_rel::unames n (*in); names (at, n); } @@ -1150,7 +1421,7 @@ namespace relational // { create_index::index_type it (create_index::unique); - instance in (emitter (), stream (), format_, it); + instance in (*this, it); trav_rel::unames n (*in); names (at, n); } @@ -1163,9 +1434,7 @@ namespace relational typedef changeset_post base; changeset_post (emitter_type& e, ostream& os, schema_format f) - : common (e, os), format_ (f) - { - } + : common (e, os, f) {} // This version is only called for file migration. // @@ -1190,7 +1459,6 @@ namespace relational } protected: - schema_format format_; unsigned short pass_; }; diff --git a/odb/relational/source.cxx b/odb/relational/source.cxx index 210d04b..0eb3cd3 100644 --- a/odb/relational/source.cxx +++ b/odb/relational/source.cxx @@ -2503,7 +2503,7 @@ traverse_object (type& c) if (poly && !poly_derived) { os << "void " << traits << "::" << endl - << "discriminator_ (statements_type & sts," << endl + << "discriminator_ (statements_type& sts," << endl << "const id_type& id," << endl << "discriminator_type* pd"; diff --git a/odb/relational/sqlite/schema.cxx b/odb/relational/sqlite/schema.cxx index 9d3f859..92f6764 100644 --- a/odb/relational/sqlite/schema.cxx +++ b/odb/relational/sqlite/schema.cxx @@ -33,6 +33,9 @@ namespace relational traverse (sema_rel::add_column& ac) { using sema_rel::alter_table; + using sema_rel::add_column; + using sema_rel::add_foreign_key; + alter_table& at (static_cast (ac.scope ())); pre_statement (); @@ -40,8 +43,37 @@ namespace relational os << "ALTER TABLE " << quote_id (at.name ()) << endl << " ADD COLUMN "; create (ac); - os << endl; + // SQLite doesn't support adding foreign keys other than inline + // via a column definition. See if we can handle any. + // + add_foreign_key* afk (0); + + for (add_column::contained_iterator i (ac.contained_begin ()); + i != ac.contained_end (); + ++i) + { + if ((afk = dynamic_cast (&i->key ()))) + { + // Check that it is a single-column foreign key. Also make + // sure the column and foreign key are from the same changeset. + // + if (afk->contains_size () != 1 || &ac.scope () != &afk->scope ()) + afk = 0; + else + break; + } + } + + if (afk != 0) + { + os << " CONSTRAINT " << quote_id (afk->name ()) << " REFERENCES " << + quote_id (afk->referenced_table ().uname ()) << " (" << + quote_id (afk->referenced_columns ()[0]) << ")"; + afk->set ("sqlite-fk-defined", true); // Mark it as defined. + } + + os << endl; post_statement (); } @@ -60,6 +92,17 @@ namespace relational { create_foreign_key (base const& x): base (x) {} + virtual void + traverse (sema_rel::foreign_key& fk) + { + // In SQLite, all constraints are defined as part of a table. + // + os << "," << endl + << " CONSTRAINT "; + + create (fk); + } + virtual string table_name (sema_rel::foreign_key& fk) { @@ -71,6 +114,22 @@ namespace relational }; entry create_foreign_key_; + struct create_table: relational::create_table, context + { + create_table (base const& x): base (x) {} + + void + traverse (sema_rel::table& t) + { + // For SQLite we do everything in a single pass since there + // is no way to add constraints later. + // + if (pass_ == 1) + create (t); + } + }; + entry create_table_; + struct create_index: relational::create_index, context { create_index (base const& x): base (x) {} @@ -115,6 +174,24 @@ namespace relational }; entry drop_index_; + struct drop_table: relational::drop_table, context + { + drop_table (base const& x): base (x) {} + + virtual void + traverse (sema_rel::table& t, bool migration) + { + // In SQLite there is no way to drop foreign keys except as part + // of the table. + // + if (pass_ != 2) + return; + + drop (t.name (), migration); + } + }; + entry drop_table_; + struct alter_table_pre: relational::alter_table_pre, context { alter_table_pre (base const& x): base (x) {} @@ -124,11 +201,8 @@ namespace relational { // SQLite can only add a single column per ALTER TABLE statement. // - instance cc (emitter (), stream (), format_); - trav_rel::alter_column ac; // Override. - trav_rel::unames n; - n >> cc; - n >> ac; + instance cc (*this); + trav_rel::unames n (*cc); names (at, n); // SQLite does not support altering columns. @@ -141,6 +215,18 @@ namespace relational "' in table '" << at.name () << "'" << endl; throw operation_failed (); } + + // SQLite does not support dropping constraints. + // + if (sema_rel::drop_foreign_key* dfk = + check (at)) + { + cerr << "error: SQLite does not support dropping of foreign keys" + << endl; + cerr << "info: first dropped foreign key is '" << dfk->name () << + "' in table '" << at.name () << "'" << endl; + throw operation_failed (); + } } }; entry alter_table_pre_; @@ -162,6 +248,26 @@ namespace relational "' in table '" << at.name () << "'" << endl; throw operation_failed (); } + + // SQLite doesn't support adding foreign keys other than inline + // via a column definition. See if there are any that we couldn't + // handle that way. + // + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + sema_rel::add_foreign_key* afk ( + dynamic_cast (&i->nameable ())); + + if (afk == 0 || afk->count ("sqlite-fk-defined")) + continue; + + cerr << "error: SQLite does not support adding foreign keys" + << endl; + cerr << "info: first added foreign key is '" << afk->name () << + "' in table '" << at.name () << "'" << endl; + throw operation_failed (); + } } }; entry alter_table_post_; diff --git a/odb/semantics/relational/deferrable.cxx b/odb/semantics/relational/deferrable.cxx new file mode 100644 index 0000000..6aecb12 --- /dev/null +++ b/odb/semantics/relational/deferrable.cxx @@ -0,0 +1,56 @@ +// file : odb/semantics/relational/deferrable.cxx +// copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#include +#include + +#include + +using namespace std; + +namespace semantics +{ + namespace relational + { + static const char* deferrable_[] = + { + "NOT DEFERRABLE", + "IMMEDIATE", + "DEFERRED" + }; + + string deferrable:: + string () const + { + return deferrable_[v_]; + } + + ostream& + operator<< (ostream& os, deferrable const& v) + { + return os << v.string (); + } + + istream& + operator>> (istream& is, deferrable& v) + { + string s; + is >> s; + + if (!is.fail ()) + { + if (s == "not_deferrable" || s == "NOT DEFERRABLE") + v = deferrable::not_deferrable; + else if (s == "immediate" || s == "IMMEDIATE") + v = deferrable::immediate; + else if (s == "deferred" || s == "DEFERRED") + v = deferrable::deferred; + else + is.setstate (istream::failbit); + } + + return is; + } + } +} diff --git a/odb/semantics/relational/deferrable.hxx b/odb/semantics/relational/deferrable.hxx new file mode 100644 index 0000000..87d696c --- /dev/null +++ b/odb/semantics/relational/deferrable.hxx @@ -0,0 +1,42 @@ +// file : odb/semantics/relational/deferrable.hxx +// copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_SEMANTICS_RELATIONAL_DEFERRABLE_HXX +#define ODB_SEMANTICS_RELATIONAL_DEFERRABLE_HXX + +#include +#include + +namespace semantics +{ + namespace relational + { + struct deferrable + { + enum value + { + not_deferrable, + immediate, + deferred + }; + + deferrable (value v = value (0)) : v_ (v) {} + operator value () const {return v_;} + + std::string + string () const; + + private: + value v_; + }; + + std::ostream& + operator<< (std::ostream&, deferrable const&); + + std::istream& + operator>> (std::istream&, deferrable&); + } +} + +#endif // ODB_SEMANTICS_RELATIONAL_DEFERRABLE_HXX diff --git a/odb/semantics/relational/foreign-key.cxx b/odb/semantics/relational/foreign-key.cxx index 0407858..8188e1f 100644 --- a/odb/semantics/relational/foreign-key.cxx +++ b/odb/semantics/relational/foreign-key.cxx @@ -50,7 +50,7 @@ namespace semantics : key (k, s, g), referenced_table_ (k.referenced_table_), referenced_columns_ (k.referenced_columns_), - deferred_ (k.deferred_), + deferrable_ (k.deferrable_), on_delete_ (k.on_delete_) { } @@ -58,7 +58,7 @@ namespace semantics foreign_key:: foreign_key (xml::parser& p, uscope& s, graph& g) : key (p, s, g), - deferred_ (p.attribute ("deferred", false)), + deferrable_ (p.attribute ("deferrable", deferrable_type ())), on_delete_ (p.attribute ("on-delete", no_action)) { using namespace xml; @@ -94,8 +94,8 @@ namespace semantics { key::serialize_attributes (s); - if (deferred ()) - s.attribute ("deferred", true); + if (deferrable () != deferrable_type::not_deferrable) + s.attribute ("deferrable", deferrable ()); if (on_delete () != no_action) s.attribute ("on-delete", on_delete ()); diff --git a/odb/semantics/relational/foreign-key.hxx b/odb/semantics/relational/foreign-key.hxx index 50a3fae..458683f 100644 --- a/odb/semantics/relational/foreign-key.hxx +++ b/odb/semantics/relational/foreign-key.hxx @@ -7,6 +7,7 @@ #include +#include #include #include @@ -38,10 +39,15 @@ namespace semantics } public: + typedef relational::deferrable deferrable_type; + + deferrable_type + deferrable () const {return deferrable_;} + bool - deferred () const + not_deferrable () const { - return deferred_; + return deferrable_ == deferrable_type::not_deferrable; } enum action_type @@ -51,19 +57,16 @@ namespace semantics }; action_type - on_delete () const - { - return on_delete_; - } + on_delete () const {return on_delete_;} public: foreign_key (string const& id, qname const& referenced_table, - bool deferred, + deferrable_type deferrable, action_type on_delete = no_action) : key (id), referenced_table_ (referenced_table), - deferred_ (deferred), + deferrable_ (deferrable), on_delete_ (on_delete) { } @@ -93,7 +96,7 @@ namespace semantics private: qname referenced_table_; columns referenced_columns_; - bool deferred_; + deferrable_type deferrable_; action_type on_delete_; }; @@ -108,7 +111,7 @@ namespace semantics public: add_foreign_key (string const& id, qname const& rt, - bool d, + deferrable_type d, action_type od = no_action) : foreign_key (id, rt, d, od) {} add_foreign_key (foreign_key const& fk, uscope& s, graph& g) -- cgit v1.1