// file : odb/relational/mssql/schema.cxx // copyright : Copyright (c) 2009-2017 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file #include #include #include using namespace std; namespace relational { namespace mssql { namespace schema { namespace relational = relational::schema; using relational::table_set; struct sql_emitter: relational::sql_emitter { sql_emitter (const base& x): base (x) {} virtual void post () { if (!first_) // Ignore empty statements. { os << ';' << endl << "GO" << endl << endl; } } }; entry sql_emitter_; // // File. // struct sql_file: relational::sql_file, context { sql_file (const base& x): base (x) {} virtual void prologue () { // Suppress the (x rows affected) messages from sqlcmd for DML // statements. We only use DML for schema version management. // if ((model == 0 || model->version () != 0) && !options.suppress_schema_version ()) os << "SET NOCOUNT ON;" << endl << endl; } }; entry sql_file_; // // Drop. // struct drop_column: relational::drop_column, context { drop_column (base const& x): base (x) {} virtual void traverse (sema_rel::drop_column& dc) { if (first_) first_ = false; else os << "," << endl << " "; os << quote_id (dc.name ()); } }; entry drop_column_; struct drop_foreign_key: relational::drop_foreign_key, context { drop_foreign_key (base const& x): base (x) {} virtual void drop (sema_rel::table& t, sema_rel::foreign_key& fk) { bool migration (dropped_ == 0); if (migration) { if (fk.not_deferrable ()) pre_statement (); else { if (format_ != schema_format::sql) return; 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::drop_foreign_key& dfk) { // Find the foreign key we are dropping in the base model. // sema_rel::foreign_key& fk (find (dfk)); bool c (!fk.not_deferrable () && !in_comment); if (c && format_ != schema_format::sql) return; if (!first_) os << (c ? "" : ",") << endl << " "; if (c) os << "/* "; os << quote_id (fk.name ()); if (c) os << " */"; 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; } } }; entry drop_foreign_key_; struct drop_index: relational::drop_index, context { drop_index (base const& x): base (x) {} virtual void drop (sema_rel::index& in) { sema_rel::table& t (static_cast (in.scope ())); os << "DROP INDEX " << name (in) << " ON " << quote_id (t.name ()) << endl; } }; entry drop_index_; struct drop_table: relational::drop_table, context { drop_table (base const& x): base (x) {} virtual void 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 (name.string ()) << ", " << quote_string ("U") << ") IS NOT NULL" << endl << " "; os << "DROP TABLE " << quote_id (name) << endl; post_statement (); } }; entry drop_table_; // // Create. // struct create_column: relational::create_column, context { create_column (base const& x): base (x) {} virtual void traverse (sema_rel::add_column& ac) { if (first_) first_ = false; else os << "," << endl << " "; create (ac); } virtual void auto_ (sema_rel::primary_key&) { os << " IDENTITY"; } }; entry create_column_; struct create_foreign_key: relational::create_foreign_key, context { create_foreign_key (base const& x): base (x) {} void diagnose (sema_rel::foreign_key& fk) { if (fk.on_delete () != sema_rel::foreign_key::no_action) { cerr << "warning: foreign key '" << fk.name () << "' has " << "ON DELETE clause but is disabled in SQL Server due to lack " "of deferrable constraint support" << endl; cerr << "info: consider using non-deferrable foreign keys (" << "--fkeys-deferrable-mode)" << endl; } } virtual void traverse_create (sema_rel::foreign_key& fk) { // SQL Server 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::traverse_create (fk); else { diagnose (fk); // Don't bloat C++ code with comment strings if we are // generating embedded schema. // if (format_ != schema_format::sql) return; os << endl << " /*" << endl << " CONSTRAINT "; create (fk); os << endl << " */"; } } virtual void traverse_add (sema_rel::foreign_key& fk) { bool c (!fk.not_deferrable () && !in_comment); if (c) diagnose (fk); if (c && format_ != schema_format::sql) return; if (!first_) os << (c ? "" : ",") << endl << " "; if (c) os << "/*" << endl << " "; os << "CONSTRAINT "; create (fk); 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 deferrable (sema_rel::deferrable) { // This will still be called to output the comment. } }; entry create_foreign_key_; struct create_table: relational::create_table, context { create_table (base const& x): base (x) {} // See if there are any undefined foreign keys that are not // deferrable. // bool check_undefined_fk_deferrable_only (sema_rel::table& t) { for (sema_rel::table::names_iterator i (t.names_begin ()); i != t.names_end (); ++i) { using sema_rel::foreign_key; if (foreign_key* fk = dynamic_cast (&i->nameable ())) { if (!fk->count ("mssql-fk-defined") && fk->not_deferrable ()) return false; } } return true; } virtual void traverse (sema_rel::table& t) { if (pass_ == 1) base::traverse (t); else { // Add undefined foreign keys. // if (check_undefined_fk (t)) { bool deferrable (check_undefined_fk_deferrable_only (t)); if (!deferrable || format_ == schema_format::sql) { if (deferrable) { os << "/*" << endl; in_comment = true; } else pre_statement (); os << "ALTER TABLE " << quote_id (t.name ()) << endl << " ADD "; instance cfk (*this); trav_rel::unames n (*cfk); names (t, n); os << endl; if (deferrable) { in_comment = false; os << "*/" << endl << endl; } else post_statement (); } } } } }; entry create_table_; // // Alter. // struct alter_column: relational::alter_column, context { alter_column (base const& x): base (x) {} virtual void traverse (sema_rel::column& c) { // Relax (NULL) in pre and tighten (NOT NULL) in post. // if (pre_ != c.null ()) return; using sema_rel::table; table& at (static_cast (c.scope ())); pre_statement (); os << "ALTER TABLE " << quote_id (at.name ()) << endl << " ALTER COLUMN "; alter (c); os << endl; post_statement (); } }; 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) { 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 (); os << "ALTER TABLE " << quote_id (at.name ()) << 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 (); os << "ALTER TABLE " << quote_id (at.name ()) << endl << " ADD "; instance cc (*this); trav_rel::unames n (*cc); names (at, n); os << endl; post_statement (); } // For ALTER COLUMN, SQL Server can only have one per ALTER TABLE. // { bool tl (true); // (Im)perfect forwarding. instance ac (*this, tl); trav_rel::unames n (*ac); names (at, n); } } }; 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) { 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) { // SQL Server can only alter one kind of thing at a time. // if (check (at)) { pre_statement (); os << "ALTER TABLE " << quote_id (at.name ()) << endl << " DROP COLUMN "; instance dc (*this); trav_rel::unames n (*dc); names (at, n); os << endl; post_statement (); } // For ALTER COLUMN, SQL Server can only have one per ALTER TABLE. // { bool fl (false); // (Im)perfect forwarding. 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 (); os << "ALTER TABLE " << quote_id (at.name ()) << 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_; // // Schema version table. // struct version_table: relational::version_table, context { version_table (base const& x): base (x) {} virtual void create_table () { pre_statement (); os << "IF OBJECT_ID(" << quote_string (table_.string ()) << ", " << quote_string ("U") << ") IS NULL" << endl << " CREATE TABLE " << qt_ << " (" << endl << " " << qn_ << " VARCHAR(256) NOT NULL PRIMARY KEY," << endl << " " << qv_ << " BIGINT NOT NULL," << endl << " " << qm_ << " BIT NOT NULL)" << endl; post_statement (); } virtual void create (sema_rel::version v) { pre_statement (); os << "IF NOT EXISTS (SELECT 1 FROM " << qt_ << " WHERE " << qn_ << " = " << qs_ << ")" << endl << " INSERT INTO " << qt_ << " (" << endl << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; post_statement (); } }; entry version_table_; } } }