summaryrefslogtreecommitdiff
path: root/odb/odb/relational/model.hxx
diff options
context:
space:
mode:
Diffstat (limited to 'odb/odb/relational/model.hxx')
-rw-r--r--odb/odb/relational/model.hxx868
1 files changed, 868 insertions, 0 deletions
diff --git a/odb/odb/relational/model.hxx b/odb/odb/relational/model.hxx
new file mode 100644
index 0000000..fdfa8fd
--- /dev/null
+++ b/odb/odb/relational/model.hxx
@@ -0,0 +1,868 @@
+// file : odb/relational/model.hxx
+// license : GNU GPL v3; see accompanying LICENSE file
+
+#ifndef ODB_RELATIONAL_MODEL_HXX
+#define ODB_RELATIONAL_MODEL_HXX
+
+#include <map>
+#include <set>
+#include <cassert>
+#include <sstream>
+
+#include <odb/semantics/relational.hxx>
+
+#include <odb/relational/common.hxx>
+#include <odb/relational/context.hxx>
+
+namespace relational
+{
+ namespace model
+ {
+ typedef std::set<qname> tables;
+ typedef std::map<qname, semantics::node*> deleted_table_map;
+ typedef std::map<uname, semantics::data_member*> deleted_column_map;
+
+ struct object_columns: object_columns_base, virtual context
+ {
+ typedef object_columns base;
+
+ object_columns (sema_rel::model& model,
+ sema_rel::table& table,
+ bool object)
+ : model_ (model),
+ table_ (table),
+ object_ (object),
+ pkey_ (0),
+ 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_ = class_name (c) + "::";
+ 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_ += class_name (c) + "::";
+
+ object_columns_base::traverse_composite (m, c);
+
+ id_prefix_ = t;
+ }
+
+ virtual void
+ traverse (semantics::data_member& m,
+ semantics::type& t,
+ string const& kp,
+ string const& dn,
+ semantics::class_* to = 0)
+ {
+ // This overrides the member name for a composite container value
+ // or key.
+ //
+ if (!kp.empty ())
+ {
+ semantics::class_* c (object_pointer (t));
+ if (composite_wrapper (c == 0 ? t : utype (*id_member (*c))))
+ {
+ id_prefix_ = kp + ".";
+ id_override_ = true;
+ }
+ }
+
+ object_columns_base::traverse (m, t, kp, dn, to);
+ }
+
+ using object_columns_base::traverse;
+
+ virtual bool
+ traverse_column (semantics::data_member& m, string const& name, bool)
+ {
+ if (semantics::data_member* m = deleted_member (member_path_))
+ {
+ table_.get<deleted_column_map> ("deleted-map")[name] = m;
+ return false;
+ }
+
+ string col_id (id_prefix_ +
+ (key_prefix_.empty () ? m.name () : key_prefix_));
+
+ sema_rel::column& c (
+ model_.new_node<sema_rel::column> (col_id, type (m), null (m)));
+ c.set ("cxx-location", m.location ());
+ c.set ("member-path", member_path_);
+ model_.new_edge<sema_rel::unames> (table_, c, name);
+
+ // An id member cannot have a default value.
+ //
+ if (!object_columns_base::id ())
+ {
+ string const& d (default_ (m));
+
+ if (!d.empty ())
+ c.default_ (d);
+ }
+
+ // If we have options, add them.
+ //
+ string const& o (column_options (m, key_prefix_));
+
+ if (!o.empty ())
+ c.options (o);
+
+ constraints (m, name, col_id, c);
+ return true;
+ }
+
+ virtual string
+ type (semantics::data_member&)
+ {
+ return object_columns_base::column_type ();
+ }
+
+ virtual bool
+ null (semantics::data_member&)
+ {
+ return !object_columns_base::id () && object_columns_base::null ();
+ }
+
+ 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);
+ return string ();
+ }
+
+ virtual void
+ primary_key (sema_rel::primary_key&)
+ {
+ }
+
+ virtual void
+ constraints (semantics::data_member& m,
+ string const& /* name */,
+ string const& /* id */,
+ sema_rel::column& c)
+ {
+ if (object_)
+ {
+ if (semantics::data_member* idm = id ())
+ {
+ if (pkey_ == 0)
+ {
+ pkey_ = &model_.new_node<sema_rel::primary_key> (
+ m.count ("auto"));
+ pkey_->set ("cxx-location", idm->location ());
+
+ // 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::unames> (table_, *pkey_, "");
+ primary_key (*pkey_);
+ }
+
+ model_.new_edge<sema_rel::contains> (*pkey_, c);
+ }
+ }
+ }
+
+ virtual void
+ traverse_pointer (semantics::data_member& m, semantics::class_& c)
+ {
+ // Ignore inverse object pointers.
+ //
+ if (inverse (m, key_prefix_))
+ return;
+
+ if (deleted (member_path_))
+ {
+ // Still traverse it as columns so that we can populate the
+ // deleted map.
+ //
+ object_columns_base::traverse_pointer (m, c);
+ return;
+ }
+
+ // Get the position of the last column.
+ //
+ sema_rel::table::names_iterator i (table_.names_end ());
+
+ while (i != table_.names_begin ())
+ {
+ --i;
+ if (i->nameable ().is_a<sema_rel::column> ())
+ break;
+ }
+
+ // Traverse the object pointer as columns.
+ //
+ object_columns_base::traverse_pointer (m, c);
+
+ // Get to the first column that we have added.
+ //
+ if (i != table_.names_end ())
+ ++i; // Next column.
+ else
+ i = table_.names_begin ();
+
+ foreign_key (m, c, i);
+ }
+
+ virtual void
+ traverse_points_to (semantics::data_member& m, semantics::class_& c)
+ {
+ if (deleted (member_path_))
+ {
+ // Still traverse it as columns so that we can populate the
+ // deleted map.
+ //
+ object_columns_base::traverse_points_to (m, c);
+ return;
+ }
+
+ // Get the position of the last column.
+ //
+ sema_rel::table::names_iterator i (table_.names_end ());
+
+ while (i != table_.names_begin ())
+ {
+ --i;
+ if (i->nameable ().is_a<sema_rel::column> ())
+ break;
+ }
+
+ // Traverse the data member as columns.
+ //
+ object_columns_base::traverse_points_to (m, c);
+
+ // Get to the first column that we have added.
+ //
+ if (i != table_.names_end ())
+ ++i; // Next column.
+ else
+ i = table_.names_begin ();
+
+ foreign_key (m, c, i);
+ }
+
+ virtual void
+ foreign_key (semantics::data_member& m,
+ semantics::class_& c,
+ sema_rel::table::names_iterator i)
+ {
+ using sema_rel::column;
+ using sema_rel::foreign_key;
+
+ string id (id_prefix_ +
+ (key_prefix_.empty () ? m.name () : key_prefix_));
+
+ deferrable def (
+ m.get<deferrable> ("deferrable",
+ options.fkeys_deferrable_mode ()[db]));
+
+ foreign_key::action_type on_delete (
+ m.get<foreign_key::action_type> (
+ "on-delete", foreign_key::no_action));
+
+ foreign_key& fk (
+ model_.new_node<foreign_key> (id, table_name (c), def, on_delete));
+
+ fk.set ("cxx-location", m.location ());
+
+ bool simple;
+
+ // Get referenced columns.
+ //
+ {
+ data_member_path& id (*id_member (c));
+
+ instance<object_columns_list> ocl;
+ ocl->traverse (id);
+
+ for (object_columns_list::iterator i (ocl->begin ());
+ i != ocl->end (); ++i)
+ fk.referenced_columns ().push_back (i->name);
+
+ simple = (fk.referenced_columns ().size () == 1);
+ }
+
+ // Get referencing columns.
+ //
+ for (; i != table_.names_end (); ++i)
+ {
+ if (column* c = dynamic_cast<column*> (&i->nameable ()))
+ model_.new_edge<sema_rel::contains> (fk, *c);
+ else
+ break;
+ }
+
+ // 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 a composite id, we will need to use the
+ // column prefix which is based on the data member name, unless
+ // overridden by the user. In the latter case the prefix can be
+ // empty, in which case we will just fall back on the member's
+ // public name.
+ //
+ string name;
+
+ if (simple)
+ name = fk.contains_begin ()->column ().name ();
+ else
+ {
+ string p (column_prefix (m, key_prefix_, default_name_).prefix);
+
+ if (p.empty ())
+ p = public_name_db (m);
+ else if (p[p.size () - 1] == '_')
+ p.resize (p.size () - 1); // Remove trailing underscore.
+
+ name = column_prefix_.prefix + p;
+ }
+
+ model_.new_edge<sema_rel::unames> (
+ table_, fk, fkey_name (table_.name (), name));
+ }
+
+ protected:
+ string
+ default_ (semantics::data_member&);
+
+ protected:
+ sema_rel::model& model_;
+ sema_rel::table& table_;
+ bool object_;
+ sema_rel::primary_key* pkey_;
+ string id_prefix_;
+ bool id_override_;
+ };
+
+ struct object_indexes: traversal::class_, virtual context
+ {
+ typedef object_indexes base;
+
+ object_indexes (sema_rel::model& model, sema_rel::table& table)
+ : model_ (model), table_ (table)
+ {
+ *this >> inherits_ >> *this;
+ }
+
+ object_indexes (object_indexes const& x)
+ : root_context (), context (), //@@ -Wextra
+ model_ (x.model_), table_ (x.table_)
+ {
+ *this >> inherits_ >> *this;
+ }
+
+ virtual void
+ traverse (type& c)
+ {
+ if (!object (c)) // Ignore transient bases.
+ return;
+
+ // Polymorphic bases get their own tables.
+ //
+ if (!polymorphic (c))
+ inherits (c);
+
+ indexes& ins (c.get<indexes> ("index"));
+
+ for (indexes::iterator i (ins.begin ()); i != ins.end (); ++i)
+ {
+ // Using index name as its id.
+ //
+ sema_rel::index& in (
+ model_.new_node<sema_rel::index> (
+ i->name, i->type, i->method, i->options));
+ in.set ("cxx-location", location (i->loc));
+ model_.new_edge<sema_rel::unames> (table_, in, i->name);
+
+ for (index::members_type::iterator j (i->members.begin ());
+ j != i->members.end (); ++j)
+ {
+ using sema_rel::column;
+
+ index::member& im (*j);
+
+ semantics::type* t (&utype (*im.path.back ()));
+
+ if (semantics::class_* ptr = object_pointer (*t))
+ t = &utype (*id_member (*ptr));
+
+ if (type* comp = composite_wrapper (*t))
+ {
+ // Composite value. Get the list of the columns. Note that
+ // the column prefix needs to contain all the components.
+ //
+ instance<object_columns_list> ocl (
+ column_prefix (im.path, true));
+ ocl->traverse (*comp);
+
+ for (object_columns_list::iterator i (ocl->begin ());
+ i != ocl->end (); ++i)
+ {
+ column* c (table_.find<column> (i->name));
+ assert (c != 0);
+ model_.new_edge<sema_rel::contains> (in, *c, im.options);
+ }
+ }
+ else
+ {
+ // Simple value. Get the column name and look it up in the
+ // table.
+ //
+ column* c (table_.find<column> (column_name (im.path)));
+ assert (c != 0);
+ model_.new_edge<sema_rel::contains> (in, *c, im.options);
+ }
+ }
+ }
+ }
+
+ private:
+ sema_rel::model& model_;
+ sema_rel::table& table_;
+
+ traversal::inherits inherits_;
+ };
+
+ 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_pointer (semantics::data_member&, semantics::class_&)
+ {
+ // We don't want to traverse composite id.
+ }
+
+ 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_ = class_name (c) + "::";
+ object_members_base::traverse_object (c);
+ id_prefix_ = t;
+ }
+ else
+ {
+ // Top-level object. Set its id as a prefix.
+ //
+ id_prefix_ = string (class_fq_name (c), 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_ += class_name (c) + "::";
+
+ object_members_base::traverse_composite (m, c);
+
+ id_prefix_ = t;
+ }
+
+ virtual string
+ table_options (semantics::data_member& m, semantics::type& ct)
+ {
+ return context::table_options (m, ct);
+ }
+
+ virtual void
+ traverse_container (semantics::data_member& m, semantics::type& ct)
+ {
+ using semantics::type;
+ using semantics::data_member;
+
+ using sema_rel::column;
+
+ // Ignore inverse containers of object pointers.
+ //
+ if (inverse (m, "value"))
+ return;
+
+ container_kind_type ck (container_kind (ct));
+ qname const& name (table_name (m, table_prefix_));
+
+ // Ignore deleted container members.
+ //
+ if (semantics::data_member* m = deleted_member (member_path_))
+ {
+ model_.get<deleted_table_map> ("deleted-map")[name] = m;
+ return;
+ }
+
+ // 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::table& t (model_.new_node<sema_rel::table> (id));
+ t.set ("cxx-location", m.location ());
+ t.set ("member-path", member_path_);
+ t.set ("deleted-map", deleted_column_map ());
+ model_.new_edge<sema_rel::qnames> (model_, t, name);
+
+ t.options (table_options (m, ct));
+ t.extra ()["kind"] = "container";
+
+ // object_id
+ //
+ {
+ bool f (false); //@@ (im)persfect forwarding.
+ instance<object_columns> oc (model_, t, f);
+ oc->traverse (m, container_idt (m), "id", "object_id");
+ }
+
+ // Foreign key and index for the object id. Keep this foreign
+ // key first since we reply on this information to lookup the
+ // corresponding object table.
+ //
+ {
+ // Derive the name prefix. See the comment for the other foreign
+ // key code above.
+ //
+ // Note also that id_name can be a column prefix (if id is
+ // composite), in which case it can be empty. In this case
+ // we just fallback on the default name.
+ //
+ // Finally, this is a top-level column, so there is no column
+ // prefix.
+ //
+ string id_name (
+ column_name (m, "id", "object_id", column_prefix ()));
+
+ if (id_name.empty ())
+ id_name = "object_id";
+
+ // Foreign key.
+ //
+ sema_rel::foreign_key& fk (
+ model_.new_node<sema_rel::foreign_key> (
+ id + ".id",
+ table_name (*context::top_object),
+ sema_rel::deferrable::not_deferrable,
+ sema_rel::foreign_key::cascade));
+ fk.set ("cxx-location", m.location ());
+ model_.new_edge<sema_rel::unames> (
+ t, fk, fkey_name (t.name (), id_name));
+
+ // Get referenced columns.
+ //
+ {
+ data_member_path& id (*id_member (*context::top_object));
+
+ instance<object_columns_list> ocl;
+ ocl->traverse (id);
+
+ for (object_columns_list::iterator i (ocl->begin ());
+ i != ocl->end (); ++i)
+ fk.referenced_columns ().push_back (i->name);
+ }
+
+ // All the columns we have in this table so far are for the
+ // object id. Add them to the foreign key.
+ //
+ for (sema_rel::table::names_iterator i (t.names_begin ());
+ i != t.names_end ();
+ ++i)
+ {
+ if (column* c = dynamic_cast<column*> (&i->nameable ()))
+ model_.new_edge<sema_rel::contains> (fk, *c);
+ }
+
+ // Index. See if we have a custom index.
+ //
+ index* sin (m.count ("id-index") ? &m.get<index> ("id-index") : 0);
+ sema_rel::index* in (0);
+
+ if (sin != 0)
+ {
+ in = &model_.new_node<sema_rel::index> (
+ id + ".id", sin->type, sin->method, sin->options);
+ in->set ("cxx-location", location (sin->loc));
+ }
+ else
+ {
+ in = &model_.new_node<sema_rel::index> (id + ".id");
+ in->set ("cxx-location", m.location ());
+ }
+
+ model_.new_edge<sema_rel::unames> (
+ t,
+ *in,
+ sin != 0 && !sin->name.empty ()
+ ? sin->name
+ : index_name (name, id_name));
+
+ // All the columns we have in this table so far are for the
+ // object id. Add them to the index.
+ //
+ for (sema_rel::table::names_iterator i (t.names_begin ());
+ i != t.names_end ();
+ ++i)
+ {
+ if (column* c = dynamic_cast<column*> (&i->nameable ()))
+ model_.new_edge<sema_rel::contains> (
+ *in, *c, (sin != 0 ? sin->members.back ().options : ""));
+ }
+ }
+
+ // index (simple value)
+ //
+ bool ordered (ck == ck_ordered && !unordered (m));
+ if (ordered)
+ {
+ // Column.
+ //
+ {
+ bool f (false); //@@ (im)persfect forwarding.
+ instance<object_columns> oc (model_, t, f);
+ oc->traverse (m, container_it (m), "index", "index");
+ }
+
+ // This is a simple value so the name cannot be empty. It is
+ // also a top-level column, so there is no column prefix.
+ //
+ string col (column_name (m, "index", "index", column_prefix ()));
+
+ // Index. See if we have a custom index.
+ //
+ index* sin (m.count ("index-index")
+ ? &m.get<index> ("index-index")
+ : 0);
+ sema_rel::index* in (0);
+
+ if (sin != 0)
+ {
+ in = &model_.new_node<sema_rel::index> (
+ id + ".index", sin->type, sin->method, sin->options);
+ in->set ("cxx-location", location (sin->loc));
+ }
+ else
+ {
+ in = &model_.new_node<sema_rel::index> (id + ".index");
+ in->set ("cxx-location", m.location ());
+ }
+
+ model_.new_edge<sema_rel::unames> (
+ t,
+ *in,
+ sin != 0 && !sin->name.empty ()
+ ? sin->name
+ : index_name (name, col));
+
+ column* c (t.find<column> (col));
+ assert (c != 0);
+
+ model_.new_edge<sema_rel::contains> (
+ *in,
+ *c,
+ (sin != 0 ? sin->members.back ().options : ""));
+ }
+
+ // key
+ //
+ if (ck == ck_map || ck == ck_multimap)
+ {
+ bool f (false); //@@ (im)persfect forwarding.
+ instance<object_columns> oc (model_, t, f);
+ oc->traverse (m, container_kt (m), "key", "key");
+ }
+
+ // value
+ //
+ {
+ bool f (false); //@@ (im)persfect forwarding.
+ instance<object_columns> oc (model_, t, f);
+ oc->traverse (m, container_vt (m), "value", "value");
+ }
+ }
+
+ 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 string
+ table_options (type& c)
+ {
+ return context::table_options (c);
+ }
+
+ virtual void
+ traverse (type& c)
+ {
+ if (!options.at_once () && class_file (c) != unit.file ())
+ return;
+
+ if (!object (c))
+ return;
+
+ semantics::class_* poly (polymorphic (c));
+
+ if (abstract (c) && poly == 0)
+ return;
+
+ qname const& name (table_name (c));
+
+ // If the table with this name was already seen, assume the
+ // user knows what they are doing and skip it.
+ //
+ if (tables_.count (name))
+ return;
+
+ if (deleted (c))
+ {
+ model_.get<deleted_table_map> ("deleted-map")[name] = &c;
+ return;
+ }
+
+ string id (class_fq_name (c), 2); // Remove leading '::'.
+
+ sema_rel::table& t (model_.new_node<sema_rel::table> (id));
+ t.set ("cxx-location", c.location ());
+ t.set ("class", &c);
+ t.set ("deleted-map", deleted_column_map ());
+ model_.new_edge<sema_rel::qnames> (model_, t, name);
+
+ t.options (table_options (c));
+
+ t.extra ()["kind"] =(poly == 0
+ ? "object"
+ : (poly == &c
+ ? "polymorphic root object"
+ : "polymorphic derived object"));
+
+ // Add columns.
+ //
+ {
+ bool tr (true); //@@ (im)persfect forwarding.
+ instance<object_columns> oc (model_, t, tr);
+ oc->traverse (c);
+ }
+
+ // Add indexes.
+ //
+ {
+ instance<object_indexes> oi (model_, t);
+ oi->traverse (c);
+ }
+
+ tables_.insert (name);
+
+ // Create tables for members.
+ //
+ {
+ instance<member_create> mc (model_);
+ mc->traverse (c);
+ }
+ }
+
+ protected:
+ sema_rel::model& model_;
+ tables tables_;
+ };
+ }
+}
+
+#endif // ODB_RELATIONAL_MODEL_HXX