diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2011-10-24 16:32:51 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2011-10-24 16:32:51 +0200 |
commit | 08a47c70ad517b80b72914d47d547463f576bcd3 (patch) | |
tree | 8a6ab07cf05e8668ea3c91735dfe97e2a98f3f05 /odb/relational/model.hxx | |
parent | a976183dc95a8b7a9bd7a308c3ea94f08982c426 (diff) |
Generate database schema from database model instead of C++ model
We now first create the so-called database model from C++ model and
then use that to generate the database schema. The new approach also
adds more general support for primary/foreign keys, including multi-
column keys. Finally, for MySQL we now generate out-of-line foreign
key definitions. Because MySQL does not support deferred constraints
checking, deferred foreign keys are written commented out, for
documentation.
Diffstat (limited to 'odb/relational/model.hxx')
-rw-r--r-- | odb/relational/model.hxx | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/odb/relational/model.hxx b/odb/relational/model.hxx new file mode 100644 index 0000000..3d8cc4b --- /dev/null +++ b/odb/relational/model.hxx @@ -0,0 +1,528 @@ +// file : odb/relational/model.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MODEL_HXX +#define ODB_RELATIONAL_MODEL_HXX + +#include <set> +#include <cassert> +#include <sstream> + +#include <odb/emitter.hxx> + +#include <odb/semantics/relational.hxx> + +#include <odb/relational/common.hxx> +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace model + { + typedef std::set<std::string> tables; + + struct object_columns: object_columns_base, virtual context + { + typedef object_columns base; + + object_columns (sema_rel::model& model, + sema_rel::table& table, + string const& prefix = string ()) + : model_ (model), + table_ (table), + prefix_ (prefix), + id_override_ (false) + { + } + + virtual void + traverse_object (semantics::class_& c) + { + if (context::top_object != &c) + { + // We are in one of the bases. Set the id_prefix to its + // (unqualified) name. + // + string t (id_prefix_); + id_prefix_ = c.name () + "::"; + object_columns_base::traverse_object (c); + id_prefix_ = t; + } + else + object_columns_base::traverse_object (c); + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + string t (id_prefix_); + + if (m != 0) + // Member of a composite type. Add the data member to id_prefix. + // + if (!id_override_) + id_prefix_ += m->name () + "."; + else + id_override_ = false; + else + // Composite base. Add its unqualified name to id_prefix. + // + id_prefix_ += c.name () + "::"; + + object_columns_base::traverse_composite (m, c); + + id_prefix_ = t; + } + + virtual void + traverse (semantics::data_member& m, + semantics::class_& c, + std::string const& kp, + std::string const& dn) + { + // This overrides the member name for a composite container value + // or key. + // + if (!kp.empty ()) + { + id_prefix_ = kp + "."; + id_override_ = true; + } + + object_columns_base::traverse (m, c, kp, dn); + } + + using object_columns_base::traverse; + + virtual bool + traverse_column (semantics::data_member& m, string const& name, bool) + { + // Ignore inverse object pointers. + // + if (inverse (m)) + return false; + + string id (id_prefix_ + (prefix_.empty () ? m.name () : prefix_)); + + sema_rel::column& c ( + model_.new_node<sema_rel::column> ( + id, column_type (m, prefix_), context::null (m, prefix_))); + c.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::names> (table_, c, name); + + // An id member cannot have a default value. + // + if (!context::id (m)) + { + string const& d (default_ (m)); + + if (!d.empty ()) + c.default_ (d); + } + + // If we have options, add them. + // + string const& o (column_options (m, prefix_)); + + if (!o.empty ()) + c.options (o); + + constraints (m, name, id, c); + reference (m, name, id, c); + + return true; + } + + virtual string + default_null (semantics::data_member&) + { + return "NULL"; + } + + virtual string + default_bool (semantics::data_member&, bool v) + { + // Most databases do not support boolean literals. Those that + // do should override this. + // + return (v ? "1" : "0"); + } + + virtual string + default_integer (semantics::data_member&, unsigned long long v, bool neg) + { + std::ostringstream ostr; + ostr << (neg ? "-" : "") << v; + return ostr.str (); + } + + virtual string + default_float (semantics::data_member&, double v) + { + std::ostringstream ostr; + ostr << v; + return ostr.str (); + } + + virtual string + default_string (semantics::data_member&, string const& v) + { + return quote_string (v); + } + + virtual string + default_enum (semantics::data_member&, + tree /*enumerator*/, + string const& /*name*/) + { + // Has to be implemented by the database-specific override. + // + assert (false); + } + + virtual void + constraints (semantics::data_member& m, + string const& /* name */, + string const& /* id */, + sema_rel::column& c) + { + if (!id (m)) + return; + + sema_rel::primary_key& pk ( + model_.new_node<sema_rel::primary_key> (m.count ("auto"))); + pk.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::contains> (pk, c); + + // In most databases the primary key constraint can be manipulated + // without an explicit name. So we use the special empty name for + // primary keys in order not to clash with columns and other + // constraints. If the target database does not support unnamed + // primary key manipulation, then the database-specific code will + // have to come up with a suitable name. + // + model_.new_edge<sema_rel::names> (table_, pk, ""); + } + + virtual void + reference (semantics::data_member& m, + string const& name, + string const& id, + sema_rel::column& c) + { + semantics::class_* p (object_pointer (member_utype (m, prefix_))); + + if (p == 0) + return; + + sema_rel::foreign_key& fk ( + model_.new_node<sema_rel::foreign_key> ( + id, + table_name (*p), + true)); // deferred + + fk.set ("cxx-node", static_cast<semantics::node*> (&m)); + + fk.referenced_columns ().push_back (column_name (*id_member (*p))); + + model_.new_edge<sema_rel::contains> (fk, c); + + // Derive the constraint name. Generally, we want it to be based + // on the column name. This is straightforward for single column + // references. In case of the composite ids, we will need to use + // the column prefix which is based on the data member name, + // unless overridden (see how the column pragma works for members + // of composite value types). @@ This is a TODO. Perhaps use the + // up-to-and-including composite member prefix? Though it can be + // empty. + // + model_.new_edge<sema_rel::names> (table_, fk, name + "_fk"); + } + + protected: + string + default_ (semantics::data_member&); + + protected: + sema_rel::model& model_; + sema_rel::table& table_; + string prefix_; + string id_prefix_; + bool id_override_; + }; + + struct member_create: object_members_base, virtual context + { + typedef member_create base; + + member_create (sema_rel::model& model) + : object_members_base (false, true, false), model_ (model) + { + } + + virtual void + traverse_object (semantics::class_& c) + { + if (context::top_object != &c) + { + // We are in one of the bases. Set the id_prefix to its + // (unqualified) name. + // + string t (id_prefix_); + id_prefix_ = c.name () + "::"; + object_members_base::traverse_object (c); + id_prefix_ = t; + } + else + { + // Top-level object. Set its id as a prefix. + // + id_prefix_ = string (c.fq_name (), 2) + "::"; + object_members_base::traverse_object (c); + } + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + string t (id_prefix_); + + if (m != 0) + // Member of a composite type. Add the data member to id_prefix. + // + id_prefix_ += m->name () + "."; + else + // Composite base. Add its unqualified name to id_prefix. + // + id_prefix_ += c.name () + "::"; + + object_members_base::traverse_composite (m, c); + + id_prefix_ = t; + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& ct) + { + using semantics::type; + using semantics::data_member; + + // Ignore inverse containers of object pointers. + // + if (inverse (m, "value")) + return; + + container_kind_type ck (container_kind (ct)); + type& vt (container_vt (ct)); + + string const& name (table_name (m, table_prefix_)); + + // Add the [] decorator to distinguish this id from non-container + // ids (we don't want to ever end up comparing, for example, an + // object table to a container table). + // + string id (id_prefix_ + m.name () + "[]"); + + sema_rel::container_table& t ( + model_.new_node<sema_rel::container_table> (id)); + t.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::names> (model_, t, name); + + // object_id (simple value, for now) + // + string id_name (column_name (m, "id", "object_id")); + { + instance<object_columns> oc (model_, t, "id"); + oc->traverse_column (m, id_name, true); + } + + // Foreign key for the object id. + // + { + sema_rel::foreign_key& fk ( + model_.new_node<sema_rel::foreign_key> ( + id + ".id", + table_name (*context::top_object), + false, // immediate + sema_rel::foreign_key::cascade)); + + fk.set ("cxx-node", static_cast<semantics::node*> (&m)); + + fk.referenced_columns ().push_back ( + column_name ( + *id_member (*context::top_object))); + + // All the columns we have in this table so far are for the + // object id. + // + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); + ++i) + model_.new_edge<sema_rel::contains> ( + fk, dynamic_cast<sema_rel::column&> (i->nameable ())); + + // Derive the constraint name. See the comment for the other + // foreign key code above. + // + model_.new_edge<sema_rel::names> (t, fk, id_name + "_fk"); + } + + // index (simple value) + // + string index_name; + bool ordered (ck == ck_ordered && !unordered (m)); + if (ordered) + { + instance<object_columns> oc (model_, t, "index"); + index_name = column_name (m, "index", "index"); + oc->traverse_column (m, index_name, true); + } + + // key (simple or composite value) + // + if (ck == ck_map || ck == ck_multimap) + { + type& kt (container_kt (ct)); + + if (semantics::class_* ckt = composite_wrapper (kt)) + { + instance<object_columns> oc (model_, t); + oc->traverse (m, *ckt, "key", "key"); + } + else + { + instance<object_columns> oc (model_, t, "key"); + string const& name (column_name (m, "key", "key")); + oc->traverse_column (m, name, true); + } + } + + // value (simple or composite value) + // + { + if (semantics::class_* cvt = composite_wrapper (vt)) + { + instance<object_columns> oc (model_, t); + oc->traverse (m, *cvt, "value", "value"); + } + else + { + instance<object_columns> oc (model_, t, "value"); + string const& name (column_name (m, "value", "value")); + oc->traverse_column (m, name, true); + } + } + + // Create indexes. + // + using sema_rel::index; + using sema_rel::column; + + { + index& i (model_.new_node<index> (id + ".id")); + i.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::contains> ( + i, dynamic_cast<column&> (t.find (id_name)->nameable ())); + + //@@ Once id can be composite, we need to revise this (see + // a comment for the foreign key generation above). + // + model_.new_edge<sema_rel::names> ( + model_, i, name + '_' + id_name + "_i"); + } + + if (ordered) + { + index& i (model_.new_node<index> (id + ".index")); + i.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::contains> ( + i, dynamic_cast<column&> (t.find (index_name)->nameable ())); + + // This is always a single column (simple value). + // + model_.new_edge<sema_rel::names> ( + model_, i, name + '_' + index_name + "_i"); + } + } + + protected: + sema_rel::model& model_; + string id_prefix_; + }; + + struct class_: traversal::class_, virtual context + { + typedef class_ base; + + class_ (sema_rel::model& model) + : model_ (model) + { + } + + virtual void + traverse (type& c) + { + if (c.file () != unit.file ()) + return; + + if (!object (c) || abstract (c)) + return; + + string const& name (table_name (c)); + + // If the table with this name was already created, assume the + // user knows what they are doing and skip it. + // + if (tables_.count (name)) + { + c.set ("model-range-first", model_.names_end ()); + c.set ("model-range-last", model_.names_end ()); + return; + } + + string id (c.fq_name (), 2); // Remove leading '::'. + + sema_rel::object_table& t( + model_.new_node<sema_rel::object_table> (id)); + + t.set ("cxx-node", static_cast<semantics::node*> (&c)); + + model_.new_edge<sema_rel::names> (model_, t, name); + + sema_rel::model::names_iterator begin (--model_.names_end ()); + + { + instance<object_columns> oc (model_, t); + oc->traverse (c); + } + + tables_.insert (name); + + // Create tables for members. + // + { + instance<member_create> mc (model_); + mc->traverse (c); + } + + sema_rel::model::names_iterator end (--model_.names_end ()); + + c.set ("model-range-first", begin); + c.set ("model-range-last", end); + } + + protected: + sema_rel::model& model_; + tables tables_; + }; + } +} + +#endif // ODB_RELATIONAL_MODEL_HXX |