// file : odb/relational/schema.hxx // copyright : Copyright (c) 2009-2012 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file #ifndef ODB_RELATIONAL_SCHEMA_HXX #define ODB_RELATIONAL_SCHEMA_HXX #include #include #include #include #include #include namespace relational { namespace schema { struct common: virtual context { typedef ::emitter emitter_type; common (emitter_type& e, ostream& os): e_ (e), os_ (os) {} void pre_statement () { e_.pre (); diverge (os_); } void post_statement () { restore (); e_.post (); } emitter_type& emitter () const { return e_; } ostream& stream () const { return os_; } protected: emitter_type& e_; ostream& os_; }; // // Drop. // struct drop_index: trav_rel::index, common { typedef drop_index base; drop_index (emitter_type& e, ostream& os, schema_format f) : common (e, os), format_ (f) { } virtual string name (sema_rel::index& in) { return quote_id (in.name ()); } virtual string table_name (sema_rel::index& in) { return quote_id (static_cast (in.scope ()).name ()); } virtual void drop (string const& /*name*/, string const& /*table*/) { // Most database systems drop indexes together with the table. // // os << "DROP INDEX IF EXISTS " << quote_id (name) << " ON " << // table << endl; } virtual void traverse (sema_rel::index& in) { pre_statement (); drop (name (in), table_name (in)); post_statement (); } protected: schema_format format_; }; struct drop_table: trav_rel::table, common { typedef drop_table base; drop_table (emitter_type& e, ostream& os, schema_format f) : common (e, os), format_ (f) { } virtual void drop (sema_rel::qname const& table) { os << "DROP TABLE IF EXISTS " << quote_id (table) << endl; } virtual void traverse (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; // Drop indexes. // { instance in (emitter (), stream (), format_); trav_rel::unames n (*in); names (t, n); } pre_statement (); drop (t.name ()); post_statement (); } void pass (unsigned short p) { pass_ = p; } protected: schema_format format_; unsigned short pass_; }; struct drop_model: trav_rel::model, common { typedef drop_model base; drop_model (emitter_type& e, ostream& os, schema_format f) : common (e, os), format_ (f) { } // This version is only called for file schema. // virtual void traverse (sema_rel::model& m) { traverse (m.names_begin (), m.names_end ()); } virtual void traverse (sema_rel::model::names_iterator begin, sema_rel::model::names_iterator end) { // Traverse named entities in the reverse order. This way we // drop them in the order opposite to creating. // if (begin != end) { for (--end;; --end) { dispatch (*end); if (begin == end) break; } } } void pass (unsigned short p) { pass_ = p; } protected: schema_format format_; unsigned short pass_; }; // // Create. // struct create_table; struct create_column: trav_rel::column, virtual context { typedef create_column base; create_column (schema_format f, create_table& ct) : format_ (f), create_table_ (ct), first_ (true) { } virtual void traverse (sema_rel::column& c) { if (first_) first_ = false; else os << "," << endl; create (c); } virtual void create (sema_rel::column& c) { using sema_rel::column; // See if this column is (part of) a primary key. // sema_rel::primary_key* pk (0); for (column::contained_iterator i (c.contained_begin ()); i != c.contained_end (); ++i) { if ((pk = dynamic_cast (&i->key ()))) break; } os << " " << quote_id (c.name ()) << " "; type (c, pk != 0 && pk->auto_ ()); 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_ (c); if (!c.options ().empty ()) os << " " << c.options (); } virtual void type (sema_rel::column& c, bool /*auto*/) { os << c.type (); } virtual void null (sema_rel::column& c) { if (!c.null ()) os << " NOT NULL"; } virtual void primary_key () { os << " PRIMARY KEY"; } virtual void auto_ (sema_rel::column&) { } protected: schema_format format_; create_table& create_table_; bool first_; }; struct create_primary_key: trav_rel::primary_key, virtual context { typedef create_primary_key base; create_primary_key (schema_format f, create_table& ct) : format_ (f), create_table_ (ct) { } virtual void traverse (sema_rel::primary_key& pk) { // Single-column primary keys are generated inline in the // column declaration. // if (pk.contains_size () == 1) return; // We will always follow a column. // os << "," << endl << endl; create (pk); } virtual void create (sema_rel::primary_key& pk) { using sema_rel::primary_key; // By default we create unnamed primary key constraint. // os << " PRIMARY KEY ("; for (primary_key::contains_iterator i (pk.contains_begin ()); i != pk.contains_end (); ++i) { if (pk.contains_size () > 1) { if (i != pk.contains_begin ()) os << ","; os << endl << " "; } os << quote_id (i->column ().name ()); } os << ")"; } protected: schema_format format_; create_table& create_table_; }; struct create_foreign_key: trav_rel::foreign_key, virtual context { typedef create_foreign_key base; create_foreign_key (schema_format f, create_table& ct) : format_ (f), create_table_ (ct) { } virtual void traverse (sema_rel::foreign_key& fk) { // We will always follow a column or another key. // os << "," << endl << endl; create (fk); } virtual void create (sema_rel::foreign_key& fk) { using sema_rel::foreign_key; os << " CONSTRAINT " << name (fk) << endl << " FOREIGN KEY ("; for (foreign_key::contains_iterator i (fk.contains_begin ()); i != fk.contains_end (); ++i) { if (fk.contains_size () > 1) { if (i != fk.contains_begin ()) os << ","; os << endl << " "; } os << quote_id (i->column ().name ()); } os << ")" << endl << " REFERENCES " << table_name (fk) << " ("; foreign_key::columns const& refs (fk.referenced_columns ()); for (foreign_key::columns::const_iterator i (refs.begin ()); i != refs.end (); ++i) { if (refs.size () > 1) { if (i != refs.begin ()) os << ","; os << endl << " "; } os << quote_id (*i); } os << ")"; if (fk.on_delete () != foreign_key::no_action) on_delete (fk.on_delete ()); if (fk.deferred ()) deferred (); } virtual string name (sema_rel::foreign_key& fk) { return quote_id (fk.name ()); } virtual string table_name (sema_rel::foreign_key& fk) { return quote_id (fk.referenced_table ()); } virtual void on_delete (sema_rel::foreign_key::action a) { using sema_rel::foreign_key; switch (a) { case foreign_key::cascade: { os << endl << " ON DELETE CASCADE"; break; } case foreign_key::no_action: break; } } virtual void deferred () { os << endl << " DEFERRABLE INITIALLY DEFERRED"; } protected: schema_format format_; create_table& create_table_; }; struct create_index: trav_rel::index, common { typedef create_index base; create_index (emitter_type& e, ostream& os, schema_format f) : common (e, os), format_ (f) { } virtual void traverse (sema_rel::index& in) { pre_statement (); create (in); post_statement (); } virtual string name (sema_rel::index& in) { return quote_id (in.name ()); } virtual string table_name (sema_rel::index& in) { return quote_id (static_cast (in.scope ()).name ()); } virtual void columns (sema_rel::index& in) { using sema_rel::index; for (index::contains_iterator i (in.contains_begin ()); i != in.contains_end (); ++i) { if (in.contains_size () > 1) { if (i != in.contains_begin ()) os << ","; os << endl << " "; } os << quote_id (i->column ().name ()); if (!i->options ().empty ()) os << ' ' << i->options (); } } virtual void create (sema_rel::index& in) { // Default implementation that ignores the method. // os << "CREATE "; if (!in.type ().empty ()) os << in.type () << ' '; os << "INDEX " << name (in) << endl << " ON " << table_name (in) << " ("; columns (in); os << ")" << endl; if (!in.options ().empty ()) os << ' ' << in.options () << endl; } protected: schema_format format_; }; struct create_table: trav_rel::table, common { typedef create_table base; create_table (emitter_type& e, ostream& os, schema_format f) : common (e, os), format_ (f) { } virtual void create_pre (sema_rel::qname const& table) { os << "CREATE TABLE " << quote_id (table) << " (" << endl; } virtual void create_post () { os << ")" << endl; } virtual void traverse (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 (format_, *this); instance pk (format_, *this); instance fk (format_, *this); trav_rel::unames n; n >> c; n >> pk; n >> fk; names (t, n); create_post (); post_statement (); // Create indexes. // { instance in (emitter (), stream (), format_); trav_rel::unames n (*in); names (t, n); } } void pass (unsigned short p) { pass_ = p; } protected: schema_format format_; unsigned short pass_; }; struct create_model: trav_rel::model, common { typedef create_model base; create_model (emitter_type& e, ostream& os, schema_format f) : common (e, os), format_ (f) { } // This version is only called for file schema. // virtual void traverse (sema_rel::model& m) { traverse (m.names_begin (), m.names_end ()); } virtual void traverse (sema_rel::model::names_iterator begin, sema_rel::model::names_iterator end) { for (; begin != end; ++begin) dispatch (*begin); } void pass (unsigned short p) { pass_ = p; } protected: schema_format format_; unsigned short pass_; }; // // SQL output. // struct sql_emitter: emitter, virtual context { typedef sql_emitter base; virtual void pre () { first_ = true; } virtual void line (const std::string& l) { if (first_ && !l.empty ()) first_ = false; else os << endl; os << l; } virtual void post () { if (!first_) // Ignore empty statements. os << ';' << endl << endl; } protected: bool first_; }; struct sql_file: virtual context { typedef sql_file base; virtual void prologue () { } virtual void epilogue () { } }; // // C++ output. // struct cxx_emitter: emitter, virtual context { typedef cxx_emitter base; void pass (unsigned short p) { empty_ = true; pass_ = p; new_pass_ = true; if (pass_ == 1) empty_passes_ = 0; // New set of passes. // Assume this pass is empty. // empty_passes_++; } // Did this pass produce anything? // bool empty () const { return empty_; } virtual void pre () { first_ = true; } virtual void line (const string& l) { if (l.empty ()) return; // Ignore empty lines. if (first_) { first_ = false; // If this line starts a new pass, then output the switch/case // blocks. // if (new_pass_) { new_pass_ = false; empty_ = false; empty_passes_--; // This pass is not empty. // Output case statements for empty preceeding passes, if any. // if (empty_passes_ != 0) { unsigned short s (pass_ - empty_passes_); if (s == 1) os << "switch (pass)" << "{"; else os << "return true;" // One more pass. << "}"; for (; s != pass_; ++s) os << "case " << s << ":" << endl; os << "{"; empty_passes_ = 0; } if (pass_ == 1) os << "switch (pass)" << "{"; else os << "return true;" // One more pass. << "}"; os << "case " << pass_ << ":" << endl << "{"; } os << "db.execute ("; } else os << strlit (line_ + '\n') << endl; line_ = l; } virtual void post () { if (!first_) // Ignore empty statements. os << strlit (line_) << ");"; } private: std::string line_; bool first_; bool empty_; bool new_pass_; unsigned short pass_; unsigned short empty_passes_; // Number of preceding empty passes. }; struct cxx_object: virtual context { typedef cxx_object base; //@@ (im)-perfect forwarding. // static schema_format format_embedded; cxx_object () : stream_ (*emitter_), drop_model_ (*emitter_, stream_, format_embedded), drop_table_ (*emitter_, stream_, format_embedded), create_model_ (*emitter_, stream_, format_embedded), create_table_ (*emitter_, stream_, format_embedded) { init (); } cxx_object (cxx_object const&) : root_context (), //@@ -Wextra context (), stream_ (*emitter_), drop_model_ (*emitter_, stream_, format_embedded), drop_table_ (*emitter_, stream_, format_embedded), create_model_ (*emitter_, stream_, format_embedded), create_table_ (*emitter_, stream_, format_embedded) { init (); } void init () { drop_model_ >> drop_names_; drop_names_ >> drop_table_; create_model_ >> create_names_; create_names_ >> create_table_; } void traverse (semantics::class_& c) { typedef sema_rel::model::names_iterator iterator; iterator begin (c.get ("model-range-first")); iterator end (c.get ("model-range-last")); if (begin == model->names_end ()) return; // This class doesn't have any model entities (e.g., // a second class mapped to the same table). ++end; // Transform the range from [begin, end] to [begin, end). string const& type (class_fq_name (c)); string traits ("access::object_traits< " + type + " >"); // create_schema () // os << "bool " << traits << "::" << endl << "create_schema (database& db, unsigned short pass, bool drop)" << "{" << "ODB_POTENTIALLY_UNUSED (db);" << "ODB_POTENTIALLY_UNUSED (pass);" << "ODB_POTENTIALLY_UNUSED (drop);" << endl; // Drop. // if (!options.omit_drop ()) { bool close (false); os << "if (drop)" << "{"; for (unsigned short pass (1); pass < 3; ++pass) { emitter_->pass (pass); drop_model_->pass (pass); drop_table_->pass (pass); drop_model_->traverse (begin, end); close = close || !emitter_->empty (); } if (close) // Close the last case and the switch block. os << "return false;" << "}" // case << "}"; // switch os << "}"; } // Create. // if (!options.omit_create ()) { bool close (false); if (options.omit_drop ()) os << "if (!drop)"; else os << "else"; os << "{"; for (unsigned short pass (1); pass < 3; ++pass) { emitter_->pass (pass); create_model_->pass (pass); create_table_->pass (pass); create_model_->traverse (begin, end); close = close || !emitter_->empty (); } if (close) // Close the last case and the switch block. os << "return false;" << "}" // case << "}"; // switch os << "}"; } os << "return false;" << "}"; os << "static const schema_catalog_entry" << endl << "schema_catalog_entry_" << flat_name (type) << "_ (" << endl << strlit (options.schema_name ()) << "," << endl << "&" << traits << "::create_schema);" << endl; } private: instance emitter_; emitter_ostream stream_; trav_rel::qnames drop_names_; instance drop_model_; instance drop_table_; trav_rel::qnames create_names_; instance create_model_; instance create_table_; }; } } #endif // ODB_RELATIONAL_SCHEMA_HXX