// file : odb/relational/oracle/schema.cxx // copyright : Copyright (c) 2009-2013 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file #include #include // pair #include #include #include #include using namespace std; namespace relational { namespace oracle { 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 line (const std::string& l) { // SQLPlus doesn't like empty line in the middle of a statement. // if (!l.empty ()) { base::line (l); last_ = l; } } virtual void post () { if (!first_) // Ignore empty statements. { if (last_ == "END;") os << endl << '/' << endl << endl; else os << ';' << endl << endl; } } private: string last_; }; entry sql_emitter_; // // File. // struct sql_file: relational::sql_file, context { sql_file (const base& x): base (x) {} virtual void prologue () { // Quiet down SQLPlus and make sure it exits with an error // code if there is an error. // os << "SET FEEDBACK OFF;" << endl << "WHENEVER SQLERROR EXIT FAILURE;" << endl << "WHENEVER OSERROR EXIT FAILURE;" << endl << endl; } virtual void epilogue () { os << "EXIT;" << 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 traverse (sema_rel::drop_foreign_key& dfk) { os << endl; drop (dfk); } }; entry drop_foreign_key_; struct drop_index: relational::drop_index, context { drop_index (base const& x): base (x) {} virtual string name (sema_rel::index& in) { // In Oracle, index names can be qualified with the schema. // sema_rel::table& t (static_cast (in.scope ())); sema_rel::qname n (t.name ().qualifier ()); n.append (in.name ()); return quote_id (n); } }; 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) { 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); string qt (quote_id (t.name ())); string qs (pk != 0 && pk->auto_ () ? quote_id (qname::from_string (pk->extra ()["sequence"])) : ""); if (migration) { pre_statement (); os << "DROP TABLE " << qt << endl; post_statement (); // Drop the sequence if we have auto primary key. // if (!qs.empty ()) { pre_statement (); os << "DROP SEQUENCE " << qs << endl; post_statement (); } } else { // Oracle has no IF EXISTS conditional for dropping objects. The // PL/SQL approach below seems to be the least error-prone and the // most widely used of the alternatives. // pre_statement (); os << "BEGIN" << endl << " BEGIN" << endl << " EXECUTE IMMEDIATE 'DROP TABLE " << qt << " CASCADE " << "CONSTRAINTS';" << endl << " EXCEPTION" << endl << " WHEN OTHERS THEN" << endl << " IF SQLCODE != -942 THEN RAISE; END IF;" << endl << " END;" << endl; // Drop the sequence if we have auto primary key. // if (!qs.empty ()) { os << " BEGIN" << endl << " EXECUTE IMMEDIATE 'DROP SEQUENCE " << qs << "';" << endl << " EXCEPTION" << endl << " WHEN OTHERS THEN" << endl << " IF SQLCODE != -2289 THEN RAISE; END IF;" << endl << " END;" << endl; } os << "END;" << endl; post_statement (); } } virtual void traverse (sema_rel::table& t, bool migration) { // For migration drop foreign keys explicitly in pre-migration. // if (migration) { base::traverse (t, migration); return; } // For schema creation we use the CASCADE clause to drop foreign // keys. // if (pass_ != 2) return; drop (t, migration); } }; entry drop_table_; // // Create. // static sema_rel::uname truncate (location const& l, const char* kind, sema_rel::uname n, bool w) { if (n.size () > 30) { if (w) warn (l) << kind << " name '" << n << "' is longer than 30 " << "characters and will be truncated" << endl; n.resize (30); } return n; } static sema_rel::qname truncate (location const& l, const char* kind, sema_rel::qname const& n, bool w) { // Don't bother verifying the schema name since that is // specified explicitly and in a single place. // qname r (n.qualifier ()); r.append (truncate (l, kind, n.uname (), w)); return r; } template struct scope { typedef std::map > map; scope (const char* k, const char* p, bool w) : kind_ (k), prag_ (p), warn_ (w) {} void check (location const& l, N const& n) { N tn (truncate (l, kind_, n, warn_)); pair r ( map_.insert (make_pair (tn, make_pair (n, l)))); if (r.second) return; error (l) << kind_ << " name '" << tn << "' conflicts with an " << "already defined " << kind_ << " name" << endl; if (tn != n) info (l) << kind_ << " name '" << tn << "' is truncated '" << n << "'" << endl; N const& n1 (r.first->second.first); location const& l1 (r.first->second.second); info (l1) << "conflicting " << kind_ << " is defined here" << endl; if (tn != n) info (l1) << "conflicting " << kind_ << " name '" << tn << "' is truncated '" << n1 << "'" << endl; info (l) << "use #pragma db " << prag_ << " to change one of " << "the names" << endl; throw operation_failed (); } void clear () {map_.clear ();} const char* kind_; const char* prag_; bool warn_; map map_; }; struct scopes { scopes (bool warn) : tables ("table", "table", warn), fkeys ("foreign key", "column", warn), // Change column name. indexes ("index", "index", warn), sequences ("sequence", "table", warn), // Change table name. columns ("column", "column", warn) {} // In Oracle, all these entities are in their own name spaces, // as in an index and a foreign key with the same name do not // conflict. // scope tables; scope fkeys; // Global but can't have schema. scope indexes; scope sequences; scope columns; }; struct create_column: relational::create_column, context { create_column (base const& x): base (x) {} virtual void traverse (sema_rel::column& c) { // Check name trunction and conflicts. // if (scopes* s = static_cast (context::extra)) s->columns.check (c.get ("cxx-location"), c.name ()); base::traverse (c); } virtual void traverse (sema_rel::add_column& ac) { if (first_) first_ = false; else os << "," << endl << " "; create (ac); } virtual void constraints (sema_rel::column& c, sema_rel::primary_key* pk) { // Oracle wants DEFAULT before NULL even though we can end // up with mouthfulls like DEFAULT NULL NULL. // if (!c.default_ ().empty ()) os << " DEFAULT " << c.default_ (); null (c); // If this is a single-column primary key, generate it inline. // if (pk != 0 && pk->contains_size () == 1) primary_key (); if (pk != 0 && pk->auto_ ()) auto_ (*pk); } }; entry create_column_; struct create_foreign_key: relational::create_foreign_key, context { create_foreign_key (base const& x): base (x) {} virtual void traverse_create (sema_rel::foreign_key& fk) { // Check name trunction and conflicts. // if (scopes* s = static_cast (context::extra)) s->fkeys.check (fk.get ("cxx-location"), fk.name ()); base::traverse_create (fk); } virtual void traverse_add (sema_rel::foreign_key& fk) { // Check name trunction and conflicts. // if (scopes* s = static_cast (context::extra)) s->fkeys.check (fk.get ("cxx-location"), fk.name ()); os << endl << " ADD CONSTRAINT "; create (fk); } }; entry create_foreign_key_; struct create_index: relational::create_index, context { create_index (base const& x): base (x) {} virtual string name (sema_rel::index& in) { // In Oracle, index names can be qualified with the schema. // sema_rel::table& t (static_cast (in.scope ())); sema_rel::qname n (t.name ().qualifier ()); n.append (in.name ()); // Check name trunction and conflicts. // if (scopes* s = static_cast (context::extra)) s->indexes.check (in.get ("cxx-location"), n); return quote_id (n); } }; entry create_index_; struct create_table: relational::create_table, context { create_table (base const& x): base (x) {} void traverse (sema_rel::table& t) { // Check name trunction and conflicts. // if (scopes* s = static_cast (context::extra)) { if (pass_ == 1) { s->tables.check (t.get ("cxx-location"), t.name ()); s->columns.clear (); } } base::traverse (t); 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); if (pk != 0 && pk->auto_ ()) { // Already qualified with the table's schema, if any. // sema_rel::qname n ( qname::from_string (pk->extra ()["sequence"])); if (scopes* s = static_cast (context::extra)) s->sequences.check (pk->get ("cxx-location"), n); pre_statement (); os_ << "CREATE SEQUENCE " << quote_id (n) << endl << " START WITH 1 INCREMENT BY 1" << endl; post_statement (); } } } }; entry create_table_; struct create_model: relational::create_model, context { create_model (base const& x): base (x) {} void traverse (sema_rel::model& m) { scopes s (options.oracle_warn_truncation ()); context::extra = &s; base::traverse (m); context::extra = 0; } }; entry create_model_; // // 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; if (first_) first_ = false; else os << "," << endl << " "; os << quote_id (c.name ()) << (c.null () ? " NULL" : " NOT NULL"); } }; entry alter_column_; struct alter_table_pre: relational::alter_table_pre, context { alter_table_pre (base const& x): base (x) {} virtual void alter (sema_rel::alter_table& at) { // Oracle can only alter certain kinds of things together but // grouped one at a time. // if (check (at)) { pre_statement (); os << "ALTER TABLE " << quote_id (at.name ()); instance dfc (*this); trav_rel::unames n (*dfc); names (at, n); os << endl; 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 (); } if (check_alter_column_null (at, true)) { pre_statement (); os << "ALTER TABLE " << quote_id (at.name ()) << endl << " MODIFY ("; bool tl (true); // (Im)perfect forwarding. instance ac (*this, tl); trav_rel::unames n (*ac); names (at, n); os << ")" << endl; post_statement (); } } }; entry alter_table_pre_; struct alter_table_post: relational::alter_table_post, context { alter_table_post (base const& x): base (x) {} virtual void alter (sema_rel::alter_table& at) { // Oracle can only alter certain kinds of things together but // grouped one at a time. // if (check (at)) { pre_statement (); os << "ALTER TABLE " << quote_id (at.name ()) << endl << " DROP ("; instance dc (*this); trav_rel::unames n (*dc); names (at, n); os << ")" << endl; post_statement (); } if (check_alter_column_null (at, false)) { pre_statement (); os << "ALTER TABLE " << quote_id (at.name ()) << endl << " MODIFY ("; bool fl (false); // (Im)perfect forwarding. instance ac (*this, fl); trav_rel::unames n (*ac); names (at, n); os << ")" << endl; post_statement (); } if (check (at)) { pre_statement (); os << "ALTER TABLE " << quote_id (at.name ()); instance cfc (*this); trav_rel::unames n (*cfc); names (at, n); os << endl; post_statement (); } } }; entry alter_table_post_; // // Schema version table. // struct version_table: relational::version_table, context { version_table (base const& x) : base (x) { // If the schema name is empty, replace it with a single space // to workaround the VARCHAR2 empty/NULL issue. // if (qs_ == "''") qs_ = "' '"; } virtual void create_table () { pre_statement (); os << "BEGIN" << endl << " EXECUTE IMMEDIATE 'CREATE TABLE " << qt_ << " (" << endl << " " << qn_ << " VARCHAR2(512) NOT NULL PRIMARY KEY," << endl << " " << qv_ << " NUMBER(20) NOT NULL," << endl << " " << qm_ << " NUMBER(1) NOT NULL)';" << endl << "EXCEPTION" << endl << " WHEN OTHERS THEN" << endl << " IF SQLCODE != -955 THEN RAISE; END IF;" << endl << "END;" << endl; post_statement (); } virtual void create (sema_rel::version v) { pre_statement (); os << "MERGE INTO " << qt_ << " USING DUAL ON (" << qn_ << " = " << qs_ << ")" << endl << " WHEN NOT MATCHED THEN INSERT (" << endl << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; post_statement (); } }; entry version_table_; } } }