// 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 #include #include using namespace std; namespace relational { namespace oracle { namespace schema { namespace relational = relational::schema; 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_table: relational::drop_table, context { drop_table (base const& x): base (x) {} virtual void traverse (sema_rel::table& t, bool migration) { if (pass_ != 2) return; using sema_rel::primary_key; qname const& table (t.name ()); 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 (table)); string qs (pk != 0 && pk->auto_ () ? quote_id (sequence_name (table)) : ""); if (migration) { pre_statement (); os << "DROP TABLE " << qt << " CASCADE CONSTRAINTS" << 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 (); } } }; 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); } }; 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) { // 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. } } }; 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 ("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 (); } } }; 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); // 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_ ()) { 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); } 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 ()); return quote_id (n); } }; entry create_index_; 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 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 (); alter_header (at.name ()); os << " ADD ("; instance cc (emitter (), stream (), format_); trav_rel::alter_column ac; // Override. trav_rel::unames n; n >> cc; n >> ac; names (at, n); os << ")" << endl; post_statement (); } if (check_alter_null (at, true)) { pre_statement (); alter_header (at.name ()); os << " MODIFY ("; bool tl (true); // (Im)perfect forwarding. instance ac (emitter (), stream (), format_, 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 (); alter_header (at.name ()); os << " DROP ("; instance dc (emitter (), stream (), format_); trav_rel::unames n (*dc); names (at, n); os << ")" << endl; post_statement (); } if (check_alter_null (at, false)) { pre_statement (); alter_header (at.name ()); os << " MODIFY ("; bool fl (false); // (Im)perfect forwarding. instance ac (emitter (), stream (), format_, fl); trav_rel::unames n (*ac); names (at, n); os << ")" << endl; post_statement (); } } }; entry alter_table_post_; } } }