diff options
Diffstat (limited to 'odb/odb/relational/mssql/schema.cxx')
-rw-r--r-- | odb/odb/relational/mssql/schema.cxx | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/odb/odb/relational/mssql/schema.cxx b/odb/odb/relational/mssql/schema.cxx new file mode 100644 index 0000000..c5f6bc1 --- /dev/null +++ b/odb/odb/relational/mssql/schema.cxx @@ -0,0 +1,651 @@ +// file : odb/relational/mssql/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +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> 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> 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> 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<sema_rel::foreign_key> (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> 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<sema_rel::table&> (in.scope ())); + + os << "DROP INDEX " << name (in) << " ON " << + quote_id (t.name ()) << endl; + } + }; + entry<drop_index> 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> 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> 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> 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<foreign_key*> (&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<create_foreign_key> 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> 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<table&> (c.scope ())); + + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ALTER COLUMN "; + alter (c); + os << endl; + + post_statement (); + } + }; + entry<alter_column> 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<drop_foreign_key*> (&i->nameable ())) + { + foreign_key& fk (find<foreign_key> (*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<sema_rel::drop_foreign_key> (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<drop_foreign_key> 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<sema_rel::add_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD "; + + instance<create_column> 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<alter_column> ac (*this, tl); + trav_rel::unames n (*ac); + names (at, n); + } + } + }; + entry<alter_table_pre> 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<add_foreign_key*> (&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<sema_rel::drop_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " DROP COLUMN "; + + instance<drop_column> 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<alter_column> ac (*this, fl); + trav_rel::unames n (*ac); + names (at, n); + } + + if (check<sema_rel::add_foreign_key> (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<create_foreign_key> 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> 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> version_table_; + } + } +} |