diff options
Diffstat (limited to 'odb/odb/relational')
71 files changed, 40894 insertions, 0 deletions
diff --git a/odb/odb/relational/changelog.cxx b/odb/odb/relational/changelog.cxx new file mode 100644 index 0000000..99f72da --- /dev/null +++ b/odb/odb/relational/changelog.cxx @@ -0,0 +1,1239 @@ +// file : odb/relational/changelog.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <map> + +#include <odb/diagnostics.hxx> + +#include <odb/semantics/relational.hxx> +#include <odb/traversal/relational.hxx> + +#include <odb/relational/context.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace changelog + { + using namespace sema_rel; + using sema_rel::model; + using sema_rel::changelog; + + typedef map<qname, semantics::node*> deleted_table_map; + typedef map<uname, semantics::data_member*> deleted_column_map; + + namespace + { + // + // diff + // + + struct diff_table: trav_rel::column, + trav_rel::primary_key, + trav_rel::foreign_key, + trav_rel::index + { + enum mode_type {mode_add, mode_drop}; + + diff_table (table& o, + mode_type m, + alter_table& a, + graph& gr, + options const& op, + model_version const* v) + : other (o), mode (m), at (a), g (gr), ops (op), version (v) {} + + virtual void + traverse (sema_rel::column& c) + { + using sema_rel::column; + + if (mode == mode_add) + { + if (column* oc = other.find<column> (c.name ())) + { + if (c.type () != oc->type ()) + diagnose_column (c, "type", oc->type (), c.type ()); + + if (c.null () != oc->null ()) + { + alter_column& ac (g.new_node<alter_column> (c.id ())); + + // Set the alters edge. + // + column* b (at.lookup<column, drop_column> (c.name ())); + assert (b != 0); + g.new_edge<alters> (ac, *b); + + ac.null (c.null ()); + g.new_edge<unames> (at, ac, c.name ()); + } + + if (c.default_ () != oc->default_ ()) + diagnose_column ( + c, "default value", oc->default_ (), c.default_ ()); + + if (c.options () != oc->options ()) + diagnose_column (c, "options", oc->options (), c.options ()); + } + else + { + if (version != 0) + { + data_member_path const& mp ( + c.get<data_member_path> ("member-path")); + + semantics::data_member* m (context::added_member (mp)); + if (m != 0) + { + // Make sure the addition version is the current version. + // + if (context::added (*m) != version->current) + { + location l (m->get<location_t> ("added-location")); + error (l) << "member addition version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + } + // Warn about hard additions. If the version is closed, then + // we have already seen these warnings so no need to repeat + // them. + // + else if (ops.warn_hard_add () && version->open) + { + // Issue nicer diagnostics for the direct data member case. + // + if (mp.size () == 1) + { + location l (mp.back ()->location ()); + warn (l) << "data member is hard-added" << endl; + } + else + { + semantics::class_& s ( + dynamic_cast<semantics::class_&> ( + mp.front ()->scope ())); + + warn (s.location ()) << "column '" << c.name () << "' " << + "in class '" << s.name () << "' is hard-added" << endl; + + for (data_member_path::const_iterator i (mp.begin ()); + i != mp.end (); ++i) + { + info ((*i)->location ()) << "corresponding hard-" << + "added data member could be '" << (*i)->name () << + "'" << endl; + } + } + } + } + + add_column& ac (g.new_node<add_column> (c, at, g)); + g.new_edge<unames> (at, ac, c.name ()); + } + } + else + { + if (other.find<column> (c.name ()) == 0) + { + if (version != 0) + { + // See if we have an entry for this column in the soft- + // deleted map. + // + deleted_column_map const& dm ( + other.get<deleted_column_map> ("deleted-map")); + deleted_column_map::const_iterator i (dm.find (c.name ())); + + if (i != dm.end ()) + { + if (context::deleted (*i->second) != version->current) + { + location l (i->second->get<location_t> ("deleted-location")); + error (l) << "member deletion version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + } + // Warn about hard deletions. If the version is closed, then + // we have already seen these warnings so no need to repeat + // them. + // + else if (ops.warn_hard_delete () && version->open) + { + // Here all we have is a column name and a class (object) + // or data member (container) corresponding to the table. + // + if (semantics::class_* s = + other.get<semantics::class_*> ("class", 0)) + { + warn (s->location ()) << "column '" << c.name () << "' " << + "in class '" << s->name () << "' is hard-deleted" << endl; + } + else + { + data_member_path const& mp ( + other.get<data_member_path> ("member-path")); + + warn (mp.back ()->location ()) << "column '" << + c.name () << "' in container '" << + mp.back ()->name () << "' is hard-deleted" << endl; + } + } + } + + drop_column& dc (g.new_node<drop_column> (c.id ())); + g.new_edge<unames> (at, dc, c.name ()); + } + } + } + + virtual void + traverse (sema_rel::primary_key& pk) + { + using sema_rel::primary_key; + + if (mode == mode_add) + { + if (primary_key* opk = other.find<primary_key> (pk.name ())) + { + if (pk.auto_ () != opk->auto_ ()) + diagnose_primary_key (pk, "auto kind"); + + // Database-specific information. + // + for (primary_key::extra_map::const_iterator i ( + pk.extra ().begin ()); i != pk.extra ().end (); ++i) + { + if (opk->extra ().count (i->first) == 0 || + opk->extra ()[i->first] != i->second) + diagnose_primary_key (pk, i->first.c_str ()); + } + + for (primary_key::extra_map::const_iterator i ( + opk->extra ().begin ()); i != opk->extra ().end (); ++i) + { + if (pk.extra ().count (i->first) == 0 || + pk.extra ()[i->first] != i->second) + diagnose_primary_key (pk, i->first.c_str ()); + } + + if (pk.contains_size () != opk->contains_size ()) + diagnose_primary_key (pk, "member set"); + + for (primary_key::contains_size_type i (0); + i != pk.contains_size (); ++i) + { + sema_rel::contains& c (pk.contains_at (i)); + sema_rel::contains& oc (opk->contains_at (i)); + + if (c.column ().name () != oc.column ().name ()) + diagnose_primary_key (pk, "member set"); + } + } + else + { + location const& l (pk.get<location> ("cxx-location")); + error (l) << "adding object id to an existing class is " << + "not supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new class with the object id, migrating the data, and " << + "deleteing the old class" << endl; + throw operation_failed (); + } + } + else + { + if (other.find<primary_key> (pk.name ()) == 0) + { + location const& l (other.get<location> ("cxx-location")); + error (l) << "deleting object id from an existing class is " << + "not supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new class without the object id, migrating the data, " << + "and deleteing the old class" << endl; + throw operation_failed (); + } + } + } + + virtual void + traverse (sema_rel::foreign_key& fk) + { + using sema_rel::foreign_key; + + if (mode == mode_add) + { + if (foreign_key* ofk = other.find<foreign_key> (fk.name ())) + { + if (fk.deferrable () != ofk->deferrable ()) + diagnose_foreign_key (fk, "deferrable mode"); + + if (fk.on_delete () != ofk->on_delete ()) + diagnose_foreign_key (fk, "on delete action"); + + if (fk.referenced_table () != ofk->referenced_table ()) + // See diagnose_foreign_key() if changing this name. + // + diagnose_foreign_key (fk, "pointed-to class"); + + if (fk.referenced_columns () != ofk->referenced_columns ()) + diagnose_foreign_key (fk, "id member set"); + + if (fk.contains_size () != ofk->contains_size ()) + diagnose_foreign_key (fk, "id member set"); + + for (foreign_key::contains_size_type i (0); + i != fk.contains_size (); ++i) + { + sema_rel::contains& c (fk.contains_at (i)); + sema_rel::contains& oc (ofk->contains_at (i)); + + if (c.column ().name () != oc.column ().name ()) + diagnose_foreign_key (fk, "id member set"); + } + } + else + { + add_foreign_key& afk (g.new_node<add_foreign_key> (fk, at, g)); + g.new_edge<unames> (at, afk, fk.name ()); + } + } + else + { + if (other.find<foreign_key> (fk.name ()) == 0) + { + drop_foreign_key& dfk (g.new_node<drop_foreign_key> (fk.id ())); + g.new_edge<unames> (at, dfk, fk.name ()); + } + } + } + + virtual void + traverse (sema_rel::index& i) + { + using sema_rel::index; + + if (mode == mode_add) + { + if (index* oi = other.find<index> (i.name ())) + { + if (i.type () != oi->type ()) + diagnose_index (i, "type", oi->type (), i.type ()); + + if (i.method () != oi->method ()) + diagnose_index (i, "method", oi->method (), i.method ()); + + if (i.options () != oi->options ()) + diagnose_index (i, "options", oi->options (), i.options ()); + + if (i.contains_size () != oi->contains_size ()) + diagnose_index (i, "member set", "", ""); + + for (index::contains_size_type j (0); + j != i.contains_size (); ++j) + { + sema_rel::contains& c (i.contains_at (j)); + sema_rel::contains& oc (oi->contains_at (j)); + + if (c.column ().name () != oc.column ().name ()) + diagnose_index (i, "member set", "", ""); + + if (c.options () != oc.options ()) + diagnose_index ( + i, "member options", oc.options (), c.options ()); + } + } + else + { + add_index& ai (g.new_node<add_index> (i, at, g)); + g.new_edge<unames> (at, ai, i.name ()); + } + } + else + { + if (other.find<index> (i.name ()) == 0) + { + drop_index& di (g.new_node<drop_index> (i.id ())); + g.new_edge<unames> (at, di, i.name ()); + } + } + } + + void + diagnose_column (sema_rel::column& c, + char const* name, + string const& ov, + string const& nv) + { + table& t (c.table ()); + location const& tl (t.get<location> ("cxx-location")); + location const& cl (c.get<location> ("cxx-location")); + + error (cl) << "change to data member results in the change of " << + "the corresponding column " << name; + + if (!ov.empty () || !nv.empty ()) + cerr << " (old: '" << ov << "', new: '" << nv << "')"; + + cerr << endl; + + error (cl) << "this change is not yet handled automatically" << endl; + info (cl) << "corresponding column '" << c.name () << "' " << + "originates here" << endl; + info (tl) << "corresponding table '" << t.name () << "' " << + "originates here" << endl; + info (cl) << "consider re-implementing this change by adding " << + "a new data member with the desired " << name << ", migrating " << + "the data, and deleting the old data member" << endl; + + throw operation_failed (); + } + + void + diagnose_primary_key (sema_rel::primary_key& pk, char const* name) + { + location const& l (pk.get<location> ("cxx-location")); + + error (l) << "changing object id " << name << " in an existing " << + "class is not supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new class with the desired object id " << name << ", " << + "migrating the data, and deleteing the old class" << endl; + + throw operation_failed (); + } + + void + diagnose_foreign_key (sema_rel::foreign_key& fk, char const* name) + { + // This can be an object pointer or a polymorphic base link. + // The latter will trigger this call if we change one base + // to another. + // + using sema_rel::table; + using sema_rel::foreign_key; + + // Polymorphic base link is the first foreign key. + // + table& t (fk.table ()); + table::names_iterator p (t.find (fk.name ())); + + if (t.extra ()["kind"] == "polymorphic derived object" && + (p == t.names_begin () || !(--p)->is_a<foreign_key> ())) + { + location const& l (t.get<location> ("cxx-location")); + + if (name == string ("pointed-to class")) + { + error (l) << "changing polymorphic base is not " << + "supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new derived class with the desired base, migrating the " << + "data, and deleteing the old class" << endl; + } + else + { + error (l) << "changing polymorphic base " << name << + " is not supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new derived class with the desired " << name << ", " << + "migrating the data, and deleteing the old class" << endl; + } + } + else + { + location const& l (fk.get<location> ("cxx-location")); + + error (l) << "changing object pointer " << name << " is not " << + "supported" << endl; + info (l) << "consider re-implementing this change by adding " << + "a new object pointer with the desired " << name << ", " << + "migrating the data, and deleteing the old pointer" << endl; + } + + throw operation_failed (); + } + + void + diagnose_index (sema_rel::index& i, + char const* name, + string const& ov, + string const& nv) + { + table& t (i.table ()); + location const& tl (t.get<location> ("cxx-location")); + location const& il (i.get<location> ("cxx-location")); + + error (il) << "change to index " << name; + + if (!ov.empty () || !nv.empty ()) + cerr << " (old: '" << ov << "', new: '" << nv << "')"; + + cerr << " is not yet handled automatically" << endl; + + info (il) << "corresponding index '" << i.name () << "' " << + "originates here" << endl; + info (tl) << "corresponding table '" << t.name () << "' " << + "originates here" << endl; + info (il) << "consider re-implementing this change by adding " << + "a new index with the desired " << name << " and deleting the " << + "old one" << endl; + + throw operation_failed (); + } + + protected: + table& other; + mode_type mode; + alter_table& at; + graph& g; + options const& ops; + model_version const* version; + }; + + struct diff_model: trav_rel::table + { + enum mode_type {mode_add, mode_drop}; + + diff_model (model& o, + mode_type m, + changeset& s, + graph& gr, + string const& in, + options const& op, + model_version const* v) + : other (o), + mode (m), + cs (s), + g (gr), + in_name (in), + ops (op), + version (v) {} + + virtual void + traverse (sema_rel::table& t) + { + using sema_rel::table; + + if (mode == mode_add) + { + if (table* ot = other.find<table> (t.name ())) + { + // See if there are any changes to the table. + // + alter_table& at (g.new_node<alter_table> (t.id ())); + + // Set the alters edge for lookup. + // + table* bt (cs.lookup<table, drop_table> (t.name ())); + assert (bt != 0); + alters& ae (g.new_edge<alters> (at, *bt)); + + if (t.options () != ot->options ()) + diagnose_table (t, "options", ot->options (), t.options ()); + + if (ot->extra ()["kind"] != t.extra ()["kind"]) + diagnose_table (t, "kind", + ot->extra ()["kind"], t.extra ()["kind"]); + + { + trav_rel::table table; + trav_rel::unames names; + diff_table dtable ( + *ot, diff_table::mode_add, at, g, ops, version); + table >> names >> dtable; + table.traverse (t); + } + + { + trav_rel::table table; + trav_rel::unames names; + diff_table dtable ( + t, diff_table::mode_drop, at, g, ops, version); + table >> names >> dtable; + table.traverse (*ot); + } + + if (!at.names_empty ()) + g.new_edge<qnames> (cs, at, t.name ()); + else + { + g.delete_edge (at, *bt, ae); + g.delete_node (at); + } + } + else + { + // Soft-add is only applicable to containers. + // + if (version != 0 && t.count ("member-path")) + { + data_member_path const& mp ( + t.get<data_member_path> ("member-path")); + + // Make sure the addition version is the current version. + // + semantics::data_member* m (context::added_member (mp)); + if (m != 0) + { + if (context::added (*m) != version->current) + { + location l (m->get<location_t> ("added-location")); + error (l) << "member addition version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + } + // Warn about hard additions. If the version is closed, then + // we have already seen these warnings so no need to repeat + // them. + // + else if (ops.warn_hard_add () && version->open) + { + // Issue nicer diagnostics for the direct data member case. + // + if (mp.size () == 1) + { + location l (mp.back ()->location ()); + warn (l) << "data member is hard-added" << endl; + } + else + { + semantics::class_& s ( + dynamic_cast<semantics::class_&> ( + mp.front ()->scope ())); + + warn (s.location ()) << "container table '" << + t.name () << "' in class '" << s.name () << "' is " << + "hard-added" << endl; + + for (data_member_path::const_iterator i (mp.begin ()); + i != mp.end (); ++i) + { + info ((*i)->location ()) << "corresponding hard-" << + "added data member could be '" << (*i)->name () << + "'" << endl; + } + } + } + } + + add_table& at (g.new_node<add_table> (t, cs, g)); + g.new_edge<qnames> (cs, at, t.name ()); + } + } + else + { + if (other.find<table> (t.name ()) == 0) + { + if (version != 0) + { + // See if we have an entry for this table in the soft- + // deleted map. + // + deleted_table_map const& dm ( + other.get<deleted_table_map> ("deleted-map")); + deleted_table_map::const_iterator i (dm.find (t.name ())); + + if (i != dm.end ()) + { + // This table could be derived either from a class (object) + // or data member (container). + // + semantics::class_* c ( + dynamic_cast<semantics::class_*> (i->second)); + if (c != 0 && context::deleted (*c) != version->current) + { + location l (c->get<location_t> ("deleted-location")); + error (l) << "class deletion version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + + semantics::data_member* m ( + dynamic_cast<semantics::data_member*> (i->second)); + if (m != 0 && context::deleted (*m) != version->current) + { + location l (m->get<location_t> ("deleted-location")); + error (l) << "member deletion version is not the same " << + "as the current model version" << endl; + throw operation_failed (); + } + } + // Warn about hard deletions. If the version is closed, then + // we have already seen these warnings so no need to repeat + // them. + // + // At first, it may seem like a good idea not to warn about + // class deletions since it is not possible to do anything + // useful with a class without referencing it from the code + // (in C++ sense). However, things get tricky once we consider + // polymorphism since the migration code could be "working" + // with the hard-deleted derived class via its base. So we + // are going to warn about polymorphic derived tables. + // + else if (ops.warn_hard_delete () && version->open) + { + string k (t.extra ()["kind"]); + + // We don't have any sensible location. + // + if (k == "container") + { + // We will issue a useless warning if the object table + // that this container references is also deleted. + // While we could detect this, it is going to require + // some effort since we would have to delay the warning + // and check later once we know all the deleted tables. + // + cerr << in_name << ": warning: container table '" << + t.name () << "' is hard-deleted" << endl; + } + else if (k == "polymorphic derived object") + { + // The same as above: the warning will be useless if + // we are dropping the whole hierarchy. + // + cerr << in_name << ": warning: polymorphic derived " << + "object table '" << t.name () << "' is hard-deleted" << + endl; + } + } + } + + drop_table& dt (g.new_node<drop_table> (t.id ())); + g.new_edge<qnames> (cs, dt, t.name ()); + } + } + } + + void + diagnose_table (sema_rel::table& t, + char const* name, + string const& ov, + string const& nv) + { + location const& tl (t.get<location> ("cxx-location")); + + error (tl) << "change to object or container member results in " + "the change of the corresponding table " << name; + + if (!ov.empty () || !nv.empty ()) + cerr << " (old: '" << ov << "', new: '" << nv << "')"; + + cerr << endl; + + error (tl) << "this change is not yet handled automatically" << endl; + info (tl) << "consider re-implementing this change by adding a " << + "new object or container member with the desired " << name << + ", migrating the data, and deleting the old object or member" << + endl; + + throw operation_failed (); + } + + protected: + model& other; + mode_type mode; + changeset& cs; + graph& g; + string in_name; + options const& ops; + model_version const* version; + }; + + // Assumes the new model has cxx-location set. If version is not 0, + // then assume it is the current model version and the new model is + // the current model which has member paths and deleted maps set. + // + changeset& + diff (model& o, + model& n, + changelog& l, + string const& in_name, + options const& ops, + model_version const* version) + { + changeset& r (l.new_node<changeset> (n.version ())); + + // Set the alters edge for lookup. If we are diff'ing two models of + // the same version, then use the old model as a base. Otherwise use + // the tip of changelog (it should correspond to the old model). + // + if (o.version () == n.version ()) + l.new_edge<alters> (r, o); + else + { + if (l.contains_changeset_empty ()) + { + model& m (l.model ()); + + // The changelog model version may also be equal to the new model + // version if the new base model version is greater than the + // latest changeset. + // + assert (m.version () == o.version () || + m.version () == n.version ()); + + l.new_edge<alters> (r, m); + } + else + { + changeset& c (l.contains_changeset_back ().changeset ()); + assert (o.version () == c.version ()); + l.new_edge<alters> (r, c); + } + } + + { + trav_rel::model model; + trav_rel::qnames names; + diff_model dmodel ( + o, diff_model::mode_add, r, l, in_name, ops, version); + model >> names >> dmodel; + model.traverse (n); + } + + { + trav_rel::model model; + trav_rel::qnames names; + diff_model dmodel ( + n, diff_model::mode_drop, r, l, in_name, ops, version); + model >> names >> dmodel; + model.traverse (o); + } + + return r; + } + + // + // patch + // + + struct patch_table: trav_rel::add_column, + trav_rel::drop_column, + trav_rel::alter_column, + trav_rel::add_index, + trav_rel::drop_index, + trav_rel::add_foreign_key, + trav_rel::drop_foreign_key + { + patch_table (table& tl, graph& gr): t (tl), g (gr) {} + + virtual void + traverse (sema_rel::add_column& ac) + { + try + { + column& c (g.new_node<column> (ac, t, g)); + g.new_edge<unames> (t, c, ac.name ()); + } + catch (duplicate_name const&) + { + cerr << "error: invalid changelog: column '" << ac.name () << + "' already exists in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::drop_column& dc) + { + table::names_iterator i (t.find (dc.name ())); + + if (i == t.names_end () || !i->nameable ().is_a<column> ()) + { + cerr << "error: invalid changelog: column '" << dc.name () << + "' does not exist in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + + g.delete_edge (t, i->nameable (), *i); + } + + virtual void + traverse (sema_rel::alter_column& ac) + { + if (column* c = t.find<column> (ac.name ())) + { + if (ac.null_altered ()) + c->null (ac.null ()); + } + else + { + cerr << "error: invalid changelog: column '" << ac.name () << + "' does not exist in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::add_index& ai) + { + using sema_rel::index; + + try + { + index& i (g.new_node<index> (ai, t, g)); + g.new_edge<unames> (t, i, ai.name ()); + } + catch (duplicate_name const&) + { + cerr << "error: invalid changelog: index '" << ai.name () << + "' already exists in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::drop_index& di) + { + using sema_rel::index; + table::names_iterator i (t.find (di.name ())); + + if (i == t.names_end () || !i->nameable ().is_a<index> ()) + { + cerr << "error: invalid changelog: index '" << di.name () << + "' does not exist in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + + g.delete_edge (t, i->nameable (), *i); + } + + virtual void + traverse (sema_rel::add_foreign_key& afk) + { + using sema_rel::foreign_key; + + try + { + foreign_key& fk (g.new_node<foreign_key> (afk, t, g)); + g.new_edge<unames> (t, fk, afk.name ()); + } + catch (duplicate_name const&) + { + cerr << "error: invalid changelog: foreign key '" << afk.name () << + "' already exists in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + using sema_rel::foreign_key; + table::names_iterator i (t.find (dfk.name ())); + + if (i == t.names_end () || !i->nameable ().is_a<foreign_key> ()) + { + cerr << "error: invalid changelog: foreign key '" << dfk.name () << + "' does not exist in table '" << t.name () << "'" << endl; + throw operation_failed (); + } + + g.delete_edge (t, i->nameable (), *i); + } + + protected: + table& t; + graph& g; + }; + + struct patch_model: trav_rel::add_table, + trav_rel::drop_table, + trav_rel::alter_table + { + patch_model (model& ml, graph& gr): m (ml), g (gr) {} + + virtual void + traverse (sema_rel::add_table& at) + { + try + { + table& t (g.new_node<table> (at, m, g)); + g.new_edge<qnames> (m, t, at.name ()); + } + catch (duplicate_name const&) + { + cerr << "error: invalid changelog: table '" << at.name () << + "' already exists in model version " << m.version () << endl; + throw operation_failed (); + } + } + + virtual void + traverse (sema_rel::drop_table& dt) + { + model::names_iterator i (m.find (dt.name ())); + + if (i == m.names_end () || !i->nameable ().is_a<table> ()) + { + cerr << "error: invalid changelog: table '" << dt.name () << + "' does not exist in model version " << m.version () << endl; + throw operation_failed (); + } + + g.delete_edge (m, i->nameable (), *i); + } + + virtual void + traverse (sema_rel::alter_table& at) + { + if (table* t = m.find<table> (at.name ())) + { + trav_rel::alter_table atable; + trav_rel::unames names; + patch_table ptable (*t, g); + atable >> names >> ptable; + atable.traverse (at); + } + else + { + cerr << "error: invalid changelog: table '" << at.name () << + "' does not exist in model version " << m.version () << endl; + throw operation_failed (); + } + } + + protected: + model& m; + graph& g; + }; + + model& + patch (model& m, changeset& c, graph& g) + { + model& r (g.new_node<model> (m, g)); + + trav_rel::changeset changeset; + trav_rel::qnames names; + patch_model pmodel (r, g); + changeset >> names >> pmodel; + changeset.traverse (c); + + r.version (c.version ()); + return r; + } + } + + cutl::shared_ptr<changelog> + generate (model& m, + model_version const& mv, + changelog* old, + string const& in_name, + string const& out_name, + options const& ops) + { + database db (ops.database ()[0]); + cutl::shared_ptr<changelog> cl ( + new (shared) changelog (db.string (), ops.schema_name ()[db])); + graph& g (*cl); + + if (old == 0) + { + // Don't allow changelog initialization if the version is closed. + // This will prevent adding new files to an existing object model + // with a closed version. + // + if (!mv.open) + { + cerr << out_name << ": error: unable to initialize changelog " << + "because current version is closed" << endl; + throw operation_failed (); + } + + if (!ops.init_changelog ()) + cerr << out_name << ": info: initializing changelog with base " << + "version " << mv.base << endl; + + if (mv.base == mv.current) + g.new_edge<contains_model> (*cl, g.new_node<model> (m, g)); + else + { + // In this case we have to create an empty model at the base + // version and a changeset. We do it this way instead of putting + // everything into the base model in order to support adding new + // header files to the project. + // + cerr << out_name << ": warning: base and current versions " << + "differ; assuming base model is empty" << endl; + + model& nm (g.new_node<model> (mv.base)); + g.new_edge<contains_model> (*cl, nm); + changeset& c (diff (nm, m, *cl, in_name, ops, &mv)); + + if (!c.names_empty ()) + { + g.new_edge<alters_model> (c, nm); + g.new_edge<contains_changeset> (*cl, c); + } + } + + return cl; + } + + // Get the changelog base and current versions and do some sanity + // checks. + // + version bver (old->model ().version ()); + version cver ( + old->contains_changeset_empty () + ? bver + : old->contains_changeset_back ().changeset ().version ()); + + if (mv.base < bver) + { + cerr << in_name << ": error: latest changelog base version is " << + "greater than model base version" << endl; + throw operation_failed (); + } + + if (mv.current < cver) + { + cerr << in_name << ": error: latest changelog current version is " << + "greater than model current version" << endl; + throw operation_failed (); + } + + // Build the new changelog. + // + model& oldm (old->model ()); + + // Now we have a case with a "real" old model (i.e., non-empty + // and with version older than current) as well as zero or more + // changeset. + // + // + model* last (&g.new_node<model> (oldm, g)); + model* base (bver == mv.base && mv.base != mv.current ? last : 0); + if (base != 0) + g.new_edge<contains_model> (*cl, *base); + + for (changelog::contains_changeset_iterator i ( + old->contains_changeset_begin ()); + i != old->contains_changeset_end (); ++i) + { + changeset& cs (i->changeset ()); + + // Don't copy the changeset for the current version. Instead, we + // will re-create it from scratch. + // + if (cs.version () == mv.current) + break; + + model& prev (*last); + last = &patch (prev, cs, g); + + if (base == 0) + { + if (last->version () == mv.base) + { + base = last; + g.new_edge<contains_model> (*cl, *base); + } + else if (last->version () > mv.base) + { + // We have a gap. Plug it with an empty base model. We will + // also need to create a new changeset for this step. + // + base = &g.new_node<model> (mv.base); + g.new_edge<contains_model> (*cl, *base); + + changeset& c (diff (*base, *last, *cl, in_name, ops, 0)); + if (!c.names_empty ()) + { + g.new_edge<alters_model> (c, *base); + g.new_edge<contains_changeset> (*cl, c); + } + + continue; + } + } + + // Copy the changeset unless it is below or at our base version. + // + if (last->version () <= mv.base) + continue; + + changeset& c ( + g.new_node<changeset> ( + cs, + cl->contains_changeset_empty () + ? static_cast<qscope&> (*base) // Cannot be NULL. + : cl->contains_changeset_back ().changeset (), + g)); + + g.new_edge<alters_model> (c, prev); + g.new_edge<contains_changeset> (*cl, c); + } + + // If we still haven't found the new base model, then it means it + // has version greater than any changeset we have seen. + // + if (base == 0) + { + if (mv.base == mv.current) + base = &g.new_node<model> (m, g); + else + { + // Fast-forward the latest model to the new base. + // + base = last; + base->version (mv.base); + } + + g.new_edge<contains_model> (*cl, *base); + } + + // If the current version is closed, make sure the model hasn't + // changed. + // + if (!mv.open) + { + // If the last changeset has the current version, then apply it. + // + model* om (last); + if (!old->contains_changeset_empty ()) + { + changeset& c (old->contains_changeset_back ().changeset ()); + if (c.version () == mv.current) + om = &patch (*last, c, g); + } + + changeset& c (diff (*om, m, *cl, in_name, ops, &mv)); + + if (!c.names_empty ()) + { + qnames& n (*c.names_begin ()); + + cerr << out_name << ": error: current version is closed" << endl; + cerr << out_name << ": info: first new change is " << + n.nameable ().kind () << " '" << n.name () << "'" << endl; + + throw operation_failed (); + } + } + + // Add a changeset for the current version unless it is the same + // as the base version. + // + if (mv.base != mv.current) + { + // Add it even if it is empty. This can be useful, for example, + // for data-only migrations were the user relies on the database + // version being updated in the version table. + // + changeset& c (diff (*last, m, *cl, in_name, ops, &mv)); + g.new_edge<alters_model> (c, *last); + g.new_edge<contains_changeset> (*cl, c); + } + + return cl; + } + } +} diff --git a/odb/odb/relational/common-query.cxx b/odb/odb/relational/common-query.cxx new file mode 100644 index 0000000..53321ce --- /dev/null +++ b/odb/odb/relational/common-query.cxx @@ -0,0 +1,169 @@ +// file : odb/relational/common-query.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/common-query.hxx> + +using namespace std; + +namespace relational +{ + // query_alias_traits + // + + void query_alias_traits:: + generate_decl_body () + { + os << "static const char table_name[];"; + } + + void query_alias_traits:: + generate_def (semantics::data_member& m, semantics::class_& c) + { + // Come up with a table alias. 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 alias; + { + string n; + + if (composite_wrapper (utype (*id_member (c)))) + { + n = column_prefix (m, key_prefix_, default_name_).prefix; + + if (n.empty ()) + n = public_name_db (m); + else if (n[n.size () - 1] == '_') + n.resize (n.size () - 1); // Remove trailing underscore. + } + else + { + bool dummy; + n = column_name (m, key_prefix_, default_name_, dummy); + } + + alias = column_prefix_.prefix + n; + } + + generate_def (public_name (m), c, alias); + } + + void query_alias_traits:: + generate_def (string const& tag, semantics::class_& c, string const& alias) + { + semantics::class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + semantics::class_* poly_base (poly_derived ? &polymorphic_base (c) : 0); + + if (poly_derived) + generate_def (tag, *poly_base, alias); + + os << "const char alias_traits<" + << " " << class_fq_name (c) << "," << endl + << " id_" << db << "," << endl + << " " << scope_ << "::" << tag << "_tag>::" << endl + << "table_name[] = "; + + if (poly_root != 0) + os << strlit (quote_id (alias + "_" + table_name (c).uname ())); + else + os << strlit (quote_id (alias)); + + os << ";" + << endl; + } + + entry<query_alias_traits> query_alias_traits_; + + + // query_columns_base + // + + entry<query_columns_base> query_columns_base_; + + // query_columns + // + + void query_columns:: + column_ctor (string const& type, string const& name, string const& base) + { + os << name << " ("; + + if (multi_dynamic) + os << "odb::query_column< " << type << " >& qc," << endl; + + os << "const char* t, const char* c, const char* conv)" << endl + << " : " << base << " (" << (multi_dynamic ? "qc, " : "") << + "t, c, conv)" + << "{" + << "}"; + } + + void query_columns:: + column_ctor_args_extra (semantics::data_member&) + { + } + + void query_columns:: + column_common (semantics::data_member& m, + string const& type, + string const& column, + string const& suffix) + { + string name (public_name (m)); + + if (decl_) + { + string type_id (database_type_id (m)); + + os << "// " << name << endl + << "//" << endl; + + os << "typedef" << endl + << db << "::query_column<" << endl + << " " << db << "::value_traits<" << endl + << " " << type << "," << endl + << " " << type_id << " >::query_type," << endl + << " " << type_id << " >" << endl + << name << suffix << ";" + << endl; + } + else + { + // Note that here we don't use suffix. + // + string tmpl (ptr_ ? "pointer_query_columns" : "query_columns"); + tmpl += "< " + fq_name_ + ", id_" + db.string () + ", A >" + scope_; + + os << "template <typename A>" << endl + << "const typename " << tmpl << "::" << name << "_type_" << endl + << tmpl << "::" << endl + << name << " ("; + + // Pass common query column for registration. + // + if (multi_dynamic) + { + string tmpl (ptr_ ? "pointer_query_columns" : "query_columns"); + tmpl += "< " + fq_name_ + ", id_common, typename A::common_traits >" + + scope_; + + os << tmpl << "::" << name << "," << endl; + } + + os << "A::" << "table_name, " << strlit (quote_id (column)); + + string const& conv (convert_to_expr (column_type (), m)); + os << ", " << (conv.empty () ? "0" : strlit (conv)); + + column_ctor_args_extra (m); + + os << ");" + << endl; + } + } +} diff --git a/odb/odb/relational/common-query.hxx b/odb/odb/relational/common-query.hxx new file mode 100644 index 0000000..c29df6b --- /dev/null +++ b/odb/odb/relational/common-query.hxx @@ -0,0 +1,63 @@ +// file : odb/relational/common-query.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_COMMON_QUERY_HXX +#define ODB_RELATIONAL_COMMON_QUERY_HXX + +#include <odb/relational/common.hxx> + +namespace relational +{ + // + // + struct query_alias_traits: ::query_alias_traits, virtual context + { + typedef query_alias_traits base_impl; + + query_alias_traits (base const& x): base (x) {} + + virtual void + generate_decl_body (); + + virtual void + generate_def (semantics::data_member&, semantics::class_&); + + virtual void + generate_def (string const& tag, semantics::class_&, string const& alias); + }; + + // + // + struct query_columns_base: ::query_columns_base, virtual context + { + typedef query_columns_base base_impl; + + query_columns_base (base const& x): base (x) {const_ = "const ";} + }; + + // + // + struct query_columns: ::query_columns, virtual context + { + typedef query_columns base_impl; + + query_columns (base const& x): base (x) {const_ = "const ";} + + virtual string + database_type_id (semantics::data_member&) = 0; + + virtual void + column_ctor (string const& type, string const& name, string const& base); + + virtual void + column_ctor_args_extra (semantics::data_member&); + + virtual void + column_common (semantics::data_member&, + string const& type, + string const& column, + string const& suffix); + }; +} + +#endif // ODB_RELATIONAL_COMMON_QUERY_HXX diff --git a/odb/odb/relational/common.cxx b/odb/odb/relational/common.cxx new file mode 100644 index 0000000..5c9126c --- /dev/null +++ b/odb/odb/relational/common.cxx @@ -0,0 +1,27 @@ +// file : odb/relational/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/common.hxx> + +using namespace std; + +namespace relational +{ + // member_image_type + // + string member_image_type:: + image_type (semantics::data_member&) + { + assert (false); + return string (); + } + + // member_database_type_id + // + string member_database_type_id:: + database_type_id (semantics::data_member&) + { + assert (false); + return string (); + } +} diff --git a/odb/odb/relational/common.hxx b/odb/odb/relational/common.hxx new file mode 100644 index 0000000..01266a0 --- /dev/null +++ b/odb/odb/relational/common.hxx @@ -0,0 +1,273 @@ +// file : odb/relational/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_COMMON_HXX +#define ODB_RELATIONAL_COMMON_HXX + +#include <set> +#include <cassert> + +#include <odb/common.hxx> +#include <odb/relational/context.hxx> + +namespace relational +{ + struct member_base: traversal::data_member, virtual context + { + typedef member_base base; + + member_base (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix, + object_section* section = 0) + : type_override_ (type), + custom_override_ (ct), + fq_type_override_ (fq_type), + key_prefix_ (key_prefix), + section_ (section), + top_level_ (false) + { + } + + member_base (string const& var, + semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix, + object_section* section = 0) + : var_override_ (var), + type_override_ (type), + custom_override_ (ct), + fq_type_override_ (fq_type), + key_prefix_ (key_prefix), + section_ (section), + top_level_ (false) + { + } + + protected: + // For virtual inheritance only. Should not be actually called. + // + member_base (); + + protected: + string var_override_; + semantics::type* type_override_; + const custom_cxx_type* custom_override_; + string fq_type_override_; + string key_prefix_; + object_section* section_; + + // True during the top-level call of pre() and post() below. Note that + // call via another tarverser (e.g., for a class) is not considered top- + // level. + // + bool top_level_; + }; + + // Template argument is the database SQL type (sql_type). + // + template <typename T> + struct member_base_impl: virtual member_base + { + typedef member_base_impl base_impl; + + member_base_impl (base const& x): base (x) {} + + protected: + member_base_impl () {} + + public: + virtual T const& + member_sql_type (semantics::data_member&) = 0; + + void + traverse (semantics::data_member& m, bool top_level) + { + top_level_ = top_level; + traverse (m); + top_level_ = false; + } + + struct member_info + { + semantics::data_member& m; // Member. + semantics::type& t; // Cvr-unqualified member C++ type, note + // that m.type () may not be the same as t. + const custom_cxx_type* ct; // Translation used for t, if any. + semantics::class_* ptr; // Pointed-to object if m is an object + // pointer. In this case t is the id type + // while fq_type_ is the pointer fq-type. + semantics::type* wrapper; // Wrapper type if member is a composite or + // container wrapper, also cvr-unqualified. + // In this case t is the wrapped type. + bool cq; // True if the original (wrapper) type + // is const-qualified. + T const* st; // Member SQL type (only simple values). + string& var; // Member variable name with trailing '_'. + + // C++ type fq-name. + // + string + fq_type (bool unwrap = true) const + { + semantics::names* hint; + + if (wrapper != 0 && unwrap) + { + // Use the hint from the wrapper unless the wrapped type + // is qualified. + // + hint = wrapper->get<semantics::names*> ("wrapper-hint"); + utype (*context::wrapper (*wrapper), hint); + return t.fq_name (hint); + } + + // Use the original type from 'm' instead of 't' since the hint may + // be invalid for a different type. Plus, if a type is overriden, + // then the fq_type must be as well. + // + if (ptr != 0) + { + semantics::type& t (utype (*id_member (*ptr), hint)); + return t.fq_name (hint); + } + else if (fq_type_.empty ()) + { + semantics::type& t (utype (m, hint)); + return t.fq_name (hint); + } + else + // If we are translated, then fq_type_ contains the original type. + // + return ct == 0 ? fq_type_ : t.fq_name (ct->as_hint); + } + + string + ptr_fq_type () const + { + assert (ptr != 0); + + if (fq_type_.empty ()) + { + // If type is overridden so should fq_type so it is safe to + // get the type from the member. + // + semantics::names* hint; + semantics::type& t (utype (m, hint)); + return t.fq_name (hint); + } + else + return fq_type_; + } + + string const& fq_type_; + + member_info (semantics::data_member& m_, + semantics::type& t_, + const custom_cxx_type* ct_, + semantics::type* wrapper_, + bool cq_, + string& var_, + string const& fq_type) + : m (m_), + t (t_), + ct (ct_), + ptr (0), + wrapper (wrapper_), + cq (cq_), + st (0), + var (var_), + fq_type_ (fq_type) + { + } + }; + + bool + container (member_info& mi) + { + // This cannot be a container if we have a type override. + // + return type_override_ == 0 && context::container (mi.m); + } + + // The false return value indicates that no further callbacks should be + // called for this member. + // + virtual bool + pre (member_info&) {return true;} + + virtual void + post (member_info&) {} + + // Note: calling these directly will mess up the top_level logic. + // + protected: + virtual void + traverse_composite (member_info&) {} + + virtual void + traverse_container (member_info&) {} + + // Note that by default traverse_pointer() will traverse the + // pointed-to object id type. + // + virtual void + traverse_pointer (member_info&); + + virtual void + traverse_simple (member_info&) {} + + private: + virtual void + traverse (semantics::data_member&); + }; + + // + // + struct member_image_type: virtual member_base + { + typedef member_image_type base; + + member_image_type (): member_base (0, 0, string (), string ()) {} + + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type = string (), + string const& key_prefix = string ()) + : member_base (type, ct, fq_type, key_prefix) {} + + // Has to be overriden. + // + virtual string + image_type (semantics::data_member&); + }; + + // + // + struct member_database_type_id: virtual member_base + { + typedef member_database_type_id base; + + member_database_type_id (): member_base (0, 0, string (), string ()) {} + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type = string (), + string const& key_prefix = string ()) + : member_base (type, ct, fq_type, key_prefix) {} + + // Has to be overriden. + // + virtual string + database_type_id (semantics::data_member&); + }; +} + +#include <odb/relational/common.txx> + +// Other common parts. +// +#include <odb/relational/common-query.hxx> + +#endif // ODB_RELATIONAL_COMMON_HXX diff --git a/odb/odb/relational/common.txx b/odb/odb/relational/common.txx new file mode 100644 index 0000000..82a4a4a --- /dev/null +++ b/odb/odb/relational/common.txx @@ -0,0 +1,125 @@ +// file : odb/relational/common.txx +// license : GNU GPL v3; see accompanying LICENSE file + +namespace relational +{ + // + // member_base_impl + // + + template <typename T> + void member_base_impl<T>:: + traverse (semantics::data_member& m) + { + if (transient (m)) + return; + + string var; + + if (!var_override_.empty ()) + var = var_override_; + else + { + string const& name (m.name ()); + var = name + (name[name.size () - 1] == '_' ? "" : "_"); + } + + bool cq (type_override_ != 0 ? false : const_member (m)); + const custom_cxx_type* ct (type_override_ != 0 ? custom_override_ : 0); + semantics::type& t (type_override_ != 0 + ? *type_override_ + : utype (m, &ct)); + + semantics::type* cont; + if (semantics::class_* c = object_pointer (t)) + { + // A pointer in view might point to an object without id. + // + data_member_path* id (id_member (*c)); + semantics::type& t (id != 0 ? utype (*id, &ct) : utype (m, &ct)); + semantics::class_* comp (id != 0 ? composite_wrapper (t) : 0); + + member_info mi (m, + (comp != 0 ? *comp : t), + ct, + (comp != 0 && wrapper (t) ? &t : 0), + cq, + var, + fq_type_override_); // Pointer type. + + mi.ptr = c; + + // Pointer in views aren't really a "column". + // + if (!view_member (m) && comp == 0) + mi.st = &member_sql_type (m); + + if (pre (mi)) + { + traverse_pointer (mi); + post (mi); + } + } + else if (semantics::class_* c = composite_wrapper (t)) + { + // If t is a wrapper, pass the wrapped type. Also pass the + // original, wrapper type. + // + member_info mi (m, + *c, + ct, + (wrapper (t) ? &t : 0), + cq, + var, + fq_type_override_); + if (pre (mi)) + { + traverse_composite (mi); + post (mi); + } + } + // This cannot be a container if we have a type override. + // + else if (type_override_ == 0 && (cont = context::container (m))) + { + // The same unwrapping logic as for composite values. + // + member_info mi (m, + *cont, + 0, // Cannot be mapped. + (wrapper (t) ? &t : 0), + cq, + var, + fq_type_override_); + if (pre (mi)) + { + traverse_container (mi); + post (mi); + } + } + else + { + member_info mi (m, t, ct, 0, cq, var, fq_type_override_); + mi.st = &member_sql_type (m); + + if (pre (mi)) + { + traverse_simple (mi); + post (mi); + } + } + } + + template <typename T> + void member_base_impl<T>:: + traverse_pointer (member_info& mi) + { + if (!view_member (mi.m)) // Not really "as if" pointed-to id member. + { + if (composite (mi.t)) // Already unwrapped. + traverse_composite (mi); + else + traverse_simple (mi); + } + } +} diff --git a/odb/odb/relational/context.cxx b/odb/odb/relational/context.cxx new file mode 100644 index 0000000..3fba69b --- /dev/null +++ b/odb/odb/relational/context.cxx @@ -0,0 +1,169 @@ +// file : odb/relational/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/context.hxx> + +using namespace std; + +namespace relational +{ + context* context::current_; + + context:: + ~context () + { + if (current_ == this) + current_ = 0; + } + + context:: + context () + : data_ (current ().data_), + model (current ().model), + generate_grow (current ().generate_grow), + need_alias_as (current ().need_alias_as), + insert_send_auto_id (current ().insert_send_auto_id), + delay_freeing_statement_result (current ().delay_freeing_statement_result), + need_image_clone (current ().need_image_clone), + generate_bulk (current ().generate_bulk), + global_index (current ().global_index), + global_fkey (current ().global_fkey), + bind_vector (data_->bind_vector_), + truncated_vector (data_->truncated_vector_) + { + } + + context:: + context (data* d, sema_rel::model* m) + : data_ (d), + model (m), + bind_vector (data_->bind_vector_), + truncated_vector (data_->truncated_vector_) + { + assert (current_ == 0); + current_ = this; + } + + string context:: + index_name (qname const& table, string const& base) + { + string n; + + if (options.index_suffix ().count (db) != 0) + n = base + options.index_suffix ()[db]; + else + n = compose_name (base, "i"); + + // If this database has global index names, then add the table + // name as a prefix (the schema, if needed, will be added by + // database-specific create_index overrides). + // + if (global_index) + n = compose_name (table.uname (), n); + + return transform_name (n, sql_name_index); + } + + string context:: + fkey_name (qname const& table, string const& base) + { + string n; + + if (options.fkey_suffix ().count (db) != 0) + n = base + options.fkey_suffix ()[db]; + else + n = compose_name (base, "fk"); + + // If this database has global index names, then add the table + // name as a prefix (the schema, if needed, will be added by + // database-specific create_foreign_key overrides). + // + if (global_fkey) + n = compose_name (table.uname (), n); + + return transform_name (n, sql_name_fkey); + } + + string context:: + convert (string const& e, string const& c) + { + size_t p (c.find ("(?)")); + string r (c, 0, p); + r += e; + r.append (c, p + 3, string::npos); + return r; + } + + string const& context:: + convert_expr (string const&, semantics::data_member&, bool) + { + assert (false); + throw operation_failed (); + } + + bool context:: + grow_impl (semantics::class_&, user_section*) + { + return false; + } + + bool context:: + grow_impl (semantics::data_member&) + { + return false; + } + + bool context:: + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&) + { + return false; + } + + string context:: + quote_string_impl (string const& s) const + { + string r; + r.reserve (s.size ()); + r += '\''; + + for (string::size_type i (0), n (s.size ()); i < n; ++i) + { + if (s[i] == '\'') + r += "''"; + else + r += s[i]; + } + + r += '\''; + return r; + } + + string context:: + quote_id_impl (qname const& id) const + { + string r; + + bool f (true); + for (qname::iterator i (id.begin ()); i < id.end (); ++i) + { + if (i->empty ()) + continue; + + if (f) + f = false; + else + r += '.'; + + r += '"'; + r += *i; + r += '"'; + } + + return r; + } +} diff --git a/odb/odb/relational/context.hxx b/odb/odb/relational/context.hxx new file mode 100644 index 0000000..db9b5be --- /dev/null +++ b/odb/odb/relational/context.hxx @@ -0,0 +1,289 @@ +// file : odb/relational/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_CONTEXT_HXX +#define ODB_RELATIONAL_CONTEXT_HXX + +#include <odb/context.hxx> + +#include <odb/semantics/relational.hxx> +#include <odb/traversal/relational.hxx> + +namespace relational +{ + namespace sema_rel = semantics::relational; + namespace trav_rel = traversal::relational; + + enum statement_kind + { + statement_select, + statement_insert, + statement_update, + statement_delete, + statement_where // WHERE clause. + }; + + // Index. + // + struct index + { + location_t loc; // Location of this index definition. + std::string name; // If empty, then derive from the member name. + std::string type; // E.g., "UNIQUE", etc. + std::string method; // E.g., "BTREE", etc. + std::string options; // Database-specific index options. + + struct member + { + location_t loc; // Location of this member specifier. + std::string name; // Member name, e.g., foo_, foo_.bar_. + data_member_path path; // Member path. + std::string options; // Member options, e.g., "ASC", etc. + }; + typedef std::vector<member> members_type; + + members_type members; + }; + + typedef std::vector<index> indexes; + + // Indexes in the above vector are in location order. + // + struct index_comparator + { + bool + operator() (index const& x, index const& y) const + { + return x.loc < y.loc; + } + }; + + // Custom database type mapping. + // + struct custom_db_type + { + regex type; + std::string as; + std::string to; + std::string from; + location_t loc; + }; + + typedef std::vector<custom_db_type> custom_db_types; + + class context: public virtual ::context + { + public: + // Return true if an object or value type has members for which + // the image can grow. If section is not specified, then ignore + // separately loaded members. Otherwise ignore members that do + // not belong to the section. + // + bool + grow (semantics::class_&, user_section* = 0); + + // The same for a member's value type. + // + bool + grow (semantics::data_member&); + + bool + grow (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const& key_prefix); + + public: + // Quote SQL string. + // + string + quote_string (string const&) const; + + // Quote SQL identifier. + // + string + quote_id (string const&) const; + + string + quote_id (qname const&) const; + + // Quoted column and table names. + // + string + column_qname (semantics::data_member& m, column_prefix const& cp) const + { + return quote_id (column_name (m, cp)); + } + + string + column_qname (data_member_path const& mp) const + { + return quote_id (column_name (mp)); + } + + string + column_qname (semantics::data_member& m, + string const& key_prefix, + string const& default_name, + column_prefix const& cp) const + { + return quote_id (column_name (m, key_prefix, default_name, cp)); + } + + string + table_qname (semantics::class_& c) const + { + return quote_id (table_name (c)); + } + + string + table_qname (semantics::class_& obj, data_member_path const& mp) const + { + return quote_id (table_name (obj, mp)); + } + + string + table_qname (semantics::data_member& m, table_prefix const& p) const + { + return quote_id (table_name (m, p)); + } + + public: + string + index_name (qname const& table, string const& base); + + string + fkey_name (qname const& table, string const& base); + + // Custom database type conversion. + // + public: + string + convert_to (string const& expr, + string const& sqlt, + semantics::data_member& m) + { + string const& conv (current ().convert_expr (sqlt, m, true)); + return conv.empty () ? expr : convert (expr, conv); + } + + string + convert_from (string const& expr, + string const& sqlt, + semantics::data_member& m) + { + string const& conv (current ().convert_expr (sqlt, m, false)); + return conv.empty () ? expr : convert (expr, conv); + } + + // These shortcut versions should only be used on special members + // (e.g., auto id, version, etc) since they may not determine the + // proper SQL type in other cases (prefixes, composite ids, etc). + // + string + convert_to (string const& expr, semantics::data_member& m) + { + return convert_to (expr, column_type (m), m); + } + + string + convert_from (string const& expr, semantics::data_member& m) + { + return convert_from (expr, column_type (m), m); + } + + // Return the conversion expression itself. + // + string const& + convert_to_expr (string const& sqlt, semantics::data_member& m) + { + return current ().convert_expr (sqlt, m, true); + } + + protected: + virtual string const& + convert_expr (string const& sqlt, semantics::data_member&, bool to); + + string + convert (string const& expr, string const& conv); + + protected: + // The default implementation returns false. + // + virtual bool + grow_impl (semantics::class_&, user_section*); + + virtual bool + grow_impl (semantics::data_member&); + + virtual bool + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&); + + // The default implementation uses the ISO quoting ('') and + // escapes singe quotes inside the string by double-quoting + // (' -> ''). Some (most?) database systems support escape + // sequences. We may want to provide utilize that to support + // things like \n, \t, etc. + // + virtual string + quote_string_impl (string const&) const; + + // The default implementation uses the ISO quoting (""). + // + virtual string + quote_id_impl (qname const&) const; + + public: + virtual + ~context (); + context (); + + static context& + current () + { + return *current_; + } + + protected: + struct data; + typedef context base_context; + + context (data*, sema_rel::model*); + + private: + static context* current_; + + protected: + struct data: root_context::data + { + data (std::ostream& os): root_context::data (os) {} + + string bind_vector_; + string truncated_vector_; + }; + data* data_; + + public: + sema_rel::model* model; + + bool generate_grow; + bool need_alias_as; + bool insert_send_auto_id; + bool delay_freeing_statement_result; + bool need_image_clone; + bool generate_bulk; + + bool global_index; + bool global_fkey; + + string const& bind_vector; + string const& truncated_vector; + }; +} + +#include <odb/relational/context.ixx> + +#endif // ODB_RELATIONAL_CONTEXT_HXX diff --git a/odb/odb/relational/context.ixx b/odb/odb/relational/context.ixx new file mode 100644 index 0000000..abf1fb5 --- /dev/null +++ b/odb/odb/relational/context.ixx @@ -0,0 +1,44 @@ +// file : odb/relational/context.ixx +// license : GNU GPL v3; see accompanying LICENSE file + +namespace relational +{ + inline bool context:: + grow (semantics::class_& c, user_section* s) + { + return current ().grow_impl (c, s); + } + + inline bool context:: + grow (semantics::data_member& m) + { + return current ().grow_impl (m); + } + + inline bool context:: + grow (semantics::data_member& m, + semantics::type& t, + const custom_cxx_type* ct, + string const& kp) + { + return current ().grow_impl (m, t, ct, kp); + } + + inline context::string context:: + quote_string (string const& str) const + { + return current ().quote_string_impl (str); + } + + inline context::string context:: + quote_id (string const& id) const + { + return current ().quote_id_impl (qname (id)); + } + + inline context::string context:: + quote_id (qname const& id) const + { + return current ().quote_id_impl (id); + } +} diff --git a/odb/odb/relational/generate.hxx b/odb/odb/relational/generate.hxx new file mode 100644 index 0000000..e597fb8 --- /dev/null +++ b/odb/odb/relational/generate.hxx @@ -0,0 +1,81 @@ +// file : odb/relational/generate.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_GENERATE_HXX +#define ODB_RELATIONAL_GENERATE_HXX + +#include <string> +#include <libcutl/shared-ptr.hxx> + +#include <odb/context.hxx> +#include <odb/semantics/relational/model.hxx> +#include <odb/semantics/relational/changeset.hxx> +#include <odb/semantics/relational/changelog.hxx> + +namespace relational +{ + namespace header + { + void + generate (); + } + + namespace inline_ + { + void + generate (); + } + + namespace source + { + void + generate (); + } + + namespace model + { + cutl::shared_ptr<semantics::relational::model> + generate (); + } + + namespace changelog + { + // Returns NULL if the changelog is unchanged. + // + cutl::shared_ptr<semantics::relational::changelog> + generate (semantics::relational::model&, + model_version const&, + semantics::relational::changelog* old, // Can be NULL. + std::string const& in_name, + std::string const& out_name, + options const&); + } + + namespace schema + { + void + generate_prologue (); + + void + generate_epilogue (); + + void + generate_drop (); + + void + generate_create (); + + void + generate_migrate_pre (semantics::relational::changeset&); + + void + generate_migrate_post (semantics::relational::changeset&); + + // Generate embedded schema. + // + void + generate_source (semantics::relational::changelog*); + } +} + +#endif // ODB_RELATIONAL_GENERATE_HXX diff --git a/odb/odb/relational/header.cxx b/odb/odb/relational/header.cxx new file mode 100644 index 0000000..364d48e --- /dev/null +++ b/odb/odb/relational/header.cxx @@ -0,0 +1,1146 @@ +// file : odb/relational/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +void relational::header::class1:: +traverse_object (type& c) +{ + using semantics::data_member; + + data_member_path* id (id_member (c)); + data_member* idf (id ? id->front () : 0); + data_member* idb (id ? id->back () : 0); + bool auto_id (id && auto_ (*id)); + bool base_id (id && &idf->scope () != &c); // Comes from base. + + data_member* opt (context::optimistic (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + data_member* discriminator (poly ? context::discriminator (*poly_root) : 0); + + bool abst (abstract (c)); + bool reuse_abst (abst && !poly); + + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + + // Sections. + // + user_sections& uss (c.get<user_sections> ("user-sections")); + + os << "// " << class_name (c) << endl + << "//" << endl; + + // pointer_query_columns & query_columns + // + if (options.generate_query ()) + { + // If we don't have object pointers, then also generate + // query_columns (in this case pointer_query_columns and + // query_columns are the same and the former inherits from + // the latter). Otherwise we have to postpone query_columns + // generation until the second pass to deal with forward- + // declared objects. + // + if (!has_a (c, test_pointer | include_base)) + query_columns_type_->traverse (c); + + pointer_query_columns_type_->traverse (c); + } + + // object_traits_impl + // + os << "template <>" << endl + << "class " << exp << "access::object_traits_impl< " << type << ", " << + "id_" << db << " >:" << endl + << " public access::object_traits< " << type << " >" + << "{" + << "public:" << endl; + + object_public_extra_pre (c); + + // For dynamic multi-database support also generate common traits + // alias (used in query aliasing). + // + if (options.generate_query () && multi_dynamic) + { + os << "typedef access::object_traits_impl< " << type << ", " << + "id_common > common_traits;" + << endl; + } + + // Polymorphic root_traits, base_traits, and discriminator_image_type. + // + if (poly) + { + if (!abst) + os << "typedef polymorphic_entry<object_type, id_" << db << + "> entry_type;"; + + os << "typedef object_traits_impl<root_type, id_" << db << "> " << + "root_traits;"; + + if (poly_derived) + { + os << "typedef object_traits_impl<base_type, id_" << db << "> " << + "base_traits;" + << endl; + } + else + { + os << endl + << "struct discriminator_image_type" + << "{"; + + discriminator_image_member_->traverse (*discriminator); + + if (opt != 0) + version_image_member_->traverse (*opt); + + os << "std::size_t version;" + << "};"; + } + } + + // id_image_type + // + if (id != 0) + { + if (base_id) + { + if (poly_derived) + os << "typedef root_traits::id_image_type id_image_type;" + << endl; + else + { + semantics::class_& b ( + dynamic_cast<semantics::class_&> (idf->scope ())); + + os << "typedef object_traits_impl< " << class_fq_name (b) << ", " << + "id_" << db << " >::id_image_type id_image_type;" + << endl; + } + } + else + { + os << "struct id_image_type" + << "{"; + + id_image_member_->traverse (*idb); + + if (opt != 0) + version_image_member_->traverse (*opt); + + os << "std::size_t version;" + << "};"; + } + } + + // Polymorphic map. + // + if (poly) + { + if (!poly_derived) + os << "static map_type* map;"; + + os << "static const " << (abst ? "abstract_" : "") << "info_type info;" + << endl; + } + + // image_type + // + image_type_->traverse (c); + + // Extra (container, section) statement cache (forward declaration). + // + if (!reuse_abst && id != 0) + os << "struct extra_statement_cache_type;" + << endl; + + // + // Containers (abstract and concrete). + // + + { + instance<container_traits> t (c); + t->traverse (c); + } + + // + // Sections (abstract and concrete). + // + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + instance<section_traits> t (c); + t->traverse (*i); + } + + // + // Query (abstract and concrete). + // + + if (options.generate_query ()) + { + // Generate object pointer tags here unless we are generating dynamic + // multi-database support, in which case they generated in object_traits. + // + if (!multi_dynamic && has_a (c, test_pointer | exclude_base)) + { + query_tags t; // Not customizable. + t.traverse (c); + } + } + + // + // Functions (abstract and concrete). + // + + if (id != 0 || !reuse_abst) + os << "using object_traits<object_type>::id;" + << endl; + + if (opt != 0) + os << "using object_traits<object_type>::version;" + << endl; + + if (!poly_derived && id != 0) + { + if (auto_id) + os << "static id_type" << endl + << "id (const id_image_type&);" + << endl; + + if (options.generate_query ()) + os << "static id_type" << endl + << "id (const image_type&);" + << endl; + + if (opt != 0) + os << "static version_type" << endl + << "version (const image_type&);" + << endl; + } + + // discriminator() + // + if (poly && !poly_derived) + os << "static discriminator_type" << endl + << "discriminator (const image_type&);" + << endl; + + // grow () + // + if (generate_grow) + { + // For derived classes in a polymorphic hierarchy, grow() will + // check bases up to the specified depth. If one of the base + // images has grown, then it will increment its version. But + // the return value only indicates the state of this image, + // excluding polymorphic bases (in other words, it is possible + // that one of the bases has grown but this function returns + // false). + // + os << "static bool" << endl + << "grow (image_type&," << endl + << truncated_vector; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + if (poly_derived) + os << "," << endl + << "std::size_t = depth"; + + os << ");" + << endl; + } + + // bind (image_type) + // + os << "static void" << endl + << "bind (" << bind_vector << "," << endl; + + // If we are a derived type in a polymorphic hierarchy, then + // we get the the external id binding. + // + if (poly_derived) + os << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl; + + os << "image_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // bind (id_image_type) + // + if (id != 0) + { + os << "static void" << endl + << "bind (" << bind_vector << ", id_image_type&" << + (opt != 0 ? ", bool bind_version = true" : "") << ");" + << endl; + } + + // init (image, object) + // + os << "static " << (generate_grow ? "bool" : "void") << endl + << "init (image_type&," << endl + << "const object_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // init (object, image) + // + os << "static void" << endl + << "init (object_type&," << endl + << "const image_type&," << endl + << "database*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + if (poly_derived) + os << "," << endl + << "std::size_t = depth"; + + os << ");" + << endl; + + // init (id_image, id) + // + if (id != 0) + { + os << "static void" << endl + << "init (id_image_type&, const id_type&" << + (opt != 0 ? ", const version_type* = 0" : "") << ");" + << endl; + } + + if (poly_derived) + { + // check_version + // + os << "static bool" << endl + << "check_version (const std::size_t*, const image_type&);" + << endl; + + // update_version + // + os << "static void" << endl + << "update_version (std::size_t*, const image_type&, " << + db << "::binding*);" + << endl; + } + + // The rest does not apply to reuse-abstract objects. + // + if (reuse_abst) + { + object_public_extra_post (c); + os << "};"; + return; + } + + column_count_type const& cc (column_count (c)); + + // Statements typedefs. + // + if (poly) + { + if (poly_derived) + os << "typedef" << endl + << db << "::polymorphic_derived_object_statements" << + "<object_type>" << endl + << "statements_type;" + << endl + << "typedef" << endl + << db << "::polymorphic_root_object_statements<root_type>" << endl + << "root_statements_type;" + << endl; + else + os << "typedef" << endl + << db << "::polymorphic_root_object_statements<object_type>" << endl + << "statements_type;" + << endl + << "typedef statements_type root_statements_type;" + << endl; + } + else + { + if (id != 0) + os << "typedef " << db << "::object_statements<object_type> " << + "statements_type;" + << endl; + else + os << "typedef " << db << "::no_id_object_statements<object_type> " << + "statements_type;" + << endl; + } + + // + // Query (concrete). + // + + if (options.generate_query ()) + { + // query_base_type + // + os << "typedef " << db << "::query_base query_base_type;" + << endl; + } + + // + // Containers (concrete). + // + + // + // Sections (concrete). + // + + // column_count + // + os << "static const std::size_t column_count = " << cc.total << "UL;" + << "static const std::size_t id_column_count = " << cc.id << "UL;" + << "static const std::size_t inverse_column_count = " << + cc.inverse << "UL;" + << "static const std::size_t readonly_column_count = " << + cc.readonly << "UL;" + << "static const std::size_t managed_optimistic_column_count = " << + cc.optimistic_managed << "UL;"; + + if (poly && !poly_derived) + os << "static const std::size_t discriminator_column_count = " << + cc.discriminator << "UL;"; + + os << endl + << "static const std::size_t separate_load_column_count = " << + cc.separate_load << "UL;" + << "static const std::size_t separate_update_column_count = " << + cc.separate_update << "UL;" + << endl; + + os << "static const bool versioned = " << versioned << ";" + << endl; + + // Statements. + // + os << "static const char persist_statement[];"; + + if (id != 0) + { + if (poly_derived) + { + char const* n (abst ? "1" : "depth"); + + os << "static const char* const find_statements[" << n << "];" + << "static const std::size_t find_column_counts[" << n << "];"; + } + else + { + os << "static const char find_statement[];"; + + if (poly) + os << "static const char find_discriminator_statement[];"; + } + + if (cc.total != cc.id + cc.inverse + cc.readonly + cc.separate_update) + os << "static const char update_statement[];"; + + os << "static const char erase_statement[];"; + + if (opt != 0 && !poly_derived) + os << "static const char optimistic_erase_statement[];"; + } + + if (options.generate_query ()) + { + os << "static const char query_statement[];" + << "static const char erase_query_statement[];" + << endl + << "static const char table_name[];"; + } + + os << endl; + + // + // Functions (concrete). + // + + // persist () + // + os << "static void" << endl + << "persist (database&, " << (auto_id ? "" : "const ") << "object_type&"; + + if (poly) + os << ", bool top = true, bool dyn = true"; + + os << ");" + << endl; + + if (c.count ("bulk-persist")) + os << "static void" << endl + << "persist (database&, " << (auto_id ? "" : "const ") << + "object_type**, std::size_t, multiple_exceptions&);" + << endl; + + if (id != 0) + { + // find (id) + // + if (c.default_ctor ()) + os << "static pointer_type" << endl + << "find (database&, const id_type&);" + << endl; + + // find (id, obj) + // + os << "static bool" << endl + << "find (database&, const id_type&, object_type&"; + + if (poly) + os << ", bool dyn = true"; + + os << ");" + << endl; + + // reload () + // + os << "static bool" << endl + << "reload (database&, object_type&"; + + if (poly) + os << ", bool dyn = true"; + + os << ");" + << endl; + + // update () + // + // In case of a polymorphic object, we generate update() even if it is + // readonly since the potentially-readwrite base will rely on it to + // initialize the id image. + // + // + if (!readonly (c) || poly) + { + os << "static void" << endl + << "update (database&, const object_type&"; + + if (poly) + os << ", bool top = true, bool dyn = true"; + + os << ");" + << endl; + + if (c.count ("bulk-update")) + os << "static void" << endl + << "update (database&, const object_type**, std::size_t, " << + "multiple_exceptions&);" + << endl; + } + + // erase () + // + os << "static void" << endl + << "erase (database&, const id_type&"; + + if (poly) + os << ", bool top = true, bool dyn = true"; + + os << ");" + << endl; + + os << "static void" << endl + << "erase (database&, const object_type&"; + + if (poly) + os << ", bool top = true, bool dyn = true"; + + os << ");" + << endl; + + if (c.count ("bulk-erase")) + { + os << "static std::size_t" << endl + << "erase (database&, const id_type**, std::size_t, " << + "multiple_exceptions&);" + << endl; + + os << "static void" << endl + << "erase (database&, const object_type**, std::size_t, " << + "multiple_exceptions&);" + << endl; + } + + // Sections. + // + // We treat all polymorphic sections as (potentially) having something + // to load or to update since we cannot predict what will be added to + // them in overrides. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "static bool" << endl + << "load (connection&, object_type&, section&" << + (poly ? ", const info_type* = 0" : "") << ");" + << endl; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "static bool" << endl + << "update (connection&, const object_type&, const section&" << + (poly ? ", const info_type* = 0" : "") << ");" + << endl; + } + + // query () + // + if (options.generate_query ()) + { + if (!options.omit_unprepared ()) + { + os << "static result<object_type>" << endl + << "query (database&, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static result<object_type>" << endl + << "query (database&, const odb::query_base&);" + << endl; + } + + os << "static unsigned long long" << endl + << "erase_query (database&, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static unsigned long long" << endl + << "erase_query (database&, const odb::query_base&);" + << endl; + + if (options.generate_prepared ()) + { + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, " << + "const odb::query_base&);" + << endl; + + os << "static odb::details::shared_ptr<result_impl>" << endl + << "execute_query (prepared_query_impl&);" + << endl; + } + } + + object_public_extra_post (c); + + // Implementation details. + // + os << "public:" << endl; + + if (id != 0) + { + // Load the object image. + // + os << "static bool" << endl + << "find_ (statements_type&," << endl + << "const id_type*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + if (poly_derived && !abst) + os << "," << endl + << "std::size_t = depth"; + + os << ");" + << endl; + + // Load the rest of the object (containers, etc). Expects the id + // image in the object statements to be initialized to the object + // id. + // + os << "static void" << endl + << "load_ (statements_type&," << endl + << "object_type&," << endl + << "bool reload"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + if (poly_derived) + os << "," << endl + << "std::size_t = depth"; + + os << ");" + << endl; + } + + // discriminator_ () + // + if (poly && !poly_derived) + { + os << "static void" << endl + << "discriminator_ (statements_type&," << endl + << "const id_type&," << endl + << "discriminator_type*"; + + if (opt != 0) + os << "," << endl + << "version_type* = 0"; + + os << ");" + << endl; + } + + // Load the dynamic part of the object. Depth inidicates where + // the dynamic part starts. Expects the id image in the object + // statements to be initialized to the object id. We don't need + // it if we are poly-abstract. + // + if (poly_derived && !abst) + os << "static void" << endl + << "load_ (database&, root_type&, std::size_t);" + << endl; + + // Image chain manipulation. + // + if (poly && need_image_clone && options.generate_query ()) + { + os << "static root_traits::image_type&" << endl + << "root_image (image_type&);" + << endl; + + // Note that the original image is non-const since for some databases + // the copy "steals" stuff from the original (e.g., LOB descriptors in + // Oracle). + // + os << "static image_type*" << endl + << "clone_image (image_type&);" + << endl; + + os << "static void" << endl + << "copy_image (image_type&, image_type&);" + << endl; + + os << "static void" << endl + << "free_image (image_type*);" + << endl; + } + + os << "};"; + + + // object_traits_impl< , id_common> + // + // Note that it is not generated for reuse-abstract classes. + // + if (options.default_database_specified () && + options.default_database () == db) + { + os << "template <>" << endl + << "class access::object_traits_impl< " << type << ", " << + "id_common >:" << endl + << " public access::object_traits_impl< " << type << ", " << + "id_" << db << " >" + << "{" + << "};"; + } +} + +void relational::header::class1:: +traverse_view (type& c) +{ + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + size_t columns (column_count (c).total); + size_t obj_count (c.get<size_t> ("object-count")); + + os << "// " << class_name (c) << endl + << "//" << endl; + + // view_traits_impl + // + os << "template <>" << endl + << "class " << exp << "access::view_traits_impl< " << type << ", " << + "id_" << db << " >:" << endl + << " public access::view_traits< " << type << " >" + << "{" + << "public:" << endl; + + view_public_extra_pre (c); + + // For dynamic multi-database support also generate common traits + // alias (used in query aliasing). + // + if (multi_dynamic) + { + os << "typedef access::view_traits_impl< " << type << ", " << + "id_common > common_traits;" + << endl; + } + + // image_type + // + image_type_->traverse (c); + + os << "typedef " << db << "::view_statements<view_type> statements_type;" + << endl; + + // + // Query. + // + + // Generate associated object tags here unless we are generating dynamic + // multi-database support, in which case they generated in object_traits. + // + if (!multi_dynamic) + { + query_tags t; // Not customizable. + t.traverse (c); + } + + // query_base_type and query_columns (definition generated by class2). + // + os << "typedef " << db << "::query_base query_base_type;" + << "struct query_columns"; + + if (obj_count == 0) + os << "{" + << "};"; + else + os << ";" + << endl; + + os << "static const bool versioned = " << versioned << ";" + << endl; + + // + // Functions. + // + + // grow () + // + if (generate_grow) + { + os << "static bool" << endl + << "grow (image_type&," << endl + << truncated_vector; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ")" << (columns != 0 ? ";\n" : "{}"); + } + + // bind (image_type) + // + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "image_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ")" << (columns != 0 ? ";\n" : "{}"); + + // init (view, image) + // + os << "static void" << endl + << "init (view_type&," << endl + << "const image_type&," << endl + << "database*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ")" << (columns != 0 ? ";\n" : "{}"); + + // column_count + // + os << "static const std::size_t column_count = " << columns << "UL;" + << endl; + + // Statements. + // + view_query& vq (c.get<view_query> ("query")); + + if (vq.kind != view_query::runtime) + { + os << "static query_base_type" << endl + << "query_statement (const query_base_type&);" + << endl; + } + + // + // Functions. + // + + // query () + // + if (!options.omit_unprepared ()) + { + os << "static result<view_type>" << endl + << "query (database&, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static result<view_type>" << endl + << "query (database&, const odb::query_base&);" + << endl; + } + + if (options.generate_prepared ()) + { + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, const query_base_type&);" + << endl; + + if (multi_dynamic) + os << "static odb::details::shared_ptr<prepared_query_impl>" << endl + << "prepare_query (connection&, const char*, " << + "const odb::query_base&);" + << endl; + + os << "static odb::details::shared_ptr<result_impl>" << endl + << "execute_query (prepared_query_impl&);" + << endl; + } + + view_public_extra_post (c); + + os << "};"; + + // view_traits_impl< , id_common> + // + if (options.default_database_specified () && + options.default_database () == db) + { + os << "template <>" << endl + << "class access::view_traits_impl< " << type << ", " << + "id_common >:" << endl + << " public access::view_traits_impl< " << type << ", " << + "id_" << db << " >" + << "{" + << "};"; + } +} + +void relational::header::class1:: +traverse_composite (type& c) +{ + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + + os << "// " << class_name (c) << endl + << "//" << endl; + + // While composite_value_traits is not used directly by user code, we + // still need to export it if the generated code for the same database + // is split into several DLLs. + // + os << "template <>" << endl + << "class " << exp << "access::composite_value_traits< " << type << + ", id_" << db << " >" + << "{" + << "public:" << endl; + + // value_type + // + os << "typedef " << type << " value_type;" + << endl; + + // image_type + // + image_type_->traverse (c); + + // Containers. + // + { + instance<container_traits> t (c); + t->traverse (c); + } + + // grow () + // + if (generate_grow) + { + os << "static bool" << endl + << "grow (image_type&," << endl + << truncated_vector; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // bind (image_type) + // + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "image_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // init (image, value) + // + os << "static " << (generate_grow ? "bool" : "void") << endl + << "init (image_type&," << endl + << "const value_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // init (value, image) + // + os << "static void" << endl + << "init (value_type&," << endl + << "const image_type&," << endl + << "database*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + if (!has_a (c, test_container)) + { + // get_null (image) + // + os << "static bool" << endl + << "get_null (const image_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // set_null (image) + // + os << "static void" << endl + << "set_null (image_type&," << endl + << db << "::statement_kind"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + column_count_type const& cc (column_count (c)); + os << "static const std::size_t column_count = " << cc.total << "UL;"; + + os << "};"; +} + +void relational::header:: +generate () +{ + context ctx; + ostream& os (ctx.os); + + instance<include> i; + i->generate (); + + os << "namespace odb" + << "{"; + + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + instance<class1> c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx.unit); + } + + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + instance<class2> c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx.unit); + } + + os << "}"; +} diff --git a/odb/odb/relational/header.hxx b/odb/odb/relational/header.hxx new file mode 100644 index 0000000..964aff2 --- /dev/null +++ b/odb/odb/relational/header.hxx @@ -0,0 +1,1466 @@ +// file : odb/relational/header.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_HEADER_HXX +#define ODB_RELATIONAL_HEADER_HXX + +#include <odb/relational/context.hxx> +#include <odb/relational/common.hxx> + +namespace relational +{ + namespace header + { + // + // image_type + // + + struct image_member: virtual member_base + { + typedef image_member base; + + image_member (string const& var = string ()) + : member_base (var, 0, 0, string (), string ()) {} + + image_member (string const& var, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix) {} + }; + + template <typename T> + struct image_member_impl: image_member, virtual member_base_impl<T> + { + typedef image_member_impl base_impl; + + image_member_impl (base const& x) + : member_base::base (x), // virtual base + base (x), + member_image_type_ (base::type_override_, + base::custom_override_, + base::fq_type_override_, + base::key_prefix_) + { + } + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual bool + pre (member_info& mi) + { + if (container (mi)) + return false; + + image_type = member_image_type_->image_type (mi.m); + + if (var_override_.empty ()) + os << "// " << mi.m.name () << endl + << "//" << endl; + + return true; + } + + virtual void + traverse_pointer (member_info& mi) + { + // Object pointers in views require special treatment. + // + if (view_member (mi.m)) + { + using semantics::class_; + + class_& c (*mi.ptr); + class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + if (poly_derived) + // Use a helper to create a complete chain of images all + // the way to the root (see libodb/odb/view-image.hxx). + // + os << "view_object_image<" << endl + << " " << class_fq_name (c) << "," << endl + << " " << class_fq_name (*poly_root) << "," << endl + << " id_" << db << " >"; + else + os << "object_traits_impl< " << class_fq_name (c) << ", " << + "id_" << db << " >::image_type"; + + os << " " << mi.var << "value;" + << endl; + } + else + member_base_impl<T>::traverse_pointer (mi); + } + + virtual void + traverse_composite (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << endl; + } + + protected: + string image_type; + instance<member_image_type> member_image_type_; + }; + + struct image_base: traversal::class_, virtual context + { + typedef image_base base; + + image_base (): first_ (true) {} + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + if (first_) + { + os << ": "; + first_ = false; + } + else + { + os << "," << endl + << " "; + } + + string const& type (class_fq_name (c)); + + if (obj) + os << "object_traits_impl< " << type << ", id_" << db << + " >::image_type"; + else + os << "composite_value_traits< " << type << ", id_" << db << + " >::image_type"; + } + + private: + bool first_; + }; + + struct image_type: traversal::class_, virtual context + { + typedef image_type base; + + image_type () + { + *this >> names_member_ >> member_; + } + + image_type (image_type const&) + : root_context (), context () //@@ -Wextra + { + *this >> names_member_ >> member_; + } + + virtual void + image_extra (type&) + { + } + + virtual void + traverse (type& c) + { + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + os << "struct image_type"; + + if (!view (c)) + { + // Don't go into the base if we are a derived type in a + // polymorphic hierarchy. + // + if (!poly_derived) + { + instance<image_base> b; + traversal::inherits i (*b); + inherits (c, i); + } + } + + os << "{"; + + if (poly_derived) + os << "base_traits::image_type* base;" + << endl; + + names (c); + + // We don't need a version if this is a composite value type + // or reuse-abstract object. + // + if (!(composite (c) || (abstract (c) && !polymorphic (c)))) + os << "std::size_t version;" + << endl; + + image_extra (c); + + os << "};"; + } + + private: + instance<image_member> member_; + traversal::names names_member_; + }; + + // Member-specific traits types for container members. + // + struct container_traits: object_members_base, virtual context + { + typedef container_traits base; + + container_traits (semantics::class_& c) + : object_members_base (true, false, false), c_ (c) + { + } + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&) + { + // We don't want to traverse composite id. + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + if (object (c_)) + object_members_base::traverse_composite (m, c); + else + { + // If we are generating traits for a composite value type, then + // we don't want to go into its bases or it composite members. + // + if (m == 0 && &c == &c_) + names (c); + } + } + + virtual void + container_public_extra_pre (semantics::data_member&, semantics::type&) + { + } + + virtual void + container_public_extra_post (semantics::data_member&, semantics::type&) + { + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& c) + { + using semantics::type; + using semantics::class_; + + // Figure out if this member is from a base object or composite + // value and if it's from an object, whether it is reuse-abstract. + // + bool base, reuse_abst; + + if (object (c_)) + { + base = cur_object != &c_ || + !object (dynamic_cast<type&> (m.scope ())); + reuse_abst = abstract (c_) && !polymorphic (c_); + } + else + { + base = false; // We don't go into bases. + reuse_abst = true; // Always abstract. + } + + container_kind_type ck (container_kind (c)); + + const custom_cxx_type* vct (0); + const custom_cxx_type* ict (0); + const custom_cxx_type* kct (0); + + type& vt (container_vt (m, &vct)); + type* it (0); + type* kt (0); + + bool ordered (false); + bool inverse (context::inverse (m, "value")); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + { + it = &container_it (m, &ict); + ordered = true; + } + break; + } + case ck_map: + case ck_multimap: + { + kt = &container_kt (m, &kct); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + bool smart (!inverse && + (ck != ck_ordered || ordered) && + container_smart (c)); + + string name (flat_prefix_ + public_name (m) + "_traits"); + + // Figure out column counts. + // + size_t id_columns, value_columns, data_columns, cond_columns; + bool versioned (context::versioned (m)); + + if (!reuse_abst) + { + type& idt (container_idt (m)); + + if (class_* idc = composite_wrapper (idt)) + id_columns = column_count (*idc).total; + else + id_columns = 1; + + data_columns = cond_columns = id_columns; + + switch (ck) + { + case ck_ordered: + { + // Add one for the index. + // + if (ordered) + { + data_columns++; + + if (smart) + cond_columns++; + } + break; + } + case ck_map: + case ck_multimap: + { + // Add some for the key. + // + size_t n; + + class_* ptr (object_pointer (*kt)); + semantics::type& t (ptr == 0 ? *kt : utype (*id_member (*ptr))); + + if (class_* comp = composite_wrapper (t)) + n = column_count (*comp).total; + else + n = 1; + + data_columns += n; + + // Key is not currently used (see also bind()). + // + // cond_columns += n; + + break; + } + case ck_set: + case ck_multiset: + { + // Not currently used (see also bind()) + // + // Value is also a key. + // + // class_* ptr (object_pointer (vt)); + // semantics::type& t (ptr == 0 ? vt : utype (*id_member (*ptr))); + // + // if (class_* comp = composite_wrapper (t)) + // cond_columns += column_count (*comp).total; + // else + // cond_columns++; + // + break; + } + } + + { + class_* ptr (object_pointer (vt)); + semantics::type& t (ptr == 0 ? vt : utype (*id_member (*ptr))); + + if (class_* comp = composite_wrapper (t)) + value_columns = column_count (*comp).total; + else + value_columns = 1; + + data_columns += value_columns; + } + + // Store column counts for the source generator. + // + m.set ("id-column-count", id_columns); + m.set ("value-column-count", value_columns); + m.set ("cond-column-count", cond_columns); + m.set ("data-column-count", data_columns); + } + + os << "// " << m.name () << endl + << "//" << endl + << "struct " << exp << name; + + if (base) + { + semantics::class_& b (dynamic_cast<semantics::class_&> (m.scope ())); + string const& type (class_fq_name (b)); + + if (object (b)) + os << ": access::object_traits_impl< " << type << ", id_" << + db << " >::" << name; + else + os << ": access::composite_value_traits< " << type << ", id_" << + db << " >::" << public_name (m) << "_traits"; // No prefix_. + } + + os << "{"; + + container_public_extra_pre (m, c); + + if (!reuse_abst) + { + // column_count + // + os << "static const std::size_t id_column_count = " << + id_columns << "UL;"; + + if (smart) + os << "static const std::size_t value_column_count = " << + value_columns << "UL;" + << "static const std::size_t cond_column_count = " << + cond_columns << "UL;"; + + os << "static const std::size_t data_column_count = " << + data_columns << "UL;" + << endl; + + os << "static const bool versioned = " << versioned << ";" + << endl; + + // Statements. + // + os << "static const char insert_statement[];" + << "static const char select_statement[];"; + + if (smart) + os << "static const char update_statement[];"; + + os << "static const char delete_statement[];" + << endl; + } + + if (base) + { + container_public_extra_post (m, c); + os << "};"; + + return; + } + + // container_type + // container_traits + // index_type + // key_type + // value_type + // + os << "typedef "; + + { + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + if (semantics::type* wt = wrapper (t)) + { + // Use the hint from the wrapper unless the wrapped type is + // qualified. In this case use the hint for the unqualified + // type. + // + hint = t.get<semantics::names*> ("wrapper-hint"); + utype (*wt, hint); + + os << c.fq_name (hint); + } + else + // t and c are the same. + // + os << t.fq_name (hint); + } + + os << " container_type;"; + + os << "typedef" << endl + << "odb::access::container_traits<container_type>" << endl + << "container_traits_type;"; + + switch (ck) + { + case ck_ordered: + { + os << "typedef container_traits_type::index_type index_type;"; + break; + } + case ck_map: + case ck_multimap: + { + os << "typedef container_traits_type::key_type key_type;"; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "typedef container_traits_type::value_type value_type;" + << endl; + + // functions_type + // + switch (ck) + { + case ck_ordered: + { + os << "typedef " << (smart ? "smart_" : "") << + "ordered_functions<index_type, value_type> functions_type;"; + break; + } + case ck_map: + case ck_multimap: + { + os << "typedef map_functions<key_type, value_type> " << + "functions_type;"; + break; + } + case ck_set: + case ck_multiset: + { + os << "typedef set_functions<value_type> functions_type;"; + break; + } + } + + os << "typedef " << db << "::" << (smart ? "smart_" : "") + << "container_statements< " << name << " > statements_type;" + << endl; + + // cond_image_type (object id is taken from the object image). + // + // For dumb containers we use the id binding directly. + // + if (smart) + { + os << "struct cond_image_type" + << "{"; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<image_member> im ( + "index_", *it, ict, "index_type", "index"); + im->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<image_member> im ("key_", *kt, kct, "key_type", "key"); + im->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + os << "// value" << endl + << "//" << endl; + instance<image_member> im ( + "value_", vt, vct, "value_type", "value"); + im->traverse (m); + break; + } + } + + os << "std::size_t version;" + << "};"; + } + + // data_image_type (object id is taken from the object image) + // + os << "struct data_image_type" + << "{"; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<image_member> im ( + "index_", *it, ict, "index_type", "index"); + im->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<image_member> im ("key_", *kt, kct, "key_type", "key"); + im->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "// value" << endl + << "//" << endl; + instance<image_member> im ("value_", vt, vct, "value_type", "value"); + im->traverse (m); + + os << "std::size_t version;" + << "};"; + + // bind (cond_image) + // + if (smart) + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "cond_image_type&);" + << endl; + + // bind (data_image) + // + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "data_image_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // bind (cond_image, data_image) (update) + // + if (smart) + { + os << "static void" << endl + << "bind (" << bind_vector << "," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "cond_image_type&," << endl + << "data_image_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // grow () + // + if (generate_grow) + { + os << "static void" << endl + << "grow (data_image_type&," << endl + << truncated_vector; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // init (data_image) + // + if (!inverse) + { + os << "static void" << endl + << "init (data_image_type&," << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "index_type*," << endl; + break; + } + case ck_map: + case ck_multimap: + { + os << "const key_type*," << endl; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "const value_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // init (cond_image) + // + if (smart) + { + os << "static void" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "init (cond_image_type&, index_type);"; + break; + } + case ck_map: + case ck_multimap: + { + // os << "init (data_image_type&, const key_type&);"; + break; + } + case ck_set: + case ck_multiset: + { + // os << "init (data_image_type&, const value_type&);"; + break; + } + } + + os << endl; + } + + // init (data) + // + os << "static void" << endl + << "init ("; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "index_type&," << endl; + break; + } + case ck_map: + case ck_multimap: + { + os << "key_type&," << endl; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "value_type&," << endl; + os << "const data_image_type&," << endl + << "database*"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // insert + // + os << "static void" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "insert (index_type, const value_type&, void*);"; + break; + } + case ck_map: + case ck_multimap: + { + os << "insert (const key_type&, const value_type&, void*);"; + break; + } + case ck_set: + case ck_multiset: + { + os << "insert (const value_type&, void*);"; + break; + } + } + + os << endl; + + // select + // + os << "static bool" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "select (index_type&, value_type&, void*);"; + break; + } + case ck_map: + case ck_multimap: + { + os << "select (key_type&, value_type&, void*);"; + break; + } + case ck_set: + case ck_multiset: + { + os << "select (value_type&, void*);"; + break; + } + } + + os << endl; + + // update + // + if (smart) + { + os << "static void" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "update (index_type, const value_type&, void*);"; + break; + } + case ck_map: + case ck_multimap: + { + //os << "update (const key_type&, const value_type&, void*);"; + break; + } + case ck_set: + case ck_multiset: + { + //os << "update (const value_type&, const value_type&, void*);"; + break; + } + } + + os << endl; + } + + // delete_ + // + os << "static void" << endl + << "delete_ ("; + + if (smart) + { + switch (ck) + { + case ck_ordered: + { + os << "index_type, "; + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + } + + os << "void*);" + << endl; + + // persist + // + if (!inverse) + { + os << "static void" << endl + << "persist (const container_type&," << endl + << "statements_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // load + // + os << "static void" << endl + << "load (container_type&," << endl + << "statements_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + + // update + // + if (!(inverse || readonly (member_path_, member_scope_))) + { + os << "static void" << endl + << "update (const container_type&," << endl + << "statements_type&"; + + if (versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // erase + // + if (!inverse) + { + os << "static void" << endl + << "erase ("; + + if (smart) + os << "const container_type*, "; + + os << "statements_type&);" + << endl; + } + + container_public_extra_post (m, c); + + os << "};"; + } + + protected: + semantics::class_& c_; + }; + + // + // + struct section_traits: virtual context + { + typedef section_traits base; + + section_traits (semantics::class_& c): c_ (c) {} + + virtual void + section_public_extra_pre (user_section&) + { + } + + virtual void + section_public_extra_post (user_section&) + { + } + + virtual void + traverse (user_section& s) + { + semantics::class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c_); + + semantics::data_member* opt (optimistic (c_)); + + // Treat the special version update sections as abstract in reuse + // inheritance. + // + bool reuse_abst (!poly && + (abstract (c_) || + s.special == user_section::special_version)); + + bool load (s.total != 0 && s.separate_load ()); + bool load_con (s.containers && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_con (s.readwrite_containers); + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + // Don't generate anything for empty sections. + // + if (!(load || load_con || load_opt || + update || update_con || update_opt)) + return; + + // If we are adding a new section to a derived class in an optimistic + // hierarchy, then pretend it inherits from the special version update + // section. + // + user_section* rs (0); + if (opt != 0) + { + // Skip overrides and get to the new section if polymorphic. + // + for (rs = &s; poly && rs->base != 0; rs = rs->base) ; + + if (rs != 0) + { + if (rs->object != &opt->scope ()) + rs->base = &(poly ? poly_root : &opt->scope ())-> + get<user_sections> ("user-sections").back (); + else + rs = 0; + } + } + + string name (public_name (*s.member) + "_traits"); + + os << "// " << s.member->name () << endl + << "//" << endl + << "struct " << exp << name + << "{"; + + os << "typedef object_traits_impl<object_type, id_" << db << + ">::image_type image_type;" + << "typedef object_traits_impl<object_type, id_" << db << + ">::id_image_type id_image_type;" + << endl; + + section_public_extra_pre (s); + + // bind (id, image_type) + // + // If id is NULL, then id is ignored (select). Otherwise, it is + // copied at the end (update). + // + if (load || load_opt || update || update_opt) + { + os << "static std::size_t" << endl + << "bind (" << bind_vector << "," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "image_type&," << endl + << db << "::statement_kind"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // grow () + // + // We have to have out own version because the truncated vector + // will have different number of elements. + // + if (generate_grow && (load || load_opt)) + { + os << "static bool" << endl + << "grow (image_type&," << endl + << truncated_vector; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // init (object, image) + // + if (load) + { + os << "static void" << endl + << "init (object_type&," << endl + << "const image_type&," << endl + << "database*"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // init (image, object) + // + if (update) + { + os << "static " << (generate_grow ? "bool" : "void") << endl + << "init (image_type&," << endl + << "const object_type&"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration&"; + + os << ");" + << endl; + } + + // The rest does not apply to reuse-abstract sections. + // + if (reuse_abst) + { + section_public_extra_post (s); + os << "};"; + return; + } + + // column_count + // + column_count_type const& cc (column_count (poly ? *poly_root : c_)); + + // Generate load and update column counts even when they are zero so + // that we can instantiate section_statements. + // + os << "static const std::size_t id_column_count = " << cc.id << "UL;"; + + os << "static const std::size_t managed_optimistic_load_column_count" << + " = " << cc.optimistic_managed << "UL;" + << "static const std::size_t load_column_count = " << + (load ? s.total_total () : 0) << "UL;"; + + os << "static const std::size_t managed_optimistic_update_column_count" << + " = " << (poly_derived ? 0 : cc.optimistic_managed) << "UL;" + << "static const std::size_t update_column_count = " << + (update ? s.total - s.inverse - s.readonly : 0) << "UL;" + << endl; + + os << "static const bool versioned = " << s.versioned << ";" + << endl; + + // Statements. + // + if (load || load_opt) + os << "static const char select_statement[];" + << endl; + + if (update || update_opt) + os << "static const char update_statement[];" + << endl; + + // Section statements. + // + if (load || load_opt || update || update_opt) + os << "typedef " << db << "::section_statements< object_type, " << + name << " > statements_type;" + << endl; + + // We pass statement cache instead of just statements because + // we may also need statements for containers. + // + + // load () + // + if (load || load_opt || load_con) + os << "static void" << endl + << "load (extra_statement_cache_type&, object_type&" << + (poly ? ", bool top = true" : "") << ");" + << endl; + + // update () + // + if (update || update_opt || update_con) + os << "static void" << endl + << "update (extra_statement_cache_type&, const object_type&" << + (poly_derived && s.base != 0 ? ", bool base = true" : "") << ");" + << endl; + + section_public_extra_post (s); + + os << "};"; + + if (rs != 0) + rs->base = 0; + } + + protected: + semantics::class_& c_; + }; + + // First pass over objects, views, and composites. Some code must be + // split into two parts to deal with yet undefined types. + // + struct class1: traversal::class_, virtual context + { + typedef class1 base; + + class1 () + : typedefs_ (false), + id_image_member_ ("id_"), + version_image_member_ ("version_"), + discriminator_image_member_ ("discriminator_"), + query_columns_type_ (false, true, false), + pointer_query_columns_type_ (true, true, false) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + class1 (class1 const&) + : root_context (), //@@ -Wextra + context (), + typedefs_ (false), + id_image_member_ ("id_"), + version_image_member_ ("version_"), + discriminator_image_member_ ("discriminator_"), + query_columns_type_ (false, true, false), + pointer_query_columns_type_ (true, true, false) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + case class_composite: traverse_composite (c); break; + default: break; + } + } + + virtual void + object_public_extra_pre (type&) + { + } + + virtual void + object_public_extra_post (type&) + { + } + + virtual void + traverse_object (type&); + + virtual void + view_public_extra_pre (type&) + { + } + + virtual void + view_public_extra_post (type&) + { + } + + virtual void + traverse_view (type&); + + virtual void + traverse_composite (type&); + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<image_type> image_type_; + instance<image_member> id_image_member_; + instance<image_member> version_image_member_; + instance<image_member> discriminator_image_member_; + + instance<query_columns_type> query_columns_type_; + instance<query_columns_type> pointer_query_columns_type_; + }; + + // Second pass over objects, views, and composites. + // + struct class2: traversal::class_, virtual context + { + typedef class2 base; + + class2 () + : typedefs_ (false), + query_columns_type_ (false, true, false), + query_columns_type_inst_ (false, false, true), + view_query_columns_type_ (true) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + class2 (class2 const&) + : root_context (), //@@ -Wextra + context (), + typedefs_ (false), + query_columns_type_ (false, true, false), + query_columns_type_inst_ (false, false, true), + view_query_columns_type_ (true) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + case class_composite: traverse_composite (c); break; + default: break; + } + } + + virtual void + traverse_object (type& c) + { + if (options.generate_query ()) + { + os << "// " << class_name (c) << endl + << "//" << endl; + + // query_columns + // + // If we don't have any pointers, then query_columns is generated + // in pass 1 (see the comment in class1 for details). + // + if (has_a (c, test_pointer | include_base)) + query_columns_type_->traverse (c); + + // Generate extern template declarations. + // + if (multi_dynamic) + query_columns_type_inst_->traverse (c); + } + + // Move header comment out of if-block if adding any code here. + } + + virtual void + traverse_view (type& c) + { + // query_columns + // + if (c.get<size_t> ("object-count") != 0) + { + os << "// " << class_name (c) << endl + << "//" << endl; + + view_query_columns_type_->traverse (c); + } + + // Move header comment out of if-block if adding any code here. + } + + virtual void + traverse_composite (type&) + { + } + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<query_columns_type> query_columns_type_; + instance<query_columns_type> query_columns_type_inst_; + instance<view_query_columns_type> view_query_columns_type_; + }; + + struct include: virtual context + { + typedef include base; + + virtual void + generate () + { + os << "#include <odb/details/buffer.hxx>" << endl + << endl; + + os << "#include <odb/" << db << "/version.hxx>" << endl + << "#include <odb/" << db << "/forward.hxx>" << endl + << "#include <odb/" << db << "/binding.hxx>" << endl + << "#include <odb/" << db << "/" << db << "-types.hxx>" << endl; + + if (options.generate_query ()) + { + os << "#include <odb/" << db << "/query.hxx>" << endl; + + if (multi_dynamic) + os << "#include <odb/" << db << "/query-dynamic.hxx>" << endl; + } + + os << endl; + } + }; + } +} + +#endif // ODB_RELATIONAL_HEADER_HXX diff --git a/odb/odb/relational/inline.cxx b/odb/odb/relational/inline.cxx new file mode 100644 index 0000000..5e60705 --- /dev/null +++ b/odb/odb/relational/inline.cxx @@ -0,0 +1,47 @@ +// file : odb/relational/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace inline_ + { + void + generate () + { + context ctx; + ostream& os (ctx.os); + + instance<include> i; + i->generate (); + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + class_ c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + os << "namespace odb" + << "{"; + + unit.dispatch (ctx.unit); + + os << "}"; + } + } +} diff --git a/odb/odb/relational/inline.hxx b/odb/odb/relational/inline.hxx new file mode 100644 index 0000000..a609cc1 --- /dev/null +++ b/odb/odb/relational/inline.hxx @@ -0,0 +1,693 @@ +// file : odb/relational/inline.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_INLINE_HXX +#define ODB_RELATIONAL_INLINE_HXX + +#include <odb/diagnostics.hxx> +#include <odb/relational/context.hxx> +#include <odb/relational/common.hxx> + +namespace relational +{ + namespace inline_ + { + // + // get/set null (composite value only) + // + + struct null_member: virtual member_base + { + typedef null_member base; + + null_member (bool get) + : member_base (0, 0, string (), string ()), get_ (get) {} + + protected: + bool get_; + }; + + template <typename T> + struct null_member_impl: null_member, virtual member_base_impl<T> + { + typedef null_member_impl base_impl; + + null_member_impl (base const& x): base (x) {} + + typedef typename member_base_impl<T>::member_info member_info; + + virtual bool + pre (member_info& mi) + { + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + + // If the whole value type is readonly, then set will never be + // called with sk == statement_update. + // + if (!get_ && !readonly (*context::top_object)) + { + semantics::class_* c; + + if (readonly (mi.m) || ((c = composite (mi.t)) && readonly (*c))) + os << "if (sk == statement_insert)" << endl; + } + + return true; + } + + virtual void + post (member_info& mi) + { + if (added (mi.m) || deleted (mi.m)) + os << "}"; + } + + virtual void + traverse_composite (member_info& mi) + { + string traits ("composite_value_traits< " + mi.fq_type () + ", id_" + + db.string () + " >"); + + if (get_) + os << "r = r && " << traits << "::get_null (" << + "i." << mi.var << "value"; + else + os << traits << "::set_null (i." << mi.var << "value, sk"; + + if (versioned (*composite (mi.t))) + os << ", svm"; + + os << ");"; + } + }; + + struct null_base: traversal::class_, virtual context + { + typedef null_base base; + + null_base (bool get): get_ (get) {} + + virtual void + traverse (type& c) + { + // Ignore transient bases. + // + if (!composite (c)) + return; + + string traits ("composite_value_traits< " + class_fq_name (c) + + ", id_" + db.string () + " >"); + + // If the derived value type is readonly, then set will never be + // called with sk == statement_update. + // + if (!get_ && readonly (c) && !readonly (*context::top_object)) + os << "if (sk == statement_insert)" << endl; + + if (get_) + os << "r = r && " << traits << "::get_null (i"; + else + os << traits << "::set_null (i, sk"; + + if (versioned (c)) + os << ", svm"; + + os << ");"; + } + + protected: + bool get_; + }; + + // + // + struct class_: traversal::class_, virtual context + { + typedef class_ base; + + class_ () + : typedefs_ (false), + get_null_base_ (true), + get_null_member_ (true), + set_null_base_ (false), + set_null_member_ (false) + { + init (); + } + + class_ (class_ const&) + : root_context (), //@@ -Wextra + context (), + typedefs_ (false), + get_null_base_ (true), + get_null_member_ (true), + set_null_base_ (false), + set_null_member_ (false) + { + init (); + } + + void + init () + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + get_null_base_inherits_ >> get_null_base_; + get_null_member_names_ >> get_null_member_; + + set_null_base_inherits_ >> set_null_base_; + set_null_member_names_ >> set_null_member_; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + context::top_object = context::cur_object = &c; + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + case class_composite: traverse_composite (c); break; + default: break; + } + + context::top_object = context::cur_object = 0; + } + + virtual void + object_extra (type&) + { + } + + virtual void + traverse_object (type& c) + { + using semantics::data_member; + + data_member_path* id (id_member (c)); + data_member* idf (id ? id->front () : 0); + bool auto_id (id && auto_ (*id)); + bool base_id (id && &idf->scope () != &c); // Comes from base. + data_member* optimistic (context::optimistic (c)); + + // Base class that contains the object id and version for optimistic + // concurrency. + // + type* base (base_id ? dynamic_cast<type*> (&idf->scope ()) : 0); + + type* poly_root (context::polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + bool abst (abstract (c)); + bool reuse_abst (abst && !poly); + + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + string traits ("access::object_traits_impl< " + type + ", id_" + + db.string () + " >"); + + user_sections& uss (c.get<user_sections> ("user-sections")); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + object_extra (c); + + if (id != 0 && base_id) + { + if (!poly_derived) + { + // id (id_image_type) + // + if (auto_id) + { + os << "inline" << endl + << traits << "::id_type" << endl + << traits << "::" << endl + << "id (const id_image_type& i)" + << "{" + << "return object_traits_impl< " << class_fq_name (*base) << + ", id_" << db << " >::id (i);" + << "}"; + } + + // id (image_type) + // + if (options.generate_query ()) + { + os << "inline" << endl + << traits << "::id_type" << endl + << traits << "::" << endl + << "id (const image_type& i)" + << "{" + << "return object_traits_impl< " << class_fq_name (*base) << + ", id_" << db << " >::id (i);" + << "}"; + } + + // version (image_type) + // + if (optimistic != 0) + { + os << "inline" << endl + << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const image_type& i)" + << "{" + << "return object_traits_impl< " << class_fq_name (*base) << + ", id_" << db << " >::version (i);" + << "}"; + } + } + + // bind (id_image_type) + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b, id_image_type& i" << + (optimistic != 0 ? ", bool bv" : "") << ")" + << "{" + << "object_traits_impl< " << class_fq_name (*base) << ", id_" << + db << " >::bind (b, i" << (optimistic != 0 ? ", bv" : "") << ");" + << "}"; + + os << "inline" << endl + << "void " << traits << "::" << endl + << "init (id_image_type& i, const id_type& id" << + (optimistic != 0 ? ", const version_type* v" : "") << ")" + << "{" + << "object_traits_impl< " << class_fq_name (*base) << ", id_" << + db << " >::init (i, id" << (optimistic != 0 ? ", v" : "") << ");" + << "}"; + } + + if (poly_derived) + { + size_t depth (polymorphic_depth (c)); + + // check_version + // + os << "inline" << endl + << "bool " << traits << "::" << endl + << "check_version (const std::size_t* v, const image_type& i)" + << "{" + << "return "; + + string image ("i."); + for (size_t i (0); i < depth; ++i) + { + os << (i == 0 ? "" : " ||") << endl + << " v[" << i << "UL] != " << image << "version"; + + image += "base->"; + } + + os << ";" + << "}"; + + // update_version + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "update_version (std::size_t* v, const image_type& i, " << + db << "::binding* b)" + << "{"; + + image = "i."; + for (size_t i (0); i < depth; ++i) + { + os << "v[" << i << "UL] = " << image << "version;"; + image += "base->"; + } + + // A poly-abstract class always has only one entry in the + // bindings array. + // + if (abst) + os << "b[0].version++;"; + else + for (size_t i (0); i < depth; ++i) + os << "b[" << i << "UL].version++;"; + + os << "}"; + } + + // The rest does not apply to reuse-abstract objects. + // + if (reuse_abst) + return; + + // erase (object_type) + // + if (id != 0 && !poly && optimistic == 0 && + !has_a (c, test_smart_container)) + { + os << "inline" << endl + << "void " << traits << "::" << endl + << "erase (database& db, const object_type& obj)" + << "{" + << "callback (db, obj, callback_event::pre_erase);" + << "erase (db, id (obj));"; + + // Note that we don't reset sections since the object is now + // transient and the state of a section in a transient object + // is undefined. + + os << "callback (db, obj, callback_event::post_erase);" + << "}"; + } + + // load (section) [thunk version; poly_derived is true] + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0 && + uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) == 0) + { + os << "inline" << endl + << "bool " << traits << "::" << endl + << "load (connection& conn, object_type& obj, section& s, " << + "const info_type* pi)" + << "{" + << "return base_traits::load (conn, obj, s, pi);" + << "}"; + } + + // update (section) [thunk version; poly_derived is true] + // + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0 && + uss.count (user_sections::count_new | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) == 0) + { + os << "inline" << endl + << "bool " << traits << "::" << endl + << "update (connection& conn, const object_type& obj, " << + "const section& s, const info_type* pi)" + << "{" + << "return base_traits::update (conn, obj, s, pi);" + << "}"; + } + + // load_() + // + if (id != 0 && + !(poly_derived || + has_a (c, test_container | include_eager_load, &main_section) || + uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0)) + { + os << "inline" << endl + << "void " << traits << "::" << endl + << "load_ (statements_type& sts," << endl + << "object_type& obj," << endl + << "bool"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (sts);" + << "ODB_POTENTIALLY_UNUSED (obj);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + // Mark eager sections as loaded. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // We can only end up here if the object itself is versioned + // (simple value section). + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" << endl; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + } + + os << "}"; + } + + if (poly && need_image_clone && options.generate_query ()) + { + // root_image () + // + os << "inline" << endl + << traits << "::root_traits::image_type&" << endl + << traits << "::" << endl + << "root_image (image_type& i)" + << "{"; + + if (poly_derived) + os << "return base_traits::root_image (*i.base);"; + else + os << "return i;"; + + os << "}"; + + // clone_image () + // + os << "inline" << endl + << traits << "::image_type*" << endl + << traits << "::" << endl + << "clone_image (image_type& i)" + << "{"; + + if (poly_derived) + os << "details::unique_ptr<base_traits::image_type> p (" << endl + << "base_traits::clone_image (*i.base));" + << "image_type* c (new image_type (i));" + << "c->base = p.release ();" + << "return c;"; + else + os << "return new image_type (i);"; + + os << "}"; + + // copy_image () + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "copy_image (image_type& d, image_type& s)" + << "{"; + + if (poly_derived) + os << "base_traits::image_type* b (d.base);" + << "base_traits::copy_image (*b, *s.base);" + << "d = s;" // Overwrites the base pointer. + << "d.base = b;"; + else + os << "d = s;"; + + os << "}"; + + // free_image () + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "free_image (image_type* i)" + << "{"; + + if (poly_derived) + os << "base_traits::free_image (i->base);"; + + os << "delete i;" + << "}"; + } + } + + virtual void + view_extra (type&) + { + } + + virtual void + traverse_view (type& c) + { + string const& type (class_fq_name (c)); + string traits ("access::view_traits_impl< " + type + ", id_" + + db.string () + " >"); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + view_extra (c); + } + + virtual void + traverse_composite (type& c) + { + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + string traits ("access::composite_value_traits< " + type + ", id_" + + db.string () + " >"); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + if (!has_a (c, test_container)) + { + // get_null (image) + // + os << "inline" << endl + << "bool " << traits << "::" << endl + << "get_null (const image_type& i"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);" + << endl; + + os << "bool r (true);"; + + inherits (c, get_null_base_inherits_); + names (c, get_null_member_names_); + + os << "return r;" + << "}"; + + // set_null (image) + // + os << "inline" << endl + << "void " << traits << "::" << endl + << "set_null (image_type& i," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + inherits (c, set_null_base_inherits_); + names (c, set_null_member_names_); + + os << "}"; + } + } + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<null_base> get_null_base_; + traversal::inherits get_null_base_inherits_; + instance<null_member> get_null_member_; + traversal::names get_null_member_names_; + + instance<null_base> set_null_base_; + traversal::inherits set_null_base_inherits_; + instance<null_member> set_null_member_; + traversal::names set_null_member_names_; + }; + + struct include: virtual context + { + typedef include base; + + virtual void + generate () + { + if (versioned ()) + os << "#include <odb/schema-version.hxx>" << endl + << endl; + + if (features.polymorphic_object && options.generate_query ()) + os << "#include <odb/details/unique-ptr.hxx>" << endl + << endl; + } + }; + } +} + +#endif // ODB_RELATIONAL_INLINE_HXX diff --git a/odb/odb/relational/model.cxx b/odb/odb/relational/model.cxx new file mode 100644 index 0000000..45d555a --- /dev/null +++ b/odb/odb/relational/model.cxx @@ -0,0 +1,121 @@ +// file : odb/relational/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/diagnostics.hxx> + +#include <odb/relational/model.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace model + { + // object_columns + // + string object_columns:: + default_ (semantics::data_member& m) + { + default_value* dv (0); + + semantics::type& t (utype (m)); + + if (m.count ("default")) + dv = &m.get<default_value> ("default"); + else if (t.count ("default")) + dv = &t.get<default_value> ("default"); + else + return ""; // No default value for this column. + + switch (dv->kind) + { + case default_value::reset: + { + return ""; // No default value. + } + case default_value::null: + { + return default_null (m); + } + case default_value::boolean: + { + return default_bool (m, dv->literal == "true"); + } + case default_value::integer: + { + return default_integer (m, dv->int_value, dv->literal == "-"); + } + case default_value::floating: + { + return default_float (m, dv->float_value); + } + case default_value::string: + { + return default_string (m, dv->literal); + } + case default_value::enumerator: + { + return default_enum (m, dv->enum_value, dv->literal); + } + } + + return ""; + } + + cutl::shared_ptr<sema_rel::model> + generate () + { + context ctx; + cutl::shared_ptr<sema_rel::model> m ( + new (shared) sema_rel::model ( + ctx.versioned () ? ctx.version ().current : 0)); + m->set ("deleted-map", deleted_table_map ()); + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + instance<class_> c (*m); + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + try + { + unit.dispatch (ctx.unit); + } + catch (sema_rel::duplicate_name const& e) + { + location const& o (e.orig.get<location> ("cxx-location")); + location const& d (e.dup.get<location> ("cxx-location")); + + error (d) << e.dup.kind () << " name '" << e.name << "' conflicts " + << "with an already defined " << e.orig.kind () << " name" + << endl; + + info (o) << "conflicting " << e.orig.kind () << " is defined here" + << endl; + + if (e.dup.kind () == "index") + info (d) << "use #pragma db index to change its name" << endl; + else if (e.dup.kind () == "table") + info (d) << "use #pragma db table to change its name" << endl; + else + info (d) << "use #pragma db column to change its name" << endl; + + throw operation_failed (); + } + + return m; + } + } +} 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 diff --git a/odb/odb/relational/mssql/common.cxx b/odb/odb/relational/mssql/common.cxx new file mode 100644 index 0000000..1070d21 --- /dev/null +++ b/odb/odb/relational/mssql/common.cxx @@ -0,0 +1,603 @@ +// file : odb/relational/mssql/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/mssql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + const sql_type& st (*mi.st); + + // The same long/short data test as in context.cxx:long_data(). + // + switch (st.type) + { + // Integral types. + // + case sql_type::BIT: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::INT: + case sql_type::BIGINT: + { + traverse_integer (mi); + break; + } + + // Fixed and floating point types. + // + case sql_type::DECIMAL: + { + traverse_decimal (mi); + break; + } + case sql_type::SMALLMONEY: + { + traverse_smallmoney (mi); + break; + } + case sql_type::MONEY: + { + traverse_money (mi); + break; + } + case sql_type::FLOAT: + { + if (st.prec > 24) + traverse_float8 (mi); + else + traverse_float4 (mi); + + break; + } + + // String and binary types. + // + case sql_type::CHAR: + case sql_type::VARCHAR: + { + // Zero precision means max in VARCHAR(max). + // + if (st.prec == 0 || st.prec > options.mssql_short_limit ()) + traverse_long_string (mi); + else + traverse_string (mi); + + break; + } + case sql_type::TEXT: + { + traverse_long_string (mi); + break; + } + case sql_type::NCHAR: + case sql_type::NVARCHAR: + { + // Zero precision means max in NVARCHAR(max). Note that + // the precision is in 2-byte UCS-2 characters, not bytes. + // + if (st.prec == 0 || st.prec * 2 > options.mssql_short_limit ()) + traverse_long_nstring (mi); + else + traverse_nstring (mi); + + break; + } + case sql_type::NTEXT: + { + traverse_long_nstring (mi); + break; + } + case sql_type::BINARY: + case sql_type::VARBINARY: + { + // Zero precision means max in VARCHAR(max). + // + if (st.prec == 0 || st.prec > options.mssql_short_limit ()) + traverse_long_binary (mi); + else + traverse_binary (mi); + + break; + } + case sql_type::IMAGE: + { + traverse_long_binary (mi); + break; + } + + // Date-time types. + // + case sql_type::DATE: + { + traverse_date (mi); + break; + } + case sql_type::TIME: + { + traverse_time (mi); + break; + } + case sql_type::DATETIME: + case sql_type::DATETIME2: + case sql_type::SMALLDATETIME: + { + traverse_datetime (mi); + break; + } + case sql_type::DATETIMEOFFSET: + { + traverse_datetimeoffset (mi); + break; + } + + // Other types. + // + case sql_type::UNIQUEIDENTIFIER: + { + traverse_uniqueidentifier (mi); + break; + } + case sql_type::ROWVERSION: + { + traverse_rowversion (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + static const char* integer_types[] = + { + "unsigned char", + "unsigned char", + "short", + "int", + "long long" + }; + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_mssql >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info& mi) + { + type_ = integer_types[mi.st->type - sql_type::BIT]; + } + + void member_image_type:: + traverse_decimal (member_info&) + { + type_ = "mssql::decimal"; + } + + void member_image_type:: + traverse_smallmoney (member_info&) + { + type_ = "mssql::smallmoney"; + } + + void member_image_type:: + traverse_money (member_info&) + { + type_ = "mssql::money"; + } + + void member_image_type:: + traverse_float4 (member_info&) + { + type_ = "float"; + } + + void member_image_type:: + traverse_float8 (member_info&) + { + type_ = "double"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_long_string (member_info&) + { + type_ = "mssql::long_callback"; + } + + void member_image_type:: + traverse_nstring (member_info&) + { + type_ = "mssql::ucs2_char*"; + } + + void member_image_type:: + traverse_long_nstring (member_info&) + { + type_ = "mssql::long_callback"; + } + + void member_image_type:: + traverse_binary (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_long_binary (member_info&) + { + type_ = "mssql::long_callback"; + } + + void member_image_type:: + traverse_date (member_info&) + { + type_ = "mssql::date"; + } + + void member_image_type:: + traverse_time (member_info&) + { + type_ = "mssql::time"; + } + + void member_image_type:: + traverse_datetime (member_info&) + { + type_ = "mssql::datetime"; + } + + void member_image_type:: + traverse_datetimeoffset (member_info&) + { + type_ = "mssql::datetimeoffset"; + } + + void member_image_type:: + traverse_uniqueidentifier (member_info&) + { + type_ = "mssql::uniqueidentifier"; + } + + void member_image_type:: + traverse_rowversion (member_info&) + { + type_ = "unsigned char*"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + static const char* integer_database_id[] = + { + "mssql::id_bit", + "mssql::id_tinyint", + "mssql::id_smallint", + "mssql::id_int", + "mssql::id_bigint" + }; + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) + { + } + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) + { + } + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) + { + } + + string member_database_type_id:: + database_type_id (semantics::data_member& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_integer (member_info& mi) + { + type_id_ = integer_database_id[mi.st->type - sql_type::BIT]; + } + + void member_database_type_id:: + traverse_decimal (member_info&) + { + type_id_ = "mssql::id_decimal"; + } + + void member_database_type_id:: + traverse_smallmoney (member_info&) + { + type_id_ = "mssql::id_smallmoney"; + } + + void member_database_type_id:: + traverse_money (member_info&) + { + type_id_ = "mssql::id_money"; + } + + void member_database_type_id:: + traverse_float4 (member_info&) + { + type_id_ = "mssql::id_float4"; + } + + void member_database_type_id:: + traverse_float8 (member_info&) + { + type_id_ = "mssql::id_float8"; + } + + void member_database_type_id:: + traverse_string (member_info&) + { + type_id_ = "mssql::id_string"; + } + + void member_database_type_id:: + traverse_long_string (member_info&) + { + type_id_ = "mssql::id_long_string"; + } + + void member_database_type_id:: + traverse_nstring (member_info&) + { + type_id_ = "mssql::id_nstring"; + } + + void member_database_type_id:: + traverse_long_nstring (member_info&) + { + type_id_ = "mssql::id_long_nstring"; + } + + void member_database_type_id:: + traverse_binary (member_info&) + { + type_id_ = "mssql::id_binary"; + } + + void member_database_type_id:: + traverse_long_binary (member_info&) + { + type_id_ = "mssql::id_long_binary"; + } + + void member_database_type_id:: + traverse_date (member_info&) + { + type_id_ = "mssql::id_date"; + } + + void member_database_type_id:: + traverse_time (member_info&) + { + type_id_ = "mssql::id_time"; + } + + void member_database_type_id:: + traverse_datetime (member_info&) + { + type_id_ = "mssql::id_datetime"; + } + + void member_database_type_id:: + traverse_datetimeoffset (member_info&) + { + type_id_ = "mssql::id_datetimeoffset"; + } + + void member_database_type_id:: + traverse_uniqueidentifier (member_info&) + { + type_id_ = "mssql::id_uniqueidentifier"; + } + + void member_database_type_id:: + traverse_rowversion (member_info&) + { + type_id_ = "mssql::id_rowversion"; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + virtual void + column_ctor (string const& type, string const& name, string const& base) + { + os << name << " ("; + + if (multi_dynamic) + os << "odb::query_column< " << type << " >& qc," << endl; + + os << "const char* t," << endl + << "const char* c," << endl + << "const char* conv," << endl + << "unsigned short p = 0," << endl + << "unsigned short s = 0xFFFF)" << endl + << " : " << base << " (" << (multi_dynamic ? "qc, " : "") << + "t, c, conv, p, s)" + << "{" + << "}"; + } + + virtual void + column_ctor_args_extra (semantics::data_member& m) + { + // For some types we need to pass precision and scale. + // + sql_type const& st (parse_sql_type (column_type (), m)); + + switch (st.type) + { + case sql_type::DECIMAL: + { + os << ", " << st.prec << ", " << st.scale; + break; + } + case sql_type::FLOAT: + { + os << ", " << st.prec; + break; + } + case sql_type::CHAR: + case sql_type::VARCHAR: + { + os << ", " << st.prec; + break; + } + case sql_type::TEXT: + { + os << ", 0"; // Unlimited. + break; + } + case sql_type::NCHAR: + case sql_type::NVARCHAR: + { + os << ", " << st.prec; // In 2-byte characters. + break; + } + case sql_type::NTEXT: + { + os << ", 0"; // Unlimited. + break; + } + case sql_type::BINARY: + case sql_type::VARBINARY: + { + os << ", " << st.prec; + break; + } + case sql_type::IMAGE: + { + os << ", 0"; // Unlimited. + break; + } + // Date-time types. + // + case sql_type::TIME: + case sql_type::DATETIME2: + case sql_type::DATETIMEOFFSET: + { + os << ", 0, " << st.scale; // Fractional seconds (scale). + break; + } + case sql_type::DATETIME: + { + os << ", 0, 3"; + break; + } + case sql_type::SMALLDATETIME: + { + os << ", 0, 8"; + break; + } + default: + { + break; + } + } + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/mssql/common.hxx b/odb/odb/relational/mssql/common.hxx new file mode 100644 index 0000000..42ea412 --- /dev/null +++ b/odb/odb/relational/mssql/common.hxx @@ -0,0 +1,293 @@ +// file : odb/relational/mssql/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MSSQL_COMMON_HXX +#define ODB_RELATIONAL_MSSQL_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/mssql/context.hxx> + +namespace relational +{ + namespace mssql + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the mssql namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_integer (member_info&) + { + } + + virtual void + traverse_decimal (member_info&) + { + } + + virtual void + traverse_smallmoney (member_info&) + { + } + + virtual void + traverse_money (member_info&) + { + } + + virtual void + traverse_float4 (member_info&) + { + } + + virtual void + traverse_float8 (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_long_string (member_info&) + { + } + + virtual void + traverse_nstring (member_info&) + { + } + + virtual void + traverse_long_nstring (member_info&) + { + } + + virtual void + traverse_binary (member_info&) + { + } + + virtual void + traverse_long_binary (member_info&) + { + } + + virtual void + traverse_date (member_info&) + { + } + + virtual void + traverse_time (member_info&) + { + } + + virtual void + traverse_datetime (member_info&) + { + } + + virtual void + traverse_datetimeoffset (member_info&) + { + } + + virtual void + traverse_uniqueidentifier (member_info&) + { + } + + virtual void + traverse_rowversion (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_smallmoney (member_info&); + + virtual void + traverse_money (member_info&); + + virtual void + traverse_float4 (member_info&); + + virtual void + traverse_float8 (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_long_string (member_info&); + + virtual void + traverse_nstring (member_info&); + + virtual void + traverse_long_nstring (member_info&); + + virtual void + traverse_binary (member_info&); + + virtual void + traverse_long_binary (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_time (member_info&); + + virtual void + traverse_datetime (member_info&); + + virtual void + traverse_datetimeoffset (member_info&); + + virtual void + traverse_uniqueidentifier (member_info&); + + virtual void + traverse_rowversion (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_smallmoney (member_info&); + + virtual void + traverse_money (member_info&); + + virtual void + traverse_float4 (member_info&); + + virtual void + traverse_float8 (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_long_string (member_info&); + + virtual void + traverse_nstring (member_info&); + + virtual void + traverse_long_nstring (member_info&); + + virtual void + traverse_binary (member_info&); + + virtual void + traverse_long_binary (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_time (member_info&); + + virtual void + traverse_datetime (member_info&); + + virtual void + traverse_datetimeoffset (member_info&); + + virtual void + traverse_uniqueidentifier (member_info&); + + virtual void + traverse_rowversion (member_info&); + + private: + string type_id_; + }; + + struct has_long_data: object_columns_base, context + { + has_long_data (bool& r): r_ (r) {} + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + if (!inverse (m, key_prefix_)) + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, string const&, bool) + { + if (long_data (parse_sql_type (column_type (), m))) + r_ = true; + + return true; + } + + private: + bool& r_; + }; + } +} +#endif // ODB_RELATIONAL_MSSQL_COMMON_HXX diff --git a/odb/odb/relational/mssql/context.cxx b/odb/odb/relational/mssql/context.cxx new file mode 100644 index 0000000..afe1aa5 --- /dev/null +++ b/odb/odb/relational/mssql/context.cxx @@ -0,0 +1,766 @@ +// file : odb/relational/mssql/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> +#include <sstream> + +#include <odb/sql-token.hxx> +#include <odb/sql-lexer.hxx> + +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace + { + struct type_map_entry + { + char const* const cxx_type; + char const* const db_type; + char const* const db_id_type; + bool const null; + }; + + type_map_entry type_map[] = + { + {"bool", "BIT", 0, false}, + + {"char", "CHAR(1)", 0, false}, + {"wchar_t", "NCHAR(1)", 0, false}, + {"signed char", "TINYINT", 0, false}, + {"unsigned char", "TINYINT", 0, false}, + + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT", 0, false}, + + {"int", "INT", 0, false}, + {"unsigned int", "INT", 0, false}, + + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT", 0, false}, + + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT", 0, false}, + + {"float", "REAL", 0, false}, + {"double", "FLOAT", 0, false}, + + {"::std::string", "VARCHAR(512)", "VARCHAR(256)", false}, + {"::std::wstring", "NVARCHAR(512)", "NVARCHAR(256)", false}, + + {"::size_t", "BIGINT", 0, false}, + {"::std::size_t", "BIGINT", 0, false}, + + // Windows GUID/UUID (typedef struct _GUID {...} GUID, UUID;). + // + {"::_GUID", "UNIQUEIDENTIFIER", 0, false} + }; + } + + context* context::current_; + + context:: + ~context () + { + if (current_ == this) + current_ = 0; + } + + context:: + context (ostream& os, + semantics::unit& u, + options_type const& ops, + features_type& f, + sema_rel::model* m) + : root_context (os, u, ops, f, data_ptr (new (shared) data (os))), + base_context (static_cast<data*> (root_context::data_.get ()), m), + data_ (static_cast<data*> (base_context::data_)) + { + assert (current_ == 0); + current_ = this; + + generate_grow = false; + need_alias_as = true; + insert_send_auto_id = false; + delay_freeing_statement_result = true; + need_image_clone = true; + generate_bulk = true; + global_index = false; + global_fkey = true; + data_->bind_vector_ = "mssql::bind*"; + + // Populate the C++ type to DB type map. + // + for (size_t i (0); i < sizeof (type_map) / sizeof (type_map_entry); ++i) + { + type_map_entry const& e (type_map[i]); + + type_map_type::value_type v ( + e.cxx_type, + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); + + data_->type_map_.insert (v); + } + } + + context:: + context () + : data_ (current ().data_) + { + } + + string const& context:: + convert_expr (string const& sqlt, semantics::data_member& m, bool to) + { + sql_type const& t (parse_sql_type (sqlt, m)); + return to ? t.to : t.from; + } + + string context:: + quote_id_impl (qname const& id) const + { + string r; + + bool f (true); + for (qname::iterator i (id.begin ()); i < id.end (); ++i) + { + if (i->empty ()) + continue; + + // Warn if the name is greater than the 128 limit. + // + if (i->size () > 128) + { + cerr << "warning: SQL name '" << *i << "' is longer than the " + << "SQL Server name limit of 128 characters and will be " + << "truncated" << endl; + + cerr << "info: consider shortening it using #pragma db " + << "table/column/index or --*-regex options" << endl; + } + + if (f) + f = false; + else + r += '.'; + + r += '['; + r.append (*i, 0, 128); // Max identifier length is 128. + r += ']'; + } + + return r; + } + + string context:: + database_type_impl (semantics::type& t, + semantics::names* hint, + bool id, + bool* null) + { + string r (base_context::database_type_impl (t, hint, id, null)); + + if (!r.empty ()) + return r; + + using semantics::array; + + // char[N] mapping. + // + if (array* a = dynamic_cast<array*> (&t)) + { + semantics::type& bt (a->base_type ()); + bool c (bt.is_a<semantics::fund_char> ()); + + if (c || bt.is_a<semantics::fund_wchar> ()) + { + unsigned long long n (a->size ()); + + if (n == 0) + return r; + if (n == 1) + r = c ? "CHAR(" : "NCHAR("; + else + { + r = c ? "VARCHAR(" : "NVARCHAR("; + n--; + } + + if (n > (c ? 8000 : 4000)) + r += "max)"; + else + { + ostringstream ostr; + ostr << n; + r += ostr.str (); + r += ')'; + } + } + } + + return r; + } + + bool context:: + long_data (sql_type const& st) + { + bool r (false); + + // The same test as in common.cxx:traverse_simple(). + // + switch (st.type) + { + case sql_type::CHAR: + case sql_type::VARCHAR: + case sql_type::BINARY: + case sql_type::VARBINARY: + { + // Zero precision means max in VARCHAR(max). + // + if (st.prec == 0 || st.prec > options.mssql_short_limit ()) + r = true; + + break; + } + case sql_type::NCHAR: + case sql_type::NVARCHAR: + { + // Zero precision means max in NVARCHAR(max). Note that + // the precision is in 2-byte UCS-2 characters, not bytes. + // + if (st.prec == 0 || st.prec * 2 > options.mssql_short_limit ()) + r = true; + + break; + } + case sql_type::TEXT: + case sql_type::NTEXT: + case sql_type::IMAGE: + { + r = true; + break; + } + default: + break; + } + + return r; + } + + // + // SQL type parsing. + // + + namespace + { + struct sql_parser + { + typedef context::invalid_sql_type invalid_sql_type; + + sql_parser (custom_db_types const* ct): ct_ (ct) {} + + sql_type + parse (std::string sql) + { + r_ = sql_type (); + m_.clear (); + + // First run the type through the custom mapping, if requested. + // + if (ct_ != 0) + { + for (custom_db_types::const_iterator i (ct_->begin ()); + i != ct_->end (); ++i) + { + custom_db_type const& t (*i); + + if (t.type.match (sql)) + { + r_.to = t.type.replace (sql, t.to); + r_.from = t.type.replace (sql, t.from); + sql = t.type.replace (sql, t.as); + break; + } + } + } + + l_.lex (sql); + + bool ok (true); + + try + { + ok = parse_name (); + } + catch (sql_lexer::invalid_input const& e) + { + ok = false; + m_ = "invalid SQL Server type declaration: " + e.message; + } + + if (!ok) + { + if (ct_ == 0) + return sql_type (); + else + throw invalid_sql_type (m_); + } + + return r_; + } + + bool + parse_name () + { + sql_token t (l_.next ()); + + if (t.type () != sql_token::t_identifier) + { + m_ = "expected SQL Server type name instead of '" + + t.string () + "'"; + return false; + } + + string id (upcase (t.identifier ())); + + if (id == "BIT") + { + r_.type = sql_type::BIT; + } + else if (id == "TINYINT") + { + r_.type = sql_type::TINYINT; + } + else if (id == "SMALLINT") + { + r_.type = sql_type::SMALLINT; + } + else if (id == "INT" || + id == "INTEGER") + { + r_.type = sql_type::INT; + } + else if (id == "BIGINT") + { + r_.type = sql_type::BIGINT; + } + else if (id == "DECIMAL" || + id == "NUMERIC" || + id == "DEC") + { + r_.type = sql_type::DECIMAL; + + r_.has_prec = true; + r_.prec = 18; + + r_.has_scale = true; + r_.scale = 0; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "SMALLMONEY") + { + r_.type = sql_type::SMALLMONEY; + } + else if (id == "MONEY") + { + r_.type = sql_type::MONEY; + } + else if (id == "REAL") + { + r_.type = sql_type::FLOAT; + + r_.has_prec = true; + r_.prec = 24; + } + else if (id == "FLOAT") + { + r_.type = sql_type::FLOAT; + + r_.has_prec = true; + r_.prec = 53; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "DOUBLE") + { + t = l_.next (); + + if (t.type () != sql_token::t_identifier || + upcase (t.identifier ()) != "PRECISION") + { + m_ = "expected 'PRECISION' instead of '" + t.string () + "'"; + return false; + } + + r_.type = sql_type::FLOAT; + + r_.has_prec = true; + r_.prec = 53; + + // It appears that DOUBLE PRECISION can be followed by the + // precision specification. + // + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "CHAR" || + id == "CHARACTER") + { + if (!parse_char_trailer (false)) + return false; + } + else if (id == "VARCHAR") + { + r_.type = sql_type::VARCHAR; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "TEXT") + { + r_.type = sql_type::TEXT; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "NCHAR") + { + r_.type = sql_type::NCHAR; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "NVARCHAR") + { + r_.type = sql_type::NVARCHAR; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "NTEXT") + { + r_.type = sql_type::NTEXT; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "NATIONAL") + { + t = l_.next (); + + if (t.type () == sql_token::t_identifier) + id = upcase (t.identifier ()); + + if (id == "TEXT") + { + r_.type = sql_type::NTEXT; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "CHAR" || + id == "CHARACTER") + { + if (!parse_char_trailer (true)) + return false; + } + else + { + m_ = "expected 'CHAR', 'CHARACTER', or 'TEXT' instead of '" + + t.string () + "'"; + return false; + } + } + else if (id == "BINARY") + { + // Can be just BINARY or BINARY VARYING. + // + t = l_.next (); + + if (t.type () == sql_token::t_identifier) + id = upcase (t.identifier ()); + + if (id == "VARYING") + { + r_.type = sql_type::VARBINARY; + t = l_.next (); + } + else + r_.type = sql_type::BINARY; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (t)) + return false; + } + else if (id == "VARBINARY") + { + r_.type = sql_type::VARBINARY; + + r_.has_prec = true; + r_.prec = 1; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "IMAGE") + { + r_.type = sql_type::IMAGE; + r_.has_prec = true; + r_.prec = 0; + } + else if (id == "DATE") + { + r_.type = sql_type::DATE; + } + else if (id == "TIME") + { + r_.type = sql_type::TIME; + + r_.has_scale = true; + r_.scale = 7; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "DATETIME") + { + r_.type = sql_type::DATETIME; + } + else if (id == "DATETIME2") + { + r_.type = sql_type::DATETIME2; + + r_.has_scale = true; + r_.scale = 7; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "SMALLDATETIME") + { + r_.type = sql_type::SMALLDATETIME; + } + else if (id == "DATETIMEOFFSET") + { + r_.type = sql_type::DATETIMEOFFSET; + + r_.has_scale = true; + r_.scale = 7; + + if (!parse_precision (l_.next ())) + return false; + } + else if (id == "UNIQUEIDENTIFIER") + { + r_.type = sql_type::UNIQUEIDENTIFIER; + } + else if (id == "ROWVERSION" || + id == "TIMESTAMP") + { + r_.type = sql_type::ROWVERSION; + } + else + { + m_ = "unexpected SQL Server type name '" + t.identifier () + "'"; + return false; + } + + return true; + } + + bool + parse_precision (sql_token t) + { + if (t.punctuation () == sql_token::p_lparen) + { + // Parse the precision. + // + t = l_.next (); + + if (t.type () == sql_token::t_identifier && + upcase (t.identifier ()) == "MAX") + { + r_.prec = 0; + r_.has_prec = true; + } + else if (t.type () == sql_token::t_int_lit) + { + unsigned short v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + m_ = "invalid precision value '" + t.literal () + "' in SQL " + "Server type declaration"; + return false; + } + + switch (r_.type) + { + case sql_type::TIME: + case sql_type::DATETIME2: + case sql_type::DATETIMEOFFSET: + { + r_.scale = v; + r_.has_scale = true; + break; + } + default: + { + r_.prec = v; + r_.has_prec = true; + break; + } + } + } + else + { + m_ = "integer precision expected in SQL Server type declaration"; + return false; + } + + // Parse the scale if present. + // + t = l_.next (); + + if (t.punctuation () == sql_token::p_comma) + { + // Scale can only be specified for the DECIMAL type. + // + if (r_.type != sql_type::DECIMAL) + { + m_ = "unexpected scale in SQL Server type declaration"; + return false; + } + + t = l_.next (); + + if (t.type () != sql_token::t_int_lit) + { + m_ = "integer scale expected in SQL Server type declaration"; + return false; + } + + istringstream is (t.literal ()); + + if (!(is >> r_.scale && is.eof ())) + { + m_ = "invalid scale value '" + t.literal () + "' in SQL " + "Server type declaration"; + return false; + } + + r_.has_scale = true; + t = l_.next (); + } + + if (t.punctuation () != sql_token::p_rparen) + { + m_ = "expected ')' in SQL Server type declaration"; + return false; + } + } + + return true; + } + + bool + parse_char_trailer (bool nat) + { + sql_token t (l_.next ()); + + string id; + + if (t.type () == sql_token::t_identifier) + id = upcase (t.identifier ()); + + if (id == "VARYING") + { + r_.type = nat ? sql_type::NVARCHAR : sql_type::VARCHAR; + t = l_.next (); + } + else + r_.type = nat ? sql_type::NCHAR : sql_type::CHAR; + + r_.has_prec = true; + r_.prec = 1; + + return parse_precision (t); + } + + private: + string + upcase (string const& s) + { + return context::upcase (s); + } + + private: + custom_db_types const* ct_; + sql_lexer l_; + sql_type r_; + string m_; // Error message. + }; + } + + sql_type const& context:: + parse_sql_type (string const& t, semantics::data_member& m, bool custom) + { + // If this proves to be too expensive, we can maintain a cache of + // parsed types across contexts. + // + data::sql_type_cache::iterator i (data_->sql_type_cache_.find (t)); + + if (i != data_->sql_type_cache_.end () + && (custom ? i->second.custom_cached : i->second.straight_cached)) + { + return (custom ? i->second.custom : i->second.straight); + } + else + { + try + { + sql_type st ( + parse_sql_type ( + t, + custom ? &unit.get<custom_db_types> ("custom-db-types") : 0)); + + if (custom) + return data_->sql_type_cache_[t].cache_custom (st); + else + return data_->sql_type_cache_[t].cache_straight (st); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } + } + } + + sql_type context:: + parse_sql_type (string const& sqlt, custom_db_types const* ct) + { + sql_parser p (ct); + return p.parse (sqlt); + } + } +} diff --git a/odb/odb/relational/mssql/context.hxx b/odb/odb/relational/mssql/context.hxx new file mode 100644 index 0000000..7701aaa --- /dev/null +++ b/odb/odb/relational/mssql/context.hxx @@ -0,0 +1,194 @@ +// file : odb/relational/mssql/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MSSQL_CONTEXT_HXX +#define ODB_RELATIONAL_MSSQL_CONTEXT_HXX + +#include <map> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace mssql + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Integral types. + // + BIT, + TINYINT, + SMALLINT, + INT, + BIGINT, + + // Fixed and floating point types. + // + DECIMAL, + SMALLMONEY, + MONEY, + FLOAT, + + // String and binary types. + // + CHAR, + VARCHAR, + TEXT, + + NCHAR, + NVARCHAR, + NTEXT, + + BINARY, + VARBINARY, + IMAGE, + + // Date-time types. + // + DATE, + TIME, + DATETIME, + DATETIME2, + SMALLDATETIME, + DATETIMEOFFSET, + + // Other types. + // + UNIQUEIDENTIFIER, + ROWVERSION, + + // Invalid type. + // + invalid + }; + + sql_type () : + type (invalid), + has_prec (false), prec (0), + has_scale (false), scale (0) + { + } + + core_type type; + + bool has_prec; + unsigned short prec; // Max numeric value is 8000. 0 indicates + // 'max' as in VARCHAR(max). + bool has_scale; + unsigned short scale; // Max value is 38. + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + + // Return true if this type is long data. + // + bool + long_data (sql_type const&); + + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string const&, custom_db_types const* = 0); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual string + quote_id_impl (qname const&) const; + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_MSSQL_CONTEXT_HXX diff --git a/odb/odb/relational/mssql/header.cxx b/odb/odb/relational/mssql/header.cxx new file mode 100644 index 0000000..ebdc734 --- /dev/null +++ b/odb/odb/relational/mssql/header.cxx @@ -0,0 +1,312 @@ +// file : odb/relational/mssql/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +namespace relational +{ + namespace mssql + { + namespace header + { + namespace relational = relational::header; + + struct class1: relational::class1, context + { + class1 (base const& x): base (x) {} + + virtual void + object_public_extra_pre (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + // Bulk operations batch size. + // + { + unsigned long long b (c.count ("bulk") + ? c.get<unsigned long long> ("bulk") + : 1); + + os << "static const std::size_t batch = " << b << "UL;" + << endl; + } + + // rowvesion + // + bool rv (false); + if (semantics::data_member* m = optimistic (c)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + rv = (t.type == sql_type::ROWVERSION); + } + + os << "static const bool rowversion = " << rv << ";" + << endl; + + // Disable bulk update if we have ROWVERSION since we don't + // yet support batch extraction of the version. + // + if (rv && c.count ("bulk-update")) + c.remove ("bulk-update"); + } + + virtual void + object_public_extra_post (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + if (semantics::data_member* m = optimistic (c)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + if (t.type == sql_type::ROWVERSION) + { + os << "static version_type" << endl + << "version (const id_image_type&);" + << endl; + } + } + } + }; + entry<class1> class1_entry_; + + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_public_extra_pre (user_section&) + { + if (abstract (c_) && !polymorphic (c_)) + return; + + // rowvesion + // + bool rv (false); + if (semantics::data_member* m = optimistic (c_)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + rv = (t.type == sql_type::ROWVERSION); + } + + os << "static const bool rowversion = " << rv << ";" + << endl; + } + }; + entry<section_traits> section_traits_; + + struct image_type: relational::image_type, context + { + image_type (base const& x): base (x) {}; + + virtual void + image_extra (type& c) + { + if (!(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + + // If this is a polymorphic type, only add callback to the root. + // + if (poly_root == 0 || poly_root == &c) + { + bool gc (options.generate_query ()); + + if (gc) + os << "mssql::change_callback change_callback_;" + << endl; + + os << "mssql::change_callback*" << endl + << "change_callback ()" + << "{"; + + if (gc) + os << "return &change_callback_;"; + else + os << "return 0;"; + + os << "}"; + } + } + } + }; + entry<image_type> image_type_; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_money (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + // Extra character for the null-terminator that ODBC always adds. + // + os << "char " << mi.var << "value[" << mi.st->prec + 1 << "];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_nstring (member_info& mi) + { + // Extra character for the null-terminator that ODBC always adds. + // + os << "mssql::ucs2_char " << mi.var << "value[" << + mi.st->prec + 1 << "];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_binary (member_info& mi) + { + os << "char " << mi.var << "value[" << mi.st->prec << "];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_time (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_datetime (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << "unsigned char " << mi.var << "value[8];" + << "SQLLEN " << mi.var << "size_ind;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/mssql/inline.cxx b/odb/odb/relational/mssql/inline.cxx new file mode 100644 index 0000000..eb581d6 --- /dev/null +++ b/odb/odb/relational/mssql/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/mssql/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "size_ind == SQL_NULL_DATA;"; + else + os << "i." << mi.var << "size_ind = SQL_NULL_DATA;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/mssql/model.cxx b/odb/odb/relational/mssql/model.cxx new file mode 100644 index 0000000..0f5a85c --- /dev/null +++ b/odb/odb/relational/mssql/model.cxx @@ -0,0 +1,66 @@ +// file : odb/relational/mssql/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to an integer or DECIMAL type. + // + switch (parse_sql_type (column_type (), m, false).type) + { + case sql_type::BIT: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::INT: + case sql_type::BIGINT: + case sql_type::DECIMAL: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to SQL Server integer type" << endl; + + throw operation_failed (); + } + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); + + ostringstream ostr; + + if (e.enum_ ().unsigned_ ()) + ostr << e.value (); + else + ostr << static_cast<long long> (e.value ()); + + return ostr.str (); + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/odb/relational/mssql/schema.cxx b/odb/odb/relational/mssql/schema.cxx new file mode 100644 index 0000000..c5f6bc1 --- /dev/null +++ b/odb/odb/relational/mssql/schema.cxx @@ -0,0 +1,651 @@ +// file : odb/relational/mssql/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + 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 + post () + { + if (!first_) // Ignore empty statements. + { + os << ';' << endl + << "GO" << endl + << endl; + } + } + }; + entry<sql_emitter> sql_emitter_; + + // + // File. + // + + struct sql_file: relational::sql_file, context + { + sql_file (const base& x): base (x) {} + + virtual void + prologue () + { + // Suppress the (x rows affected) messages from sqlcmd for DML + // statements. We only use DML for schema version management. + // + if ((model == 0 || model->version () != 0) && + !options.suppress_schema_version ()) + os << "SET NOCOUNT ON;" << endl + << endl; + } + }; + entry<sql_file> 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> drop_column_; + + struct drop_foreign_key: relational::drop_foreign_key, context + { + drop_foreign_key (base const& x): base (x) {} + + virtual void + drop (sema_rel::table& t, sema_rel::foreign_key& fk) + { + bool migration (dropped_ == 0); + + if (migration) + { + if (fk.not_deferrable ()) + pre_statement (); + else + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + } + } + else + { + // Here we drop potentially deferrable keys and also need to + // test if the key exists. + // + pre_statement (); + + os << "IF OBJECT_ID(" << quote_string (fk.name ()) << ", " << + quote_string ("F") << ") IS NOT NULL" << endl + << " "; + } + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << (migration ? " " : " ") << "DROP CONSTRAINT " << + quote_id (fk.name ()) << endl; + + + if (!migration || fk.not_deferrable ()) + post_statement (); + else + os << "*/" << endl + << endl; + } + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + // Find the foreign key we are dropping in the base model. + // + sema_rel::foreign_key& fk (find<sema_rel::foreign_key> (dfk)); + + bool c (!fk.not_deferrable () && !in_comment); + + if (c && format_ != schema_format::sql) + return; + + if (!first_) + os << (c ? "" : ",") << endl + << " "; + + if (c) + os << "/* "; + + os << quote_id (fk.name ()); + + if (c) + os << " */"; + + if (first_) + { + if (c) + // There has to be a real name otherwise the whole statement + // would have been commented out. + // + os << endl + << " "; + else + first_ = false; + } + } + }; + entry<drop_foreign_key> drop_foreign_key_; + + struct drop_index: relational::drop_index, context + { + drop_index (base const& x): base (x) {} + + virtual void + drop (sema_rel::index& in) + { + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + + os << "DROP INDEX " << name (in) << " ON " << + quote_id (t.name ()) << endl; + } + }; + entry<drop_index> 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) + { + // SQL Server has no IF EXISTS conditional for dropping tables. + // The following approach appears to be the recommended way to + // drop a table if it exists. + // + sema_rel::qname const& name (t.name ()); + + pre_statement (); + + if (!migration) + os << "IF OBJECT_ID(" << quote_string (name.string ()) << + ", " << quote_string ("U") << ") IS NOT NULL" << endl + << " "; + + os << "DROP TABLE " << quote_id (name) << endl; + + post_statement (); + } + }; + entry<drop_table> 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); + } + + virtual void + auto_ (sema_rel::primary_key&) + { + os << " IDENTITY"; + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (base const& x): base (x) {} + + void + diagnose (sema_rel::foreign_key& fk) + { + if (fk.on_delete () != sema_rel::foreign_key::no_action) + { + cerr << "warning: foreign key '" << fk.name () << "' has " << + "ON DELETE clause but is disabled in SQL Server due to lack " + "of deferrable constraint support" << endl; + + cerr << "info: consider using non-deferrable foreign keys (" << + "--fkeys-deferrable-mode)" << endl; + } + } + + virtual void + traverse_create (sema_rel::foreign_key& fk) + { + // SQL Server does not support deferrable constraint checking. + // Output such foreign keys as comments, for documentation, + // unless we are generating embedded schema. + // + if (fk.not_deferrable ()) + base::traverse_create (fk); + else + { + diagnose (fk); + + // Don't bloat C++ code with comment strings if we are + // generating embedded schema. + // + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" << endl + << " CONSTRAINT "; + create (fk); + os << endl + << " */"; + } + } + + virtual void + traverse_add (sema_rel::foreign_key& fk) + { + bool c (!fk.not_deferrable () && !in_comment); + + if (c) + diagnose (fk); + + if (c && format_ != schema_format::sql) + return; + + if (!first_) + os << (c ? "" : ",") << endl + << " "; + + if (c) + os << "/*" << endl + << " "; + + os << "CONSTRAINT "; + create (fk); + + if (c) + os << endl + << " */"; + + if (first_) + { + if (c) + // There has to be a real key otherwise the whole statement + // would have been commented out. + // + os << endl + << " "; + else + first_ = false; + } + } + + virtual void + deferrable (sema_rel::deferrable) + { + // This will still be called to output the comment. + } + }; + entry<create_foreign_key> create_foreign_key_; + + struct create_table: relational::create_table, context + { + create_table (base const& x): base (x) {} + + // See if there are any undefined foreign keys that are not + // deferrable. + // + bool + check_undefined_fk_deferrable_only (sema_rel::table& t) + { + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); ++i) + { + using sema_rel::foreign_key; + + if (foreign_key* fk = dynamic_cast<foreign_key*> (&i->nameable ())) + { + if (!fk->count ("mssql-fk-defined") && + fk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + traverse (sema_rel::table& t) + { + if (pass_ == 1) + base::traverse (t); + else + { + // Add undefined foreign keys. + // + if (check_undefined_fk (t)) + { + bool deferrable (check_undefined_fk_deferrable_only (t)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " ADD "; + + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (t, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + } + } + }; + entry<create_table> create_table_; + + // + // 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; + + using sema_rel::table; + table& at (static_cast<table&> (c.scope ())); + + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ALTER COLUMN "; + alter (c); + os << endl; + + post_statement (); + } + }; + entry<alter_column> alter_column_; + + struct alter_table_pre: relational::alter_table_pre, context + { + alter_table_pre (base const& x): base (x) {} + + // Check if we are only dropping deferrable foreign keys. + // + bool + check_drop_deferrable_only (sema_rel::alter_table& at) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::foreign_key; + using sema_rel::drop_foreign_key; + + if (drop_foreign_key* dfk = + dynamic_cast<drop_foreign_key*> (&i->nameable ())) + { + foreign_key& fk (find<foreign_key> (*dfk)); + + if (fk.not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + // SQL Server can only alter one kind of thing at a time. + // + if (check<sema_rel::drop_foreign_key> (at)) + { + bool deferrable (check_drop_deferrable_only (at)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " DROP CONSTRAINT "; + + instance<drop_foreign_key> dfc (*this); + trav_rel::unames n (*dfc); + names (at, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + + if (check<sema_rel::add_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD "; + + instance<create_column> cc (*this); + trav_rel::unames n (*cc); + names (at, n); + os << endl; + + post_statement (); + } + + // For ALTER COLUMN, SQL Server can only have one per ALTER TABLE. + // + { + bool tl (true); // (Im)perfect forwarding. + instance<alter_column> ac (*this, tl); + trav_rel::unames n (*ac); + names (at, n); + } + } + }; + entry<alter_table_pre> alter_table_pre_; + + struct alter_table_post: relational::alter_table_post, context + { + alter_table_post (base const& x): base (x) {} + + // Check if we are only adding deferrable foreign keys. + // + bool + check_add_deferrable_only (sema_rel::alter_table& at) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::add_foreign_key; + + if (add_foreign_key* afk = + dynamic_cast<add_foreign_key*> (&i->nameable ())) + { + if (afk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + // SQL Server can only alter one kind of thing at a time. + // + if (check<sema_rel::drop_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " DROP COLUMN "; + + instance<drop_column> dc (*this); + trav_rel::unames n (*dc); + names (at, n); + os << endl; + + post_statement (); + } + + // For ALTER COLUMN, SQL Server can only have one per ALTER TABLE. + // + { + bool fl (false); // (Im)perfect forwarding. + instance<alter_column> ac (*this, fl); + trav_rel::unames n (*ac); + names (at, n); + } + + if (check<sema_rel::add_foreign_key> (at)) + { + bool deferrable (check_add_deferrable_only (at)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD "; + + instance<create_foreign_key> cfc (*this); + trav_rel::unames n (*cfc); + names (at, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + } + }; + entry<alter_table_post> alter_table_post_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x): base (x) {} + + virtual void + create_table () + { + pre_statement (); + + os << "IF OBJECT_ID(" << quote_string (table_.string ()) << + ", " << quote_string ("U") << ") IS NULL" << endl + << " CREATE TABLE " << qt_ << " (" << endl + << " " << qn_ << " VARCHAR(256) NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT NOT NULL," << endl + << " " << qm_ << " BIT NOT NULL)" << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + os << "IF NOT EXISTS (SELECT 1 FROM " << qt_ << " WHERE " << qn_ << + " = " << qs_ << ")" << endl + << " INSERT INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; + + post_statement (); + } + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/mssql/source.cxx b/odb/odb/relational/mssql/source.cxx new file mode 100644 index 0000000..573104d --- /dev/null +++ b/odb/odb/relational/mssql/source.cxx @@ -0,0 +1,1201 @@ +// file : odb/relational/mssql/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/mssql/common.hxx> +#include <odb/relational/mssql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mssql + { + namespace source + { + namespace relational = relational::source; + + // + // + struct query_parameters: relational::query_parameters + { + query_parameters (base const& x): base (x) {} + + virtual string + auto_id (semantics::data_member&, const string&, const string&) + { + return ""; + } + }; + entry<query_parameters> query_parameters_; + + // + // + struct object_columns: relational::object_columns, context + { + object_columns (base const& x) + : base (x), rowversion_ (false), column_count_ (0) {} + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + // Don't add a column for auto id in the INSERT statement. + // Only simple, direct id can be auto. + // + if (sk_ == statement_insert && key_prefix_.empty () && auto_ (m)) + return false; + + // Don't update the ROWVERSION column explicitly. + // + if (sk_ == statement_update) + { + sql_type t (parse_sql_type (column_type (), m)); + if (t.type == sql_type::ROWVERSION) + { + rowversion_ = true; + return false; + } + } + + bool r (base::column (m, table, column)); + + // Count the number of columns in the UPDATE statement, but + // excluding soft-deleted. + // + if (sk_ == statement_update && r && !deleted (member_path_)) + column_count_++; + + return r; + } + + virtual void + traverse_post (semantics::nameable& n) + { + if (rowversion_ && column_count_ == 0) + { + location l (n.location ()); + error (l) << "ROWVERSION in an object without any readwrite " + "data members" << endl; + error (l) << "UPDATE statement will be empty" << endl; + throw operation_failed (); + } + } + + private: + bool rowversion_; + size_t column_count_; + }; + entry<object_columns> object_columns_; + + // + // + struct persist_statement_params: relational::persist_statement_params, + context + { + persist_statement_params (base const& x): base (x) {} + + virtual string + version_value (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (), m)); + return t.type == sql_type::ROWVERSION ? "DEFAULT" : "1"; + } + }; + entry<persist_statement_params> persist_statement_params_; + + // + // bind + // + + static const char* integer_buffer_types[] = + { + "mssql::bind::bit", + "mssql::bind::tinyint", + "mssql::bind::smallint", + "mssql::bind::int_", + "mssql::bind::bigint" + }; + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_integer (member_info& mi) + { + os << b << ".type = " << + integer_buffer_types[mi.st->type - sql_type::BIT] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << b << ".type = mssql::bind::decimal;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode precision (p) and scale (s) as (p * 100 + s). + // + << b << ".capacity = " << mi.st->prec * 100 + mi.st->scale << ";"; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << b << ".type = mssql::bind::smallmoney;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_money (member_info& mi) + { + os << b << ".type = mssql::bind::money;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << b << ".type = mssql::bind::float4;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << b << ".type = mssql::bind::float8;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_string (member_info& mi) + { + os << b << ".type = mssql::bind::string;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = static_cast<SQLLEN> (sizeof (" << + arg << "." << mi.var << "value));"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << b << ".type = mssql::bind::long_string;" + << b << ".buffer = &" << arg << "." << mi.var << "callback;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode the column size with 0 indicating unlimited. + // + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_nstring (member_info& mi) + { + os << b << ".type = mssql::bind::nstring;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = static_cast<SQLLEN> (sizeof (" << + arg << "." << mi.var << "value));"; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << b << ".type = mssql::bind::long_nstring;" + << b << ".buffer = &" << arg << "." << mi.var << "callback;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode the column size (in bytes) with 0 indicating unlimited. + // + << b << ".capacity = " << mi.st->prec * 2 << ";"; + } + + virtual void + traverse_binary (member_info& mi) + { + os << b << ".type = mssql::bind::binary;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + << b << ".capacity = static_cast<SQLLEN> (sizeof (" << + arg << "." << mi.var << "value));"; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << b << ".type = mssql::bind::long_binary;" + << b << ".buffer = &" << arg << "." << mi.var << "callback;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode the column size with 0 indicating unlimited. + // + << b << ".capacity = " << mi.st->prec << ";"; + } + + virtual void + traverse_date (member_info& mi) + { + os << b << ".type = mssql::bind::date;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_time (member_info& mi) + { + os << b << ".type = mssql::bind::time;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode fractional seconds (scale). + // + << b << ".capacity = " << mi.st->scale << ";"; + } + + virtual void + traverse_datetime (member_info& mi) + { + unsigned short scale (0); + + switch (mi.st->type) + { + case sql_type::DATETIME: + { + // Looks like it is 3 (rounded to 0.000, 0.003, or 0.007). + // + scale = 3; + break; + } + case sql_type::DATETIME2: + { + scale = mi.st->scale; + break; + } + case sql_type::SMALLDATETIME: + { + // No seconds in SMALLDATATIME. Encode it a special precision + // value (8). + // + scale = 8; + break; + } + default: + { + assert (false); + break; + } + } + + os << b << ".type = mssql::bind::datetime;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode fractional seconds (scale). + // + << b << ".capacity = " << scale << ";"; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << b << ".type = mssql::bind::datetimeoffset;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;" + // Encode fractional seconds (scale). + // + << b << ".capacity = " << mi.st->scale << ";"; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << b << ".type = mssql::bind::uniqueidentifier;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << b << ".type = mssql::bind::rowversion;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size_ind = &" << arg << "." << mi.var << "size_ind;"; + } + }; + entry<bind_member> bind_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "size_ind = SQL_NULL_DATA;"; + } + + virtual void + check_accessor (member_info& mi, member_access& ma) + { + // We cannot use accessors that return by-value for long data + // members. + // + if (long_data (*mi.st) && ma.by_value) + { + error (ma.loc) << "accessor returning a value cannot be used " + << "for a data member of SQL Server long data " + << "type" << endl; + info (ma.loc) << "accessor returning a const reference is required" + << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 4;"; + } + + virtual void + traverse_money (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 8;"; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + // Don't mention the extra character for the null-terminator. + << "sizeof (i." << mi.var << "value) - 1," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind =" << endl + << " is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size);"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind = is_null ? " << + "SQL_NULL_DATA : SQL_DATA_AT_EXEC;"; + } + + virtual void + traverse_nstring (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + // Don't mention the extra character for the null-terminator. + << "sizeof (i." << mi.var << "value) / 2 - 1," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind =" << endl + << " is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size * 2);"; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind = is_null ? " << + "SQL_NULL_DATA : SQL_DATA_AT_EXEC;"; + } + + virtual void + traverse_binary (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind =" << endl + << " is_null ? SQL_NULL_DATA : static_cast<SQLLEN> (size);"; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "size_ind = is_null ? " << + "SQL_NULL_DATA : SQL_DATA_AT_EXEC;"; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_time (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, " << mi.st->scale << ", " << + "is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null" << endl + << " ? SQL_NULL_DATA" << endl + << " : static_cast<SQLLEN> (sizeof (i." << mi.var << "value));"; + } + + virtual void + traverse_datetime (member_info& mi) + { + // The same code as in bind. + // + unsigned short scale (0); + + switch (mi.st->type) + { + case sql_type::DATETIME: + { + scale = 3; + break; + } + case sql_type::DATETIME2: + { + scale = mi.st->scale; + break; + } + case sql_type::SMALLDATETIME: + { + scale = 8; + break; + } + default: + { + assert (false); + break; + } + } + + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, " << scale << ", " << + "is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, " << mi.st->scale << ", " << + "is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null" << endl + << " ? SQL_NULL_DATA" << endl + << " : static_cast<SQLLEN> (sizeof (i." << mi.var << "value));"; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 0;"; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "size_ind = is_null ? SQL_NULL_DATA : 8;"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "size_ind == SQL_NULL_DATA"; + } + + virtual void + check_modifier (member_info& mi, member_access& ma) + { + // We cannot use by-value modifier for long data members. + // + if (long_data (*mi.st) && ma.placeholder ()) + { + error (ma.loc) << "modifier accepting a value cannot be used " + << "for a data member of SQL Server long data " + << "type" << endl; + info (ma.loc) << "modifier returning a non-const reference is " + << "required" << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_smallmoney (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_money (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_float4 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_float8 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "static_cast<std::size_t> (i." << mi.var << "size_ind)," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result);" + << endl; + } + + virtual void + traverse_nstring (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "static_cast<std::size_t> (" << + "i." << mi.var << "size_ind / 2)," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_long_nstring (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result);" + << endl; + } + + virtual void + traverse_binary (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "static_cast<std::size_t> (i." << mi.var << "size_ind)," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_long_binary (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result);" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_time (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_datetime (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_datetimeoffset (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_uniqueidentifier (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + + virtual void + traverse_rowversion (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size_ind == SQL_NULL_DATA);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct statement_columns_common: context + { + void + process (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + using relational::statement_columns; + + // Long data columns must come last in the SELECT statement. If + // this statement is going to be processed at runtime, then this + // will be taken care of then. + // + if (sk != statement_select || dynamic) + return; + + // Go over the columns list while keeping track of how many + // columns we have examined. If the current column is long data, + // then move it to the back. Stop once we have examined all the + // columns. + // + size_t n (cols.size ()); + for (statement_columns::iterator i (cols.begin ()); n != 0; --n) + { + if (long_data (parse_sql_type (i->type, *i->member))) + { + cols.push_back (*i); + i = cols.erase (i); + } + else + ++i; + } + } + }; + + struct container_traits: relational::container_traits, + statement_columns_common + { + container_traits (base const& x): base (x) {} + + virtual void + cache_result (string const&) + { + // Caching is not necessary since with MARS enabled SQL Server + // can execute several interleaving statements. + // + } + + virtual void + init_value_extra () + { + os << "sts.select_statement ().stream_result ();" + << endl; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + statement_columns_common::process (cols, sk, dynamic); + } + }; + entry<container_traits> container_traits_; + + struct section_traits: relational::section_traits, + statement_columns_common + { + section_traits (base const& x): base (x) {} + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + statement_columns_common::process (cols, sk, dynamic); + } + + virtual string + optimistic_version_increment (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : "version (sts.id_image ())"; + } + + virtual string + update_statement_extra (user_section&) + { + string r; + + semantics::data_member* ver (optimistic (c_)); + + if (ver == 0 || + parse_sql_type (column_type (*ver), *ver).type != + sql_type::ROWVERSION) + return r; + + // ROWVERSION & SQL Server 2005 incompatibility is detected + // in persist_statement_extra. + // + r = "OUTPUT INSERTED." + + convert_from (column_qname (*ver, column_prefix ()), *ver); + + return r; + } + }; + entry<section_traits> section_traits_; + + struct class_: relational::class_, statement_columns_common + { + class_ (base const& x): + base (x), init_version_value_member_id_image_ ("v", "version_") {} + + virtual void + init_image_pre (type& c) + { + if (options.generate_query () && + !(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + if (poly_derived) + os << "{" + << "root_traits::image_type& ri (root_image (i));" + << endl; + + string i (poly_derived ? "ri" : "i"); + + os << "if (" << i << ".change_callback_.callback != 0)" << endl + << "(" << i << ".change_callback_.callback) (" << + i << ".change_callback_.context);"; + + if (poly_derived) + os << "}"; + else + os << endl; + } + } + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + + virtual string + persist_statement_extra (type& c, + relational::query_parameters&, + persist_position p) + { + string r; + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // If we are a derived type in a polymorphic hierarchy, then + // auto id/version are handled by the root. + // + if (poly_derived) + return r; + + // See if we have auto id or ROWVERSION version. + // + data_member_path* id (id_member (c)); + semantics::data_member* ver (optimistic (c)); + + if (id != 0 && !auto_ (*id)) + id = 0; + + if (ver != 0) + { + sql_type t (parse_sql_type (column_type (*ver), *ver)); + if (t.type != sql_type::ROWVERSION) + ver = 0; + } + + if (id == 0 && ver == 0) + return r; + + // SQL Server 2005 has a bug that causes it to fail on an + // INSERT statement with the OUTPUT clause if data for one + // of the inserted columns is supplied at execution (long + // data). To work around this problem we use the less + // efficient batch of INSERT and SELECT statements. + // + if (options.mssql_server_version () <= mssql_version (9, 0)) + { + bool ld (false); + + if (c.count ("mssql-has-long-data")) + ld = c.get<bool> ("mssql-has-long-data"); + else + { + has_long_data t (ld); + t.traverse (c); + c.set ("mssql-has-long-data", ld); + } + + if (ld) + { + if (p == persist_after_values) + { + // SQL Server 2005 has no eqivalent of SCOPE_IDENTITY for + // ROWVERSION. + // + if (ver != 0) + { + error (c.location ()) << "in SQL Server 2005 ROWVERSION " << + "value cannot be retrieved for a persistent class " << + "containing long data" << endl; + throw operation_failed (); + } + + // We also cannot support bulk INSERT. + // + if (c.count ("bulk-persist")) + { + error (c.location ()) << "in SQL Server 2005 bulk " << + "persist operation cannot be implemented for a " << + "persistent class containing long data" << endl; + throw operation_failed (); + } + + r = "; SELECT " + + convert_from ("SCOPE_IDENTITY()", *id->back ()); + } + + return r; + } + } + + if (p == persist_after_columns) + { + r = "OUTPUT "; + + // Top-level auto id column. + // + if (id != 0) + r += "INSERTED." + + convert_from (column_qname (*id), *id->back ()); + + // Top-level version column. + // + if (ver != 0) + { + if (id != 0) + r += ','; + + r += "INSERTED." + convert_from ( + column_qname (*ver, column_prefix ()), *ver); + } + } + + return r; + } + + virtual string + update_statement_extra (type& c) + { + string r; + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // If we are a derived type in a polymorphic hierarchy, then + // version is handled by the root. + // + if (poly_derived) + return r; + + semantics::data_member* ver (optimistic (c)); + + if (ver == 0 || + parse_sql_type (column_type (*ver), *ver).type != + sql_type::ROWVERSION) + return r; + + // Long data & SQL Server 2005 incompatibility is detected + // in persist_statement_extra. + // + r = "OUTPUT INSERTED." + + convert_from (column_qname (*ver, column_prefix ()), *ver); + + return r; + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool dynamic) + { + statement_columns_common::process (cols, sk, dynamic); + } + + virtual string + optimistic_version_init (semantics::data_member& m, bool index) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : (index + ? "version (sts.id_image (i))" + : "version (sts.id_image ())"); + } + + virtual string + optimistic_version_increment (semantics::data_member& m, bool index) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type != sql_type::ROWVERSION + ? "1" + : (index + ? "version (sts.id_image (i))" + : "version (sts.id_image ())"); + } + + virtual bool + optimistic_insert_bind_version (semantics::data_member& m) + { + sql_type t (parse_sql_type (column_type (m), m)); + return t.type == sql_type::ROWVERSION; + } + + virtual void + object_extra (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + if (semantics::data_member* m = optimistic (c)) + { + sql_type t (parse_sql_type (column_type (*m), *m)); + if (t.type == sql_type::ROWVERSION) + { + string const& type (class_fq_name (c)); + string traits ("access::object_traits_impl< " + type + ", id_" + + db.string () + " >"); + + os << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const id_image_type& i)" + << "{" + << "version_type v;"; + init_version_value_member_id_image_->traverse (*m); + os << "return v;" + << "}"; + } + } + } + + virtual string + from_trailer (type& c) + { + return c.get<view_query> ("query").for_update + ? " WITH (UPDLOCK)" + : ""; + } + + virtual string + select_trailer (type&) {return "";} + + private: + // Go via the dynamic creation to get access to the constructor. + // + instance<relational::init_value_member> + init_version_value_member_id_image_; + }; + entry<class_> class_entry_; + } + } +} diff --git a/odb/odb/relational/mysql/common.cxx b/odb/odb/relational/mysql/common.cxx new file mode 100644 index 0000000..d049443 --- /dev/null +++ b/odb/odb/relational/mysql/common.cxx @@ -0,0 +1,400 @@ +// file : odb/relational/mysql/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/mysql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + switch (mi.st->type) + { + // Integral types. + // + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::MEDIUMINT: + case sql_type::INT: + case sql_type::BIGINT: + { + traverse_integer (mi); + break; + } + + // Float types. + // + case sql_type::FLOAT: + case sql_type::DOUBLE: + { + traverse_float (mi); + break; + } + case sql_type::DECIMAL: + { + traverse_decimal (mi); + break; + } + + // Data-time types. + // + case sql_type::DATE: + case sql_type::TIME: + case sql_type::DATETIME: + case sql_type::TIMESTAMP: + case sql_type::YEAR: + { + traverse_date_time (mi); + break; + } + + // String and binary types. + // + case sql_type::CHAR: + case sql_type::VARCHAR: + case sql_type::TINYTEXT: + case sql_type::TEXT: + case sql_type::MEDIUMTEXT: + case sql_type::LONGTEXT: + { + // For string types the limit is in characters rather + // than in bytes. The fixed-length pre-allocated buffer + // optimization can only be used for 1-byte encodings. + // To support this we will need the character encoding + // in sql_type. + // + traverse_long_string (mi); + break; + } + case sql_type::BINARY: + case sql_type::TINYBLOB: + { + // BINARY's range is always 255 or less from MySQL 5.0.3. + // TINYBLOB can only store up to 255 bytes. + // + traverse_short_string (mi); + break; + } + case sql_type::VARBINARY: + case sql_type::BLOB: + case sql_type::MEDIUMBLOB: + case sql_type::LONGBLOB: + { + if (mi.st->range && mi.st->range_value <= 255) + traverse_short_string (mi); + else + traverse_long_string (mi); + + break; + } + + // Other types. + // + case sql_type::BIT: + { + traverse_bit (mi); + break; + } + case sql_type::ENUM: + { + traverse_enum (mi); + break; + } + case sql_type::SET: + { + traverse_set (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + static const char* integer_types[] = + { + "char", + "short", + "int", + "int", + "long long" + }; + + static const char* float_types[] = + { + "float", + "double" + }; + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_mysql >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info& mi) + { + if (mi.st->unsign) + type_ = "unsigned "; + else if (mi.st->type == sql_type::TINYINT) + type_ = "signed "; + + type_ += integer_types[mi.st->type - sql_type::TINYINT]; + } + + void member_image_type:: + traverse_float (member_info& mi) + { + type_ = float_types[mi.st->type - sql_type::FLOAT]; + } + + void member_image_type:: + traverse_decimal (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_date_time (member_info& mi) + { + if (mi.st->type == sql_type::YEAR) + type_ = "short"; + else + type_ = "MYSQL_TIME"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_bit (member_info&) + { + type_ = "unsigned char*"; + } + + void member_image_type:: + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + type_ = "mysql::value_traits< " + mi.fq_type () + + ", mysql::id_enum >::image_type"; + } + + void member_image_type:: + traverse_set (member_info&) + { + // Represented as string. + // + type_ = "details::buffer"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + static const char* integer_database_id[] = + { + "id_tiny", + "id_utiny", + "id_short", + "id_ushort", + "id_long", // INT24 + "id_ulong", // INT24 UNSIGNED + "id_long", + "id_ulong", + "id_longlong", + "id_ulonglong" + }; + + static const char* float_database_id[] = + { + "id_float", + "id_double" + }; + + static const char* date_time_database_id[] = + { + "id_date", + "id_time", + "id_datetime", + "id_timestamp", + "id_year" + }; + + static const char* char_bin_database_id[] = + { + "id_string", // CHAR + "id_blob", // BINARY, + "id_string", // VARCHAR + "id_blob", // VARBINARY + "id_string", // TINYTEXT + "id_blob", // TINYBLOB + "id_string", // TEXT + "id_blob", // BLOB + "id_string", // MEDIUMTEXT + "id_blob", // MEDIUMBLOB + "id_string", // LONGTEXT + "id_blob" // LONGBLOB + }; + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) {} + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) {} + + string member_database_type_id:: + database_type_id (type& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_integer (member_info& mi) + { + size_t i ( + (mi.st->type - sql_type::TINYINT) * 2 + (mi.st->unsign ? 1 : 0)); + type_id_ = string ("mysql::") + integer_database_id[i]; + } + + void member_database_type_id:: + traverse_float (member_info& mi) + { + type_id_ = string ("mysql::") + + float_database_id[mi.st->type - sql_type::FLOAT]; + } + + void member_database_type_id:: + traverse_decimal (member_info&) + { + type_id_ = "mysql::id_decimal"; + } + + void member_database_type_id:: + traverse_date_time (member_info& mi) + { + type_id_ = string ("mysql::") + + date_time_database_id[mi.st->type - sql_type::DATE]; + } + + void member_database_type_id:: + traverse_string (member_info& mi) + { + type_id_ = string ("mysql::") + + char_bin_database_id[mi.st->type - sql_type::CHAR]; + } + + void member_database_type_id:: + traverse_bit (member_info&) + { + type_id_ = "mysql::id_bit"; + } + + void member_database_type_id:: + traverse_enum (member_info&) + { + type_id_ = "mysql::id_enum"; + } + + void member_database_type_id:: + traverse_set (member_info&) + { + type_id_ = "mysql::id_set"; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/mysql/common.hxx b/odb/odb/relational/mysql/common.hxx new file mode 100644 index 0000000..b43dc0d --- /dev/null +++ b/odb/odb/relational/mysql/common.hxx @@ -0,0 +1,171 @@ +// file : odb/relational/mysql/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MYSQL_COMMON_HXX +#define ODB_RELATIONAL_MYSQL_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/mysql/context.hxx> + +namespace relational +{ + namespace mysql + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the mysql namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_integer (member_info&) + { + } + + virtual void + traverse_float (member_info&) + { + } + + virtual void + traverse_decimal (member_info&) + { + } + + virtual void + traverse_date_time (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_short_string (member_info& mi) + { + traverse_string (mi); + } + + virtual void + traverse_long_string (member_info& mi) + { + traverse_string (mi); + } + + virtual void + traverse_bit (member_info&) + { + } + + virtual void + traverse_enum (member_info&) + { + } + + virtual void + traverse_set (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_enum (member_info&); + + virtual void + traverse_set (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (type&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_decimal (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_enum (member_info&); + + virtual void + traverse_set (member_info&); + + private: + string type_id_; + }; + } +} +#endif // ODB_RELATIONAL_MYSQL_COMMON_HXX diff --git a/odb/odb/relational/mysql/context.cxx b/odb/odb/relational/mysql/context.cxx new file mode 100644 index 0000000..8b3d983 --- /dev/null +++ b/odb/odb/relational/mysql/context.cxx @@ -0,0 +1,868 @@ +// file : odb/relational/mysql/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> +#include <sstream> + +#include <odb/sql-token.hxx> +#include <odb/sql-lexer.hxx> + +#include <odb/relational/mysql/context.hxx> +#include <odb/relational/mysql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace + { + struct type_map_entry + { + char const* const cxx_type; + char const* const db_type; + char const* const db_id_type; + bool const null; + }; + + type_map_entry type_map[] = + { + {"bool", "TINYINT(1)", 0, false}, + + {"char", "CHAR(1)", 0, false}, + {"signed char", "TINYINT", 0, false}, + {"unsigned char", "TINYINT UNSIGNED", 0, false}, + + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT UNSIGNED", 0, false}, + + {"int", "INT", 0, false}, + {"unsigned int", "INT UNSIGNED", 0, false}, + + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT UNSIGNED", 0, false}, + + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT UNSIGNED", 0, false}, + + {"float", "FLOAT", 0, false}, + {"double", "DOUBLE", 0, false}, + + {"::std::string", "TEXT", "VARCHAR(128)", false}, + + {"::size_t", "BIGINT UNSIGNED", 0, false}, + {"::std::size_t", "BIGINT UNSIGNED", 0, false} + }; + } + + context* context::current_; + + context:: + ~context () + { + if (current_ == this) + current_ = 0; + } + + context:: + context (ostream& os, + semantics::unit& u, + options_type const& ops, + features_type& f, + sema_rel::model* m) + : root_context (os, u, ops, f, data_ptr (new (shared) data (os))), + base_context (static_cast<data*> (root_context::data_.get ()), m), + data_ (static_cast<data*> (base_context::data_)) + { + assert (current_ == 0); + current_ = this; + + generate_grow = true; + need_alias_as = true; + insert_send_auto_id = true; + delay_freeing_statement_result = false; + need_image_clone = false; + generate_bulk = false; + global_index = false; + global_fkey = true; + data_->bind_vector_ = "MYSQL_BIND*"; + data_->truncated_vector_ = "my_bool*"; + + // Populate the C++ type to DB type map. + // + for (size_t i (0); i < sizeof (type_map) / sizeof (type_map_entry); ++i) + { + type_map_entry const& e (type_map[i]); + + type_map_type::value_type v ( + e.cxx_type, + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); + + data_->type_map_.insert (v); + } + } + + context:: + context () + : data_ (current ().data_) + { + } + + string const& context:: + convert_expr (string const& sqlt, semantics::data_member& m, bool to) + { + sql_type const& t (parse_sql_type (sqlt, m)); + return to ? t.to : t.from; + } + + string context:: + quote_id_impl (qname const& id) const + { + string r; + + bool f (true); + for (qname::iterator i (id.begin ()); i < id.end (); ++i) + { + if (i->empty ()) + continue; + + // Warn if the name is greater than the 64 limit. + // + if (i->size () > 64) + { + cerr << "warning: SQL name '" << *i << "' is longer than " + << "the MySQL name limit of 64 characters and will " + << "be truncated" << endl; + + cerr << "info: consider shortening it using #pragma db " + << "table/column/index or --*-regex options" << endl; + } + + if (f) + f = false; + else + r += '.'; + + r += '`'; + r.append (*i, 0, 64); // Max identifier length is 64. + r += '`'; + } + + return r; + } + + namespace + { + struct has_grow: traversal::class_ + { + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + bool view (context::view (c)); + + // Ignore transient bases. + // + if (!(context::object (c) || view || context::composite (c))) + return; + + if (section_ == 0 && c.count ("mysql-grow")) + r_ = c.get<bool> ("mysql-grow"); + else + { + // r_ should be false. + // + if (!view) + inherits (c); + + if (!r_) + names (c); + + if (section_ == 0) + c.set ("mysql-grow", r_); + } + } + + private: + bool& r_; + user_section* section_; + traversal::inherits inherits_; + }; + + struct has_grow_member: member_base + { + has_grow_member (bool& r, user_section* section = 0) + : relational::member_base (0, 0, string (), string (), section), + r_ (r) {} + + has_grow_member (bool& r, + user_section* section, + semantics::type* t, + const custom_cxx_type* ct, + string const& key_prefix = string ()) + : relational::member_base (t, ct, string (), key_prefix, section), + r_ (r) {} + + virtual bool + pre (member_info& mi) + { + // If we have a key prefix (container), then it can't be in a + // section (while mi.m can). The same for top-level -- if we got + // called, then we shouldn't ignore it. + // + return !key_prefix_.empty () || top_level_ || + (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + + virtual void + traverse_composite (member_info& mi) + { + // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. + // + r_ = r_ || context::grow (dynamic_cast<semantics::class_&> (mi.t)); + } + + virtual void + traverse_decimal (member_info&) + { + r_ = true; + } + + virtual void + traverse_long_string (member_info&) + { + r_ = true; + } + + virtual void + traverse_short_string (member_info&) + { + r_ = true; // @@ Short string optimization disabled. + } + + virtual void + traverse_enum (member_info&) + { + r_ = true; + } + + virtual void + traverse_set (member_info&) + { + r_ = true; + } + + private: + bool& r_; + }; + } + + bool context:: + grow_impl (semantics::class_& c, user_section* section) + { + if (section == 0 && c.count ("mysql-grow")) + return c.get<bool> ("mysql-grow"); + + bool r (false); + has_grow ct (r, section); + has_grow_member mt (r, section); + traversal::names names; + ct >> names >> mt; + ct.traverse (c); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m) + { + bool r (false); + has_grow_member mt (r); + mt.traverse (m, true); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m, + semantics::type& t, + const custom_cxx_type* ct, + string const& kp) + { + bool r (false); + has_grow_member mt (r, 0, &t, ct, kp); + mt.traverse (m, true); + return r; + } + + string context:: + database_type_impl (semantics::type& t, + semantics::names* hint, + bool id, + bool* null) + { + using semantics::enum_; + using semantics::enumerator; + using semantics::array; + + string r; + + // Enum mapping. + // + if (enum_* e = dynamic_cast<enum_*> (&t)) + { + // We can only map to ENUM if the C++ enumeration is contiguous + // and starts with 0. + // + enum_::enumerates_iterator i (e->enumerates_begin ()), + end (e->enumerates_end ()); + + if (i != end) + { + r += "ENUM("; + + for (unsigned long long j (0); i != end; ++i, ++j) + { + enumerator const& er (i->enumerator ()); + + if (er.value () != j) + break; + + if (j != 0) + r += ", "; + + r += quote_string (er.name ()); + } + + if (i == end) + r += ")"; + else + r.clear (); + } + + if (!r.empty ()) + return r; + } + + r = base_context::database_type_impl (t, hint, id, null); + + if (!r.empty ()) + return r; + + // char[N] mapping. + // + else if (array* a = dynamic_cast<array*> (&t)) + { + semantics::type& bt (a->base_type ()); + if (bt.is_a<semantics::fund_char> ()) + { + unsigned long long n (a->size ()); + + if (n == 0) + return r; + else if (n == 1) + r = "CHAR("; + else + { + r = "VARCHAR("; + n--; + } + + ostringstream ostr; + ostr << n; + r += ostr.str (); + r += ')'; + } + } + + return r; + } + + // + // SQL type parsing. + // + + sql_type const& context:: + parse_sql_type (string const& t, semantics::data_member& m, bool custom) + { + // If this proves to be too expensive, we can maintain a cache of + // parsed types across contexts. + // + data::sql_type_cache::iterator i (data_->sql_type_cache_.find (t)); + + if (i != data_->sql_type_cache_.end () + && (custom ? i->second.custom_cached : i->second.straight_cached)) + { + return (custom ? i->second.custom : i->second.straight); + } + else + { + try + { + sql_type st ( + parse_sql_type ( + t, + custom ? &unit.get<custom_db_types> ("custom-db-types") : 0)); + + if (custom) + return data_->sql_type_cache_[t].cache_custom (st); + else + return data_->sql_type_cache_[t].cache_straight (st); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } + } + } + + inline sql_type + error (bool fail, string const& m) + { + if (!fail) + return sql_type (); + else + throw context::invalid_sql_type (m); + } + + sql_type context:: + parse_sql_type (string sqlt, custom_db_types const* ct) + { + try + { + sql_type r; + + // First run the type through the custom mapping, if requested. + // + if (ct != 0) + { + for (custom_db_types::const_iterator i (ct->begin ()); + i != ct->end (); ++i) + { + custom_db_type const& t (*i); + + if (t.type.match (sqlt)) + { + r.to = t.type.replace (sqlt, t.to); + r.from = t.type.replace (sqlt, t.from); + sqlt = t.type.replace (sqlt, t.as); + break; + } + } + } + + sql_lexer l (sqlt); + + // While most type names use single identifier, there are + // a couple of exceptions to this rule: + // + // NATIONAL CHAR|VARCHAR + // CHAR BYTE (BINARY) + // CHARACTER VARYING (VARCHAR) + // LONG VARBINARY (MEDIUMBLOB) + // LONG VARCHAR (MEDIUMTEXT) + // + // + enum state + { + parse_prefix, + parse_name, + parse_range, + parse_sign, + parse_done + }; + + state s (parse_prefix); + string prefix; + bool flt (false); + + for (sql_token t (l.next ()); + s != parse_done && t.type () != sql_token::t_eos; + t = l.next ()) + { + sql_token::token_type tt (t.type ()); + + switch (s) + { + case parse_prefix: + { + if (tt == sql_token::t_identifier) + { + string const& id (context::upcase (t.identifier ())); + + if (id == "NATIONAL" || + id == "CHAR" || + id == "CHARACTER" || + id == "LONG") + { + prefix = id; + s = parse_name; + continue; + } + } + + s = parse_name; + } + // Fall through. + case parse_name: + { + if (tt == sql_token::t_identifier) + { + bool match (true); + string const& id (context::upcase (t.identifier ())); + + // Numeric types. + // + if (id == "BIT") + { + r.type = sql_type::BIT; + } + else if (id == "TINYINT" || id == "INT1") + { + r.type = sql_type::TINYINT; + } + else if (id == "BOOL" || id == "BOOLEAN") + { + r.type = sql_type::TINYINT; + r.range = true; + r.range_value = 1; + } + else if (id == "SMALLINT" || id == "INT2") + { + r.type = sql_type::SMALLINT; + } + else if (id == "MEDIUMINT" || + id == "INT3" || + id == "MIDDLEINT") + { + r.type = sql_type::MEDIUMINT; + } + else if (id == "INT" || id == "INTEGER" || id == "INT4") + { + r.type = sql_type::INT; + } + else if (id == "BIGINT" || id == "INT8") + { + r.type = sql_type::BIGINT; + } + else if (id == "SERIAL") + { + r.type = sql_type::BIGINT; + r.unsign = true; + } + else if (id == "FLOAT") + { + // Assign a type only once we know the precision of the + // float; it can be either 4 or 8 byte. + // + flt = true; + } + else if (id == "FLOAT4") + { + r.type = sql_type::FLOAT; + } + else if (id == "DOUBLE" || id == "FLOAT8") + { + r.type = sql_type::DOUBLE; + } + else if (id == "DECIMAL" || + id == "DEC" || + id == "NUMERIC" || + id == "FIXED") + { + r.type = sql_type::DECIMAL; + } + // + // Date-time types. + // + else if (id == "DATE") + { + r.type = sql_type::DATE; + } + else if (id == "TIME") + { + r.type = sql_type::TIME; + } + else if (id == "DATETIME") + { + r.type = sql_type::DATETIME; + } + else if (id == "TIMESTAMP") + { + r.type = sql_type::TIMESTAMP; + } + else if (id == "YEAR") + { + r.type = sql_type::YEAR; + } + // + // String and binary types. + // + else if (id == "NCHAR") + { + r.type = sql_type::CHAR; + } + else if (id == "VARCHAR") + { + r.type = prefix == "LONG" + ? sql_type::MEDIUMTEXT + : sql_type::VARCHAR; + } + else if (id == "NVARCHAR") + { + r.type = sql_type::VARCHAR; + } + else if (id == "VARYING" && prefix == "CHARACTER") + { + r.type = sql_type::VARCHAR; + } + else if (id == "BINARY") + { + r.type = sql_type::BINARY; + } + else if (id == "BYTE" && prefix == "CHAR") + { + r.type = sql_type::BINARY; + } + else if (id == "VARBINARY") + { + r.type = prefix == "LONG" + ? sql_type::MEDIUMBLOB + : sql_type::VARBINARY; + } + else if (id == "TINYBLOB") + { + r.type = sql_type::TINYBLOB; + } + else if (id == "TINYTEXT") + { + r.type = sql_type::TINYTEXT; + } + else if (id == "BLOB") + { + r.type = sql_type::BLOB; + } + else if (id == "TEXT") + { + r.type = sql_type::TEXT; + } + else if (id == "MEDIUMBLOB") + { + r.type = sql_type::MEDIUMBLOB; + } + else if (id == "MEDIUMTEXT") + { + r.type = sql_type::MEDIUMTEXT; + } + else if (id == "LONGBLOB") + { + r.type = sql_type::LONGBLOB; + } + else if (id == "LONGTEXT") + { + r.type = sql_type::LONGTEXT; + } + else if (id == "ENUM") + { + r.type = sql_type::ENUM; + } + else if (id == "SET") + { + r.type = sql_type::SET; + } + else + match = false; + + if (match) + { + s = parse_range; + continue; + } + } + + // Some prefixes can also be type names if not followed + // by the actual type name. + // + if (!prefix.empty ()) + { + if (prefix == "CHAR" || prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + } + else if (prefix == "LONG") + { + r.type = sql_type::MEDIUMTEXT; + } + } + + if (r.type == sql_type::invalid) + { + if (tt == sql_token::t_identifier) + { + return error (ct, "unknown MySQL type '" + t.identifier () + + "'"); + } + else + return error (ct, "expected MySQL type name"); + } + + s = parse_range; + } + // Fall through. + case parse_range: + { + if (t.punctuation () == sql_token::p_lparen) + { + t = l.next (); + + // ENUM and SET have a list of members instead of the range. + // + if (r.type == sql_type::ENUM || r.type == sql_type::SET) + { + while (true) + { + if (t.type () != sql_token::t_string_lit) + { + return error (ct, "string literal expected in MySQL " + "ENUM or SET declaration"); + } + + if (r.type == sql_type::ENUM) + r.enumerators.push_back (t.literal ()); + + t = l.next (); + + if (t.punctuation () == sql_token::p_rparen) + break; + else if (t.punctuation () != sql_token::p_comma) + { + return error (ct, "comma expected in MySQL ENUM or " + "SET declaration"); + } + + t = l.next (); + } + } + else + { + if (t.type () != sql_token::t_int_lit) + { + return error (ct, "integer range expected in MySQL type " + "declaration"); + } + + unsigned int v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + return error (ct, "invalid range value '" + t.literal () + + "' in MySQL type declaration"); + } + + r.range = true; + r.range_value = v; + + t = l.next (); + + if (t.punctuation () == sql_token::p_comma) + { + // We have the second range value. Skip it. + // + // In FLOAT the two-value range means something + // completely different than the single-value. + // Pretend we don't have the range in the former + // case. + // + if (flt) + r.range = false; + + l.next (); + t = l.next (); + } + } + + if (t.punctuation () != sql_token::p_rparen) + { + return error (ct, "expected ')' in MySQL type declaration"); + } + + s = parse_sign; + continue; + } + + s = parse_sign; + } + // Fall through. + case parse_sign: + { + if (tt == sql_token::t_identifier && + context::upcase (t.identifier ()) == "UNSIGNED") + { + r.unsign = true; + } + + s = parse_done; + break; + } + case parse_done: + { + assert (false); + break; + } + } + } + + if (s == parse_name && !prefix.empty ()) + { + // Some prefixes can also be type names if not followed + // by the actual type name. + // + if (prefix == "CHAR" || prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + } + else if (prefix == "LONG") + { + r.type = sql_type::MEDIUMTEXT; + } + } + + if (flt) + { + r.type = !r.range || r.range_value < 24 + ? sql_type::FLOAT + : sql_type::DOUBLE; + } + + if (r.type == sql_type::invalid) + return error (ct, "incomplete MySQL type declaration"); + + // If range is omitted for CHAR or BIT types, it defaults to 1. + // + if ((r.type == sql_type::CHAR || r.type == sql_type::BIT) && !r.range) + { + r.range = true; + r.range_value = 1; + } + + return r; + } + catch (sql_lexer::invalid_input const& e) + { + return error (ct, "invalid MySQL type declaration: " + e.message); + } + } + } +} diff --git a/odb/odb/relational/mysql/context.hxx b/odb/odb/relational/mysql/context.hxx new file mode 100644 index 0000000..98574f2 --- /dev/null +++ b/odb/odb/relational/mysql/context.hxx @@ -0,0 +1,194 @@ +// file : odb/relational/mysql/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MYSQL_CONTEXT_HXX +#define ODB_RELATIONAL_MYSQL_CONTEXT_HXX + +#include <map> +#include <vector> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace mysql + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Integral types. + // + TINYINT, + SMALLINT, + MEDIUMINT, + INT, + BIGINT, + + // Float types. + // + FLOAT, + DOUBLE, + DECIMAL, + + // Data-time types. + // + DATE, + TIME, + DATETIME, + TIMESTAMP, + YEAR, + + // String and binary types. + // + CHAR, + BINARY, + VARCHAR, + VARBINARY, + TINYTEXT, + TINYBLOB, + TEXT, + BLOB, + MEDIUMTEXT, + MEDIUMBLOB, + LONGTEXT, + LONGBLOB, + + // Other types. + // + BIT, + ENUM, + SET, + + // Invalid type. + // + invalid + }; + + sql_type () : type (invalid), unsign (false), range (false) {} + + core_type type; + bool unsign; + bool range; + unsigned int range_value; // MySQL max value is 2^32 - 1 (LONGBLOG/TEXT). + std::vector<std::string> enumerators; // Enumerator strings for ENUM. + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string, custom_db_types const* = 0); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual bool + grow_impl (semantics::class_&, user_section*); + + virtual bool + grow_impl (semantics::data_member&); + + virtual bool + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&); + + protected: + virtual string + quote_id_impl (qname const&) const; + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_MYSQL_CONTEXT_HXX diff --git a/odb/odb/relational/mysql/header.cxx b/odb/odb/relational/mysql/header.cxx new file mode 100644 index 0000000..27bae48 --- /dev/null +++ b/odb/odb/relational/mysql/header.cxx @@ -0,0 +1,136 @@ +// file : odb/relational/mysql/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +namespace relational +{ + namespace mysql + { + namespace header + { + namespace relational = relational::header; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + // Exchanged as strings. Can have up to 65 digits not counting + // '-' and '.'. If range is not specified, the default is 10. + // + + /* + @@ Disabled. + os << "char " << mi.var << "value[" << + (t.range ? t.range_value : 10) + 3 << "];" + */ + + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "my_bool " << mi.var << "null;" + << endl; + + } + + virtual void + traverse_short_string (member_info& mi) + { + // If range is not specified, the default buffer size is 255. + // + /* + @@ Disabled. + os << "char " << mi.var << "value[" << + (t.range ? t.range_value : 255) + 1 << "];" + */ + + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Valid range is 1 to 64. + // + unsigned int n (mi.st->range / 8 + (mi.st->range % 8 ? 1 : 0)); + + os << "unsigned char " << mi.var << "value[" << n << "];" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. Since we don't know + // at the code generation time which one it is, we have to always + // keep size in case it is a string. + // + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as string. + // + os << image_type << " " << mi.var << "value;" + << "unsigned long " << mi.var << "size;" + << "my_bool " << mi.var << "null;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/mysql/inline.cxx b/odb/odb/relational/mysql/inline.cxx new file mode 100644 index 0000000..bfa2c94 --- /dev/null +++ b/odb/odb/relational/mysql/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/mysql/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "null;"; + else + os << "i." << mi.var << "null = 1;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/mysql/model.cxx b/odb/odb/relational/mysql/model.cxx new file mode 100644 index 0000000..17ed4c0 --- /dev/null +++ b/odb/odb/relational/mysql/model.cxx @@ -0,0 +1,161 @@ +// file : odb/relational/mysql/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + default_bool (semantics::data_member&, bool v) + { + // MySQL has TRUE and FALSE as just aliases for 1 and 0. Still + // use them for self-documentation. + // + return v ? "TRUE" : "FALSE"; + } + + virtual string + default_enum (semantics::data_member& m, tree en, string const& name) + { + // Make sure the column is mapped to an ENUM or integer type. + // + sql_type const& t (parse_sql_type (column_type (), m, false)); + + switch (t.type) + { + case sql_type::ENUM: + case sql_type::TINYINT: + case sql_type::SMALLINT: + case sql_type::MEDIUMINT: + case sql_type::INT: + case sql_type::BIGINT: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to MySQL ENUM or integer type" + << endl; + + throw operation_failed (); + } + } + + using semantics::enum_; + using semantics::enumerator; + + enumerator& er (dynamic_cast<enumerator&> (*unit.find (en))); + enum_& e (er.enum_ ()); + + if (t.type == sql_type::ENUM) + { + // Assuming the enumerators in the C++ enum and MySQL ENUM are + // in the same order, calculate the poistion of the C++ + // enumerator and use that as an index in the MySQL ENUM. + // + size_t pos (0); + + for (enum_::enumerates_iterator i (e.enumerates_begin ()), + end (e.enumerates_end ()); i != end; ++i) + { + if (&i->enumerator () == &er) + break; + + pos++; + } + + if (pos < t.enumerators.size ()) + return t.enumerators[pos]; + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: unable to map C++ enumerator '" << name + << "' to MySQL ENUM value" << endl; + + throw operation_failed (); + } + } + else + { + ostringstream ostr; + + if (e.unsigned_ ()) + ostr << er.value (); + else + ostr << static_cast<long long> (er.value ()); + + return ostr.str (); + } + } + }; + entry<object_columns> object_columns_; + + struct member_create: relational::member_create, context + { + member_create (base const& x): base (x) {} + + virtual string + table_options (semantics::data_member& m, semantics::type& c) + { + string r (relational::member_create::table_options (m, c)); + + string const& engine (options.mysql_engine ()); + if (engine != "default") + { + // Note: MySQL table options can be separated with spaces. + // + if (!r.empty ()) + r += ' '; + + r += "ENGINE="; + r += engine; + } + + return r; + } + }; + entry<member_create> member_create_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual string + table_options (type& c) + { + string r (relational::class_::table_options (c)); + + string const& engine (options.mysql_engine ()); + if (engine != "default") + { + // Note: MySQL table options can be separated with spaces. + // + if (!r.empty ()) + r += ' '; + + r += "ENGINE="; + r += engine; + } + + return r; + } + }; + entry<class_> class__; + } + } +} diff --git a/odb/odb/relational/mysql/schema.cxx b/odb/odb/relational/mysql/schema.cxx new file mode 100644 index 0000000..60dc95b --- /dev/null +++ b/odb/odb/relational/mysql/schema.cxx @@ -0,0 +1,489 @@ +// file : odb/relational/mysql/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace schema + { + namespace relational = relational::schema; + using relational::table_set; + + // + // Drop. + // + + struct drop_foreign_key: relational::drop_foreign_key, context + { + drop_foreign_key (base const& x): base (x) {} + + virtual void + drop (sema_rel::table& t, sema_rel::foreign_key& fk) + { + /* + // @@ This does not work: in MySQL control statements can only + // be used in stored procedures. It seems the only way to + // implement this is to define, execute, and drop a stored + // procedure, which is just too ugly. + // + // Another option would be to use CREATE TABLE IF NOT EXISTS + // to create a dummy table with a dummy constraint that makes + // the following DROP succeed. Note, however, that MySQL issues + // a notice if the table already exist so would need to suppress + // that as well. Still not sure that the utility of this support + // justifies this kind of a hack. + // + os << "IF EXISTS (SELECT NULL FROM information_schema.TABLE_CONSTRAINTS" << endl + << " WHERE CONSTRAINT_TYPE = " << quote_string ("FOREIGN KEY") << "AND" << endl + << " CONSTRAINT_SCHEMA = DATABASE() AND" << endl + << " CONSTRAINT_NAME = " << quote_string (fk.name ()) << ") THEN" << endl + << " ALTER TABLE " << quote_id (t.name ()) << " DROP FOREIGN KEY " << quote_id (fk.name ()) << ";" << endl + << "END IF;" << endl; + */ + + // So for now we only do this in migration. + // + if (dropped_ == 0) + { + if (fk.not_deferrable ()) + pre_statement (); + else + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + } + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " DROP FOREIGN KEY " << quote_id (fk.name ()) << endl; + + if (fk.not_deferrable ()) + post_statement (); + else + os << "*/" << endl + << endl; + } + } + + using base::drop; + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + // Find the foreign key we are dropping in the base model. + // + sema_rel::foreign_key& fk (find<sema_rel::foreign_key> (dfk)); + + if (fk.not_deferrable () || in_comment) + base::traverse (dfk); + else + { + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" + << endl; + + drop (dfk); + + os << endl + << " */"; + } + } + + virtual void + drop_header () + { + os << "DROP FOREIGN KEY "; + } + }; + entry<drop_foreign_key> drop_foreign_key_; + + struct drop_index: relational::drop_index, context + { + drop_index (base const& x): base (x) {} + + virtual void + drop (sema_rel::index& in) + { + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + + os << "DROP INDEX " << name (in) << " ON " << + quote_id (t.name ()) << endl; + } + }; + entry<drop_index> drop_index_; + + // + // Create. + // + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} + + virtual void + auto_ (sema_rel::primary_key&) + { + os << " AUTO_INCREMENT"; + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (base const& x): base (x) {} + + void + diagnose (sema_rel::foreign_key& fk) + { + if (fk.on_delete () != sema_rel::foreign_key::no_action) + { + cerr << "warning: foreign key '" << fk.name () << "' has " << + "ON DELETE clause but is disabled in MySQL due to lack " + "of deferrable constraint support" << endl; + + cerr << "info: consider using non-deferrable foreign keys (" << + "--fkeys-deferrable-mode)" << endl; + } + } + + virtual void + traverse_create (sema_rel::foreign_key& fk) + { + // MySQL does not support deferrable constraint checking. Output + // such foreign keys as comments, for documentation, unless we + // are generating embedded schema. + // + if (fk.not_deferrable ()) + base::traverse_create (fk); + else + { + diagnose (fk); + + // Don't bloat C++ code with comment strings if we are + // generating embedded schema. + // + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" << endl + << " CONSTRAINT "; + create (fk); + os << endl + << " */"; + } + } + + virtual void + traverse_add (sema_rel::foreign_key& fk) + { + if (fk.not_deferrable () || in_comment) + base::traverse_add (fk); + else + { + diagnose (fk); + + if (format_ != schema_format::sql) + return; + + os << endl + << " /*" + << endl; + + add (fk); + + os << endl + << " */"; + } + } + + virtual void + deferrable (sema_rel::deferrable) + { + // This will still be called to output the comment. + } + }; + entry<create_foreign_key> create_foreign_key_; + + struct create_index: relational::create_index, context + { + create_index (base const& x): base (x) {} + + virtual void + create (sema_rel::index& in) + { + os << "CREATE "; + + if (!in.type ().empty ()) + os << in.type () << ' '; + + os << "INDEX " << name (in); + + if (!in.method ().empty ()) + os << " USING " << in.method (); + + os << endl + << " ON " << table_name (in) << " ("; + + columns (in); + + os << ")" << endl; + + if (!in.options ().empty ()) + os << ' ' << in.options () << endl; + } + }; + entry<create_index> create_index_; + + struct create_table: relational::create_table, context + { + create_table (base const& x): base (x) {} + + // See if there are any undefined foreign keys that are not + // deferrable. + // + bool + check_undefined_fk_deferrable_only (sema_rel::table& t) + { + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); ++i) + { + using sema_rel::foreign_key; + + if (foreign_key* fk = dynamic_cast<foreign_key*> (&i->nameable ())) + { + if (!fk->count ("mysql-fk-defined") && + fk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + traverse (sema_rel::table& t) + { + if (pass_ == 1) + base::traverse (t); + else + { + // Add undefined foreign keys. + // + if (check_undefined_fk (t)) + { + bool deferrable (check_undefined_fk_deferrable_only (t)); + + if (!deferrable || format_ == schema_format::sql) + { + if (deferrable) + { + os << "/*" << endl; + in_comment = true; + } + else + pre_statement (); + + os << "ALTER TABLE " << quote_id (t.name ()); + + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (t, n); + os << endl; + + if (deferrable) + { + in_comment = false; + os << "*/" << endl + << endl; + } + else + post_statement (); + } + } + } + } + }; + entry<create_table> create_table_; + + // + // Alter. + // + + struct alter_column: relational::alter_column, context + { + alter_column (base const& x): base (x) {} + + virtual void + alter_header () + { + os << "MODIFY COLUMN "; + } + }; + entry<alter_column> alter_column_; + + struct alter_table_pre: relational::alter_table_pre, context + { + alter_table_pre (base const& x): base (x) {} + + // Check if we are only dropping deferrable foreign keys. + // + bool + check_drop_deferrable_only (sema_rel::alter_table& at) + { + if (check<sema_rel::add_column> (at) || + check_alter_column_null (at, true)) + return false; + + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::foreign_key; + using sema_rel::drop_foreign_key; + + if (drop_foreign_key* dfk = + dynamic_cast<drop_foreign_key*> (&i->nameable ())) + { + foreign_key& fk (find<foreign_key> (*dfk)); + + if (fk.not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + if (check_drop_deferrable_only (at)) + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + in_comment = true; + + os << "ALTER TABLE " << quote_id (at.name ()); + instance<drop_foreign_key> dfk (*this); + trav_rel::unames n (*dfk); + names (at, n); + os << endl; + + in_comment = false; + os << "*/" << endl + << endl; + } + else + base::alter (at); + } + }; + entry<alter_table_pre> alter_table_pre_; + + struct alter_table_post: relational::alter_table_post, context + { + alter_table_post (base const& x): base (x) {} + + // Check if we are only adding deferrable foreign keys. + // + bool + check_add_deferrable_only (sema_rel::alter_table& at) + { + if (check<sema_rel::drop_column> (at) || + check_alter_column_null (at, false)) + return false; + + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::add_foreign_key; + + if (add_foreign_key* afk = + dynamic_cast<add_foreign_key*> (&i->nameable ())) + { + if (afk->not_deferrable ()) + return false; + } + } + return true; + } + + virtual void + alter (sema_rel::alter_table& at) + { + if (check_add_deferrable_only (at)) + { + if (format_ != schema_format::sql) + return; + + os << "/*" << endl; + in_comment = true; + + os << "ALTER TABLE " << quote_id (at.name ()); + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (at, n); + os << endl; + + in_comment = false; + os << "*/" << endl + << endl; + } + else + base::alter (at); + } + }; + entry<alter_table_post> alter_table_post_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x): base (x) {} + + virtual void + create_table () + { + pre_statement (); + + os << "CREATE TABLE IF NOT EXISTS " << qt_ << " (" << endl + << " " << qn_ << " VARCHAR(128) NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT UNSIGNED NOT NULL," << endl + << " " << qm_ << " TINYINT(1) NOT NULL)" << endl; + + string const& engine (options.mysql_engine ()); + if (engine != "default") + os << " ENGINE=" << engine << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + os << "INSERT IGNORE INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; + + post_statement (); + } + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/mysql/source.cxx b/odb/odb/relational/mysql/source.cxx new file mode 100644 index 0000000..9131ea7 --- /dev/null +++ b/odb/odb/relational/mysql/source.cxx @@ -0,0 +1,724 @@ +// file : odb/relational/mysql/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/mysql/common.hxx> +#include <odb/relational/mysql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace mysql + { + namespace source + { + namespace relational = relational::source; + + namespace + { + const char* integer_buffer_types[] = + { + "MYSQL_TYPE_TINY", + "MYSQL_TYPE_SHORT", + "MYSQL_TYPE_LONG", // *_bind_param() doesn't support INT24. + "MYSQL_TYPE_LONG", + "MYSQL_TYPE_LONGLONG" + }; + + const char* float_buffer_types[] = + { + "MYSQL_TYPE_FLOAT", + "MYSQL_TYPE_DOUBLE" + }; + + const char* date_time_buffer_types[] = + { + "MYSQL_TYPE_DATE", + "MYSQL_TYPE_TIME", + "MYSQL_TYPE_DATETIME", + "MYSQL_TYPE_TIMESTAMP", + "MYSQL_TYPE_SHORT" + }; + + const char* char_bin_buffer_types[] = + { + "MYSQL_TYPE_STRING", // CHAR + "MYSQL_TYPE_BLOB", // BINARY, + "MYSQL_TYPE_STRING", // VARCHAR + "MYSQL_TYPE_BLOB", // VARBINARY + "MYSQL_TYPE_STRING", // TINYTEXT + "MYSQL_TYPE_BLOB", // TINYBLOB + "MYSQL_TYPE_STRING", // TEXT + "MYSQL_TYPE_BLOB", // BLOB + "MYSQL_TYPE_STRING", // MEDIUMTEXT + "MYSQL_TYPE_BLOB", // MEDIUMBLOB + "MYSQL_TYPE_STRING", // LONGTEXT + "MYSQL_TYPE_BLOB" // LONGBLOB + }; + } + + // + // + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + // When we store a ENUM column in the MySQL database, if we bind + // an integer parameter, then it is treated as an index and if we + // bind a string, then it is treated as a enumerator. Everything + // would have worked well if the same logic applied to the select + // operation. That is, if we bind integer, then the database sends + // the index and if we bind string then the database sends the + // enumerator. Unfortunately, MySQL always sends the enumerator + // and to get the index one has to resort to the enum+0 hack. + // + // This causes the following problem: at code generation time we + // do not yet know which format we want. This is determined at + // C++ compile time by traits (the reason we don't know this is + // because we don't want to drag database-specific runtimes, + // which define the necessary traits, as well as their + // prerequisites into the ODB compilation process). As a result, + // we cannot decide at code generation time whether we need the + // +0 hack or not. One way to overcome this would be to construct + // the SELECT statements at runtime, something along these lines: + // + // "enum" + enum_traits<type>::hack + "," + // + // However, this complicates the code generator quite a bit: we + // either have to move to std::string storage for all the + // statements and all the databases, which is kind of a waste, + // or do some deep per-database customizations, which is hairy. + // So, instead, we are going to use another hack (hey, what the + // hell, right?) by loading both the index and enumerator + // combined into a string: + // + // CONCAT (enum+0, ' ', enum) + // + // For cases where we need the index, everything works since + // MySQL will convert the leading number and stop at the space. + // For cases where we need the enumerator, we do a bit of pre- + // processing (see enum_traits) before handing the value off + // to value_traits. + // + + string const& type (column_type ()); + + if (sk_ != statement_select || + parse_sql_type (type, m).type != sql_type::ENUM) + { + return base::column (m, table, column); + } + + // Qualified column and conversion expression. + // + string qc; + if (!table.empty ()) + { + qc += table; + qc += '.'; + } + qc += column; + qc = convert_from (qc, type, m); + + string r ("CONCAT(" + qc + "+0,' '," + qc + ")"); + + sc_.push_back ( + relational::statement_column (table, r, type, m, key_prefix_)); + return true; + } + }; + entry<object_columns> object_columns_; + + struct view_columns: relational::view_columns, context + { + view_columns (base const& x): base (x) {} + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + // The same idea as in object_columns. + // + string const& type (column_type ()); + + if (parse_sql_type (type, m).type != sql_type::ENUM) + { + return base::column (m, table, column); + } + + // Column and conversion expression. + // + string c (convert_from (column, type, m)); + + string r ("CONCAT(" + c + "+0,' '," + c + ")"); + sc_.push_back (relational::statement_column (table, r, type, m)); + return true; + } + }; + entry<view_columns> view_columns_; + + // + // bind + // + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_integer (member_info& mi) + { + // While the is_unsigned should indicate whether the + // buffer variable is unsigned, rather than whether the + // database type is unsigned, in case of the image types, + // this is the same. + // + os << b << ".buffer_type = " << + integer_buffer_types[mi.st->type - sql_type::TINYINT] << ";" + << b << ".is_unsigned = " << (mi.st->unsign ? "1" : "0") << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << b << ".buffer_type = " << + float_buffer_types[mi.st->type - sql_type::FLOAT] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << b << ".buffer_type = MYSQL_TYPE_NEWDECIMAL;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << b << ".buffer_type = " << + date_time_buffer_types[mi.st->type - sql_type::DATE] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;"; + + if (mi.st->type == sql_type::YEAR) + os << b << ".is_unsigned = 0;"; + + os << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_short_string (member_info& mi) + { + // MySQL documentation is quite confusing about the use of + // buffer_length and length when it comes to input parameters. + // Source code, however, tells us that it uses buffer_length + // only if length is NULL. + // + os << b << ".buffer_type = " << + char_bin_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << b << ".buffer_type = " << + char_bin_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_bit (member_info& mi) + { + // Treated as a BLOB. + // + os << b << ".buffer_type = MYSQL_TYPE_BLOB;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << "sizeof (" << arg << "." << mi.var << "value));" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + os << "mysql::enum_traits::bind (" << b << "," << endl + << arg << "." << mi.var << "value," << endl + << arg << "." << mi.var << "size," << endl + << "&" << arg << "." << mi.var << "null);"; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << b << ".buffer_type = MYSQL_TYPE_STRING;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".buffer_length = static_cast<unsigned long> (" << endl + << arg << "." << mi.var << "value.capacity ());" + << b << ".length = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + }; + entry<bind_member> bind_member_; + + // + // grow + // + + struct grow_member: relational::grow_member_impl<sql_type>, + member_base + { + grow_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_float (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + // @@ Optimization disabled. + // + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_date_time (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_short_string (member_info& mi) + { + // @@ Optimization disabled. + // + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_bit (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string (and we don't know + // at the code generation time which one it is). + // + os << "if (" << e << ")" << endl + << "{" + << "if (mysql::enum_traits::grow (" << + "i." << mi.var << "value, " << + "i." << mi.var << "size))" << endl + << "grew = true;" // String + << "else" << endl + << e << " = 0;" // Integer. + << "}"; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + }; + entry<grow_member> grow_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "null = 1;"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_decimal (member_info& mi) + { + // @@ Optimization: can remove growth check if buffer is fixed. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_short_string (member_info& mi) + { + // @@ Optimization: can remove growth check if buffer is fixed. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_bit (member_info& mi) + { + // Represented as a BLOB. + // + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);"; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + os << "if (mysql::enum_traits::set_image (" << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "is_null," << endl + << member << "))" << endl + << "grew = true;" + << endl + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = static_cast<unsigned long> (size);" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "null"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_decimal (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_short_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_long_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Represented as a BLOB. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_enum (member_info& mi) + { + // Represented as either integer or string. + // + os << "mysql::enum_traits::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_set (member_info& mi) + { + // Represented as a string. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual void + init_auto_id (semantics::data_member& m, string const& im) + { + // Don't set the id value to 0 if this is a nullable wrapper. This + // will allow the user to use the NO_AUTO_VALUE_ON_ZERO mode by + // making it NULL when they want the auto semantics: + // + // #pragma db auto + // odb::nullable<int64_t> id; + // + semantics::type& t (utype (m)); + if (wrapper (t) && t.template get<bool> ("wrapper-null-handler")) + return; + + os << im << "value = 0;" + << endl; + } + + virtual string + join_syntax (view_object const& vo) + { + if (vo.join == view_object::full) + { + error (vo.loc) + << "FULL OUTER JOIN is not supported by MySQL" << endl; + throw operation_failed (); + } + + return base::join_syntax (vo); + } + }; + entry<class_> class_entry_; + + struct include: relational::include, context + { + include (base const& x): base (x) {} + + virtual void + extra_post () + { + os << "#include <odb/mysql/enum.hxx>" << endl; + } + }; + entry<include> include_; + } + } +} diff --git a/odb/odb/relational/oracle/common.cxx b/odb/odb/relational/oracle/common.cxx new file mode 100644 index 0000000..7caafc9 --- /dev/null +++ b/odb/odb/relational/oracle/common.cxx @@ -0,0 +1,522 @@ +// file : odb/relational/oracle/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/oracle/common.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + switch (mi.st->type) + { + // Numeric types. + // + case sql_type::NUMBER: + { + const sql_type& st (*mi.st); + + if (st.prec) + { + unsigned short r (st.prec_value); + + if (!st.scale) + { + if (r <= 10) + traverse_int32 (mi); + // Only OCI versions 11.2 and later support insertion and + // extraction into a 64 bit integer. + // + else if ( + (options.oracle_client_version () >= oracle_version (11, 2)) && + (r <= 19 || (r == 20 && unsigned_integer (mi.t)))) + traverse_int64 (mi); + else + traverse_big_int (mi); + } + else + { + // We can calculate the decimal exponent of the normalised + // floating point equivalent of the fixed point number using + // e = p - s, where p is the precision, s is the scale, and + // e the exponent. We can then use this to determine whether + // or not a value of Oracle SQL type NUMBER can be completely + // stored in the native floating point type. + // + + // The maximum decimal precision of a float is 7 significant + // digits. The minimum and maximum decimal exponents + // representable by a float are -37 and 38 respectively. + // + if (r <= 7) + { + int e = r - st.scale_value; + + if (e >= -37 && e <= 38) + traverse_float (mi); + else + traverse_double (mi); + } + + // The maximum decimal precision of a double is 15 significant + // digits. The minimum and maximum decimal exponent representable + // by a double exceeds that of the Oracle NUMBER type. + // + else if (r <= 15) + traverse_double (mi); + else + traverse_big_float (mi); + } + } + else + // If there is no precision, then this is a floating-point number. + // + traverse_double (mi); + + break; + } + case sql_type::FLOAT: + { + // We map FLOAT types based exclusively on their binary precision + // seeing that in 99% of cases it is the precision that is the + // limiting factor and not the exponent. + // + if (mi.st->prec_value <= 24) + traverse_float (mi); + else if (mi.st->prec_value <= 53) + traverse_double (mi); + else + traverse_big_float (mi); + + break; + } + case sql_type::BINARY_FLOAT: + { + traverse_float (mi); + break; + } + case sql_type::BINARY_DOUBLE: + { + traverse_double (mi); + break; + } + // Data-time types. + // + case sql_type::DATE: + { + traverse_date (mi); + break; + } + case sql_type::TIMESTAMP: + { + traverse_timestamp (mi); + break; + } + case sql_type::INTERVAL_YM: + { + traverse_interval_ym (mi); + break; + } + case sql_type::INTERVAL_DS: + { + traverse_interval_ds (mi); + break; + } + // String and binary types. + // + case sql_type::CHAR: + case sql_type::NCHAR: + case sql_type::VARCHAR2: + case sql_type::NVARCHAR2: + case sql_type::RAW: + { + traverse_string (mi); + break; + } + case sql_type::BLOB: + case sql_type::CLOB: + case sql_type::NCLOB: + { + traverse_lob (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_oracle >::image_type"; + } + + void member_image_type:: + traverse_int32 (member_info& mi) + { + if (unsigned_integer (mi.t)) + type_ = "unsigned int"; + else + type_ = "int"; + } + + void member_image_type:: + traverse_int64 (member_info& mi) + { + if (unsigned_integer (mi.t)) + type_ = "unsigned long long"; + else + type_ = "long long"; + } + + void member_image_type:: + traverse_big_int (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_float (member_info&) + { + type_ = "float"; + } + + void member_image_type:: + traverse_double (member_info&) + { + type_ = "double"; + } + + void member_image_type:: + traverse_big_float (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_date (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_timestamp (member_info&) + { + type_ = "oracle::datetime"; + } + + void member_image_type:: + traverse_interval_ym (member_info&) + { + type_ = "oracle::interval_ym"; + } + + void member_image_type:: + traverse_interval_ds (member_info&) + { + type_ = "oracle::interval_ds"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "char*"; + } + + void member_image_type:: + traverse_lob (member_info&) + { + type_ = "oracle::lob_callback"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + namespace + { + const char* string_bin_database_id[] = + { + "id_string", // CHAR + "id_nstring", // NCHAR + "id_string", // VARCHAR2 + "id_nstring", // NVARCHAR2 + "id_raw" // RAW + }; + + const char* lob_database_id[] = + { + "id_blob", + "id_clob", + "id_nclob" + }; + } + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) {} + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) {} + + string member_database_type_id:: + database_type_id (type& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_int32 (member_info&) + { + type_id_ = "oracle::id_int32"; + } + + void member_database_type_id:: + traverse_int64 (member_info&) + { + type_id_ = "oracle::id_int64"; + } + + void member_database_type_id:: + traverse_big_int (member_info&) + { + type_id_ = "oracle::id_big_int"; + } + + void member_database_type_id:: + traverse_float (member_info&) + { + type_id_ = "oracle::id_float"; + } + + void member_database_type_id:: + traverse_double (member_info&) + { + type_id_ = "oracle::id_double"; + } + + void member_database_type_id:: + traverse_big_float (member_info&) + { + type_id_ = "oracle::id_big_float"; + } + + void member_database_type_id:: + traverse_date (member_info&) + { + type_id_ = "oracle::id_date"; + } + + void member_database_type_id:: + traverse_timestamp (member_info&) + { + type_id_ = "oracle::id_timestamp"; + } + + void member_database_type_id:: + traverse_interval_ym (member_info&) + { + type_id_ = "oracle::id_interval_ym"; + } + + void member_database_type_id:: + traverse_interval_ds (member_info&) + { + type_id_ = "oracle::id_interval_ds"; + } + + void member_database_type_id:: + traverse_string (member_info& mi) + { + type_id_ = string ("oracle::") + + string_bin_database_id[mi.st->type - sql_type::CHAR]; + } + + void member_database_type_id:: + traverse_lob (member_info& mi) + { + type_id_ = string ("oracle::") + + lob_database_id[mi.st->type - sql_type::BLOB]; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + void + column_ctor (string const& type, string const& name, string const& base) + { + os << name << " ("; + + if (multi_dynamic) + os << "odb::query_column< " << type << " >& qc," << endl; + + os << "const char* t," << endl + << "const char* c," << endl + << "const char* conv," << endl + << "unsigned short p = 0xFFF," << endl + << "short s = 0xFFF)" << endl + << " : " << base << " (" << (multi_dynamic ? "qc, " : "") << + "t, c, conv, p, s)" + << "{" + << "}"; + } + + virtual void + column_ctor_args_extra (semantics::data_member& m) + { + // For some types we need to pass precision and scale. + // + sql_type const& st (parse_sql_type (column_type (), m)); + + switch (st.type) + { + case sql_type::NUMBER: + { + if (st.prec) + { + os << ", " << st.prec_value; + + if (st.scale) + os << ", " << st.scale_value; + } + break; + } + case sql_type::FLOAT: + { + os << ", " << st.prec_value; + break; + } + case sql_type::TIMESTAMP: + { + os << ", " << st.prec_value; + break; + } + case sql_type::INTERVAL_YM: + { + os << ", " << st.prec_value; + break; + } + case sql_type::INTERVAL_DS: + { + // INTERVAL DAY TO SECOND has two precisions. + // + os << ", " << st.prec_value << ", " << st.scale_value; + break; + } + case sql_type::CHAR: + case sql_type::NCHAR: + case sql_type::VARCHAR2: + case sql_type::NVARCHAR2: + case sql_type::RAW: + { + // The same logic as in header.cxx. + // + size_t n (st.prec ? st.prec_value : 1); + + if (!st.byte_semantics) + n *= 4; + + if (st.type == sql_type::VARCHAR2 || + st.type == sql_type::NVARCHAR2) + n = n > 4000 ? 4000 : n; + else + n = n > 2000 ? 2000 : n; + + os << ", " << n; + break; + } + default: + { + break; + } + } + } + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/oracle/common.hxx b/odb/odb/relational/oracle/common.hxx new file mode 100644 index 0000000..1958aab --- /dev/null +++ b/odb/odb/relational/oracle/common.hxx @@ -0,0 +1,203 @@ +// file : odb/relational/oracle/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_ORACLE_COMMON_HXX +#define ODB_RELATIONAL_ORACLE_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/oracle/context.hxx> + +namespace relational +{ + namespace oracle + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the oracle namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_int32 (member_info&) + { + } + + virtual void + traverse_int64 (member_info&) + { + } + + virtual void + traverse_big_int (member_info&) + { + } + + virtual void + traverse_float (member_info&) + { + } + + virtual void + traverse_double (member_info&) + { + } + + virtual void + traverse_big_float (member_info&) + { + } + + virtual void + traverse_date (member_info&) + { + } + + virtual void + traverse_timestamp (member_info&) + { + } + + virtual void + traverse_interval_ym (member_info&) + { + } + + virtual void + traverse_interval_ds (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_lob (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_int32 (member_info&); + + virtual void + traverse_int64 (member_info&); + + virtual void + traverse_big_int (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_double (member_info&); + + virtual void + traverse_big_float (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_timestamp (member_info&); + + virtual void + traverse_interval_ym (member_info&); + + virtual void + traverse_interval_ds (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_lob (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (type&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_int32 (member_info&); + + virtual void + traverse_int64 (member_info&); + + virtual void + traverse_big_int (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_double (member_info&); + + virtual void + traverse_big_float (member_info&); + + virtual void + traverse_date (member_info&); + + virtual void + traverse_timestamp (member_info&); + + virtual void + traverse_interval_ym (member_info&); + + virtual void + traverse_interval_ds (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_lob (member_info&); + + private: + string type_id_; + }; + } +} +#endif // ODB_RELATIONAL_ORACLE_COMMON_HXX diff --git a/odb/odb/relational/oracle/context.cxx b/odb/odb/relational/oracle/context.cxx new file mode 100644 index 0000000..12ce0aa --- /dev/null +++ b/odb/odb/relational/oracle/context.cxx @@ -0,0 +1,795 @@ +// file : odb/relational/oracle/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> +#include <sstream> + +#include <odb/sql-token.hxx> +#include <odb/sql-lexer.hxx> + +#include <odb/relational/oracle/context.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + namespace + { + struct type_map_entry + { + char const* const cxx_type; + char const* const db_type; + char const* const db_id_type; + bool const null; + }; + + type_map_entry type_map[] = + { + {"bool", "NUMBER(1)", 0, false}, + + {"char", "CHAR(1)", 0, false}, + {"signed char", "NUMBER(3)", 0, false}, + {"unsigned char", "NUMBER(3)", 0, false}, + + {"short int", "NUMBER(5)", 0, false}, + {"short unsigned int", "NUMBER(5)", 0, false}, + + {"int", "NUMBER(10)", 0, false}, + {"unsigned int", "NUMBER(10)", 0, false}, + + {"long int", "NUMBER(19)", 0, false}, + {"long unsigned int", "NUMBER(20)", 0, false}, + + {"long long int", "NUMBER(19)", 0, false}, + {"long long unsigned int", "NUMBER(20)", 0, false}, + + {"float", "BINARY_FLOAT", 0, false}, + {"double", "BINARY_DOUBLE", 0, false}, + + // Oracle treats empty VARCHAR2 (and NVARCHAR2) strings as NULL. + // + {"::std::string", "VARCHAR2(512)", 0, true}, + + {"::size_t", "NUMBER(20)", 0, false}, + {"::std::size_t", "NUMBER(20)", 0, false} + }; + } + + context* context::current_; + + context:: + ~context () + { + if (current_ == this) + current_ = 0; + } + + context:: + context (ostream& os, + semantics::unit& u, + options_type const& ops, + features_type& f, + sema_rel::model* m) + : root_context (os, u, ops, f, data_ptr (new (shared) data (os))), + base_context (static_cast<data*> (root_context::data_.get ()), m), + data_ (static_cast<data*> (base_context::data_)) + { + assert (current_ == 0); + current_ = this; + + generate_grow = false; + need_alias_as = false; + insert_send_auto_id = false; + delay_freeing_statement_result = false; + need_image_clone = true; + generate_bulk = true; + global_index = true; + global_fkey = true; + data_->bind_vector_ = "oracle::bind*"; + + // Populate the C++ type to DB type map. + // + for (size_t i (0); i < sizeof (type_map) / sizeof (type_map_entry); ++i) + { + type_map_entry const& e (type_map[i]); + + type_map_type::value_type v ( + e.cxx_type, + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); + + data_->type_map_.insert (v); + } + } + + context:: + context () + : data_ (current ().data_) + { + } + + string const& context:: + convert_expr (string const& sqlt, semantics::data_member& m, bool to) + { + sql_type const& t (parse_sql_type (sqlt, m)); + return to ? t.to : t.from; + } + + string context:: + quote_id_impl (qname const& id) const + { + string r; + + bool f (true); + for (qname::iterator i (id.begin ()); i < id.end (); ++i) + { + if (i->empty ()) + continue; + + if (f) + f = false; + else + r += '.'; + + r += '"'; + r.append (*i, 0, 30); // Max identifier length is 30. + r += '"'; + } + + return r; + } + + string context:: + database_type_impl (semantics::type& t, + semantics::names* hint, + bool id, + bool* null) + { + string r (base_context::database_type_impl (t, hint, id, null)); + + if (!r.empty ()) + return r; + + using semantics::array; + + // char[N] mapping. + // + if (array* a = dynamic_cast<array*> (&t)) + { + semantics::type& bt (a->base_type ()); + if (bt.is_a<semantics::fund_char> ()) + { + unsigned long long n (a->size ()); + + if (n == 0) + return r; + else if (n == 1) + r = "CHAR"; + else + { + r = "VARCHAR2"; + n--; + } + + // Oracle VARCHAR2 limit is 4000 bytes. Since there are no good + // alternatives (CLOB?), let the user specify the mapping. + // + if (n > 4000) + return ""; + + // Allow empty VARCHAR2 values. + // + if (null != 0 && r == "VARCHAR2") + *null = true; + + ostringstream ostr; + ostr << n; + r += '('; + r += ostr.str (); + r += ')'; + } + } + + return r; + } + + bool context:: + unsigned_integer (semantics::type& t) + { + semantics::type* wt (wrapper (t)); + const string& s ((wt == 0 ? t : utype (*wt)).name ()); + + return s == "bool" || + s == "unsigned char" || + s == "short unsigned int" || + s == "unsigned int" || + s == "long unsigned int" || + s == "long long unsigned int"; + } + + qname context:: + sequence_name (qname const& table) + { + string n; + + if (options.sequence_suffix ().count (db) != 0) + n = table.uname () + options.sequence_suffix ()[db]; + else + n = compose_name (table.uname (), "seq"); + + n = transform_name (n, sql_name_sequence); + + qname r (table.qualifier ()); + r.append (n); + return r; + } + + // + // SQL type parsing. + // + + sql_type const& context:: + parse_sql_type (string const& t, semantics::data_member& m, bool custom) + { + // If this proves to be too expensive, we can maintain a cache of + // parsed types across contexts. + // + data::sql_type_cache::iterator i (data_->sql_type_cache_.find (t)); + + if (i != data_->sql_type_cache_.end () + && (custom ? i->second.custom_cached : i->second.straight_cached)) + { + return (custom ? i->second.custom : i->second.straight); + } + else + { + try + { + sql_type st ( + parse_sql_type ( + t, + custom ? &unit.get<custom_db_types> ("custom-db-types") : 0)); + + if (custom) + return data_->sql_type_cache_[t].cache_custom (st); + else + return data_->sql_type_cache_[t].cache_straight (st); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } + } + } + + inline sql_type + error (bool fail, string const& m) + { + if (!fail) + return sql_type (); + else + throw context::invalid_sql_type (m); + } + + sql_type context:: + parse_sql_type (string sqlt, custom_db_types const* ct) + { + try + { + sql_type r; + + // First run the type through the custom mapping, if requested. + // + if (ct != 0) + { + for (custom_db_types::const_iterator i (ct->begin ()); + i != ct->end (); ++i) + { + custom_db_type const& t (*i); + + if (t.type.match (sqlt)) + { + r.to = t.type.replace (sqlt, t.to); + r.from = t.type.replace (sqlt, t.from); + sqlt = t.type.replace (sqlt, t.as); + break; + } + } + } + + sql_lexer l (sqlt); + + // While most type names use single identifier, there are + // a couple of exceptions to this rule: + // + // CHARACTER VARYING (VARCHAR2) + // CHAR VARYING (VARCHAR2) + // NATIONAL CHARACTER (NCHAR) + // NATIONAL CHAR (NCHAR) + // NCHAR VARYING (NVARCHAR2) + // NATIONAL CHARACTER VARYING (NVARCHAR2) + // NATIONAL CHAR VARYING (NVARCHAR2) + // NCHAR VARYING (NVARCHAR2) + // DOUBLE PRECISION (FLOAT(126)) + // INTERVAL YEAR TO MONTH + // INTERVAL DAY TO SECOND + // + enum state + { + parse_identifier, + parse_prec, + parse_done + }; + + state s (parse_identifier); + string prefix; + sql_token t (l.next ()); + + while (t.type () != sql_token::t_eos) + { + sql_token::token_type tt (t.type ()); + + switch (s) + { + case parse_identifier: + { + if (tt == sql_token::t_identifier) + { + string const& id (context::upcase (t.identifier ())); + + // + // Numeric types. + // + if ((id == "NUMBER") && prefix.empty ()) + { + // If NUMBER has no precision/scale, then it is a floating- + // point number. We indicate this by having no precision. + // + r.type = sql_type::NUMBER; + s = parse_prec; + } + else if ((id == "DEC" || id == "DECIMAL" || id == "NUMERIC") + && prefix.empty ()) + { + // DEC, DECIMAL, and NUMERIC are equivalent to NUMBER in + // all ways except that they may not represent a floating + // point number. The scale defaults to zero. + // + r.type = sql_type::NUMBER; + s = parse_prec; + } + else if ((id == "INT" || id == "INTEGER" || id == "SMALLINT") + && prefix.empty ()) + { + // INT, INTEGER, and SMALLINT map to NUMBER(38). They may not + // have precision or scale explicitly specified. + // + r.type = sql_type::NUMBER; + r.prec = true; + r.prec_value = 38; + + s = parse_done; + } + // + // Floating point types. + // + else if (id == "FLOAT" && prefix.empty ()) + { + r.type = sql_type::FLOAT; + r.prec = true; + r.prec_value = 126; + + s = parse_prec; + } + else if (id == "DOUBLE" && prefix.empty ()) + { + prefix = id; + } + else if (id == "PRECISION" && prefix == "DOUBLE") + { + r.type = sql_type::FLOAT; + r.prec = true; + r.prec_value = 126; + + s = parse_done; + } + else if (id == "REAL" && prefix.empty ()) + { + r.type = sql_type::FLOAT; + r.prec = true; + r.prec_value = 63; + + s = parse_done; + } + else if (id == "BINARY_FLOAT" && prefix.empty ()) + { + r.type = sql_type::BINARY_FLOAT; + s = parse_done; + } + else if (id == "BINARY_DOUBLE" && prefix.empty ()) + { + r.type = sql_type::BINARY_DOUBLE; + s = parse_done; + } + // + // Date-time types. + // + else if (id == "DATE" && prefix.empty ()) + { + r.type = sql_type::DATE; + s = parse_done; + } + else if (id == "TIMESTAMP" && prefix.empty ()) + { + prefix = id; + } + else if (id == "INTERVAL" && prefix.empty ()) + { + prefix = id; + } + else if (id == "YEAR" && prefix == "INTERVAL") + { + prefix += " "; + prefix += id; + + r.prec = true; + r.prec_value = 2; + s = parse_prec; + } + else if (id == "DAY" && prefix == "INTERVAL") + { + prefix += " "; + prefix += id; + + r.prec = true; + r.prec_value = 2; + s = parse_prec; + } + else if (id == "TO" && + (prefix == "INTERVAL YEAR" || + prefix == "INTERVAL DAY")) + { + prefix += " "; + prefix += id; + } + else if (id == "MONTH" && prefix == "INTERVAL YEAR TO") + { + r.type = sql_type::INTERVAL_YM; + s = parse_done; + } + else if (id == "SECOND" && prefix == "INTERVAL DAY TO") + { + r.type = sql_type::INTERVAL_DS; + + // Store seconds precision in scale since prec holds + // the days precision. + // + r.scale = true; + r.scale_value = 6; + s = parse_prec; + } + // + // Timestamp with time zone (not supported). + // + else if (id == "WITH" && prefix == "TIMESTAMP") + { + prefix += " "; + prefix += id; + } + else if (id == "TIME" && + (prefix == "TIMESTAMP WITH" || + prefix == "TIMESTAMP WITH LOCAL")) + { + prefix += " "; + prefix += id; + } + else if (id == "LOCAL" && prefix == "TIMESTAMP WITH") + { + prefix += " "; + prefix += id; + } + else if (id == "ZONE" && + (prefix == "TIMESTAMP WITH LOCAL TIME" || + prefix == "TIMESTAMP WITH TIME")) + { + return error (ct, "Oracle timestamps with time zones are " + "not currently supported"); + } + // + // String and binary types. + // + else if (id == "CHAR") + { + prefix += prefix.empty () ? "" : " "; + prefix += id; + } + else if (id == "CHARACTER") + { + prefix += prefix.empty () ? "" : " "; + prefix += id; + } + else if (id == "NCHAR") + { + prefix += prefix.empty () ? "" : " "; + prefix += id; + } + else if (id == "VARCHAR" || id == "VARCHAR2") + { + // VARCHAR is currently mapped to VARCHAR2 in Oracle server. + // However, this may change in future versions. + // + r.type = sql_type::VARCHAR2; + r.byte_semantics = true; + s = parse_prec; + } + else if (id == "NVARCHAR2") + { + r.type = sql_type::NVARCHAR2; + r.byte_semantics = false; + s = parse_prec; + } + else if (id == "VARYING") + { + // VARYING always appears at the end of an identifier. + // + if (prefix == "CHAR" || prefix == "CHARACTER") + { + r.type = sql_type::VARCHAR2; + r.byte_semantics = true; + } + else if (prefix == "NCHAR" || + prefix == "NATIONAL CHAR" || + prefix == "NATIONAL CHARACTER") + { + r.type = sql_type::NVARCHAR2; + r.byte_semantics = false; + } + + s = parse_prec; + } + else if (id == "NATIONAL" && prefix.empty ()) + { + prefix = id; + } + else if (id == "RAW" && prefix.empty ()) + { + r.type = sql_type::RAW; + s = parse_prec; + } + // + // LOB types. + // + else if (id == "BLOB" && prefix.empty ()) + { + r.type = sql_type::BLOB; + s = parse_done; + } + else if (id == "CLOB" && prefix.empty ()) + { + r.type = sql_type::CLOB; + s = parse_done; + } + else if (id == "NCLOB" && prefix.empty ()) + { + r.type = sql_type::NCLOB; + s = parse_done; + } + // + // LONG types. + // + else if (id == "LONG") + return error (ct, "Oracle LONG types are not supported"); + else + return error (ct, "unknown Oracle type '" + + t.identifier () + "'"); + + t = l.next (); + continue; + } + else if (!prefix.empty ()) + { + // Some prefixes can also be type names if not followed + // by the actual type name. + // + + if (prefix == "CHAR" || prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + r.byte_semantics = true; + r.prec = true; + r.prec_value = 1; + } + else if (prefix == "NCHAR" || + prefix == "NATIONAL CHAR" || + prefix == "NATIONAL CHARACTER") + { + r.type = sql_type::NCHAR; + r.byte_semantics = false; + r.prec = true; + r.prec_value = 1; + } + else if (prefix == "TIMESTAMP") + { + r.type = sql_type::TIMESTAMP; + r.prec = true; + r.prec_value = 6; + } + else + return error (ct, "incomplete Oracle type declaration: '" + + prefix + "'"); + + // All of the possible types handled in this block can take + // an optional precision specifier. Set the state and fall + // through to the parse_prec handler. + // + s = parse_prec; + } + else + { + assert (r.type == sql_type::invalid); + return error (ct, "unexepected '" + t.literal () + + "' in Oracle type declaration"); + } + } + // Fall through. + case parse_prec: + { + if (t.punctuation () == sql_token::p_lparen) + { + t = l.next (); + + if (t.type () != sql_token::t_int_lit) + { + return error (ct, "integer size/precision expected in " + "Oracle type declaration"); + } + + // Parse the precision. + // + { + unsigned short v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + return error (ct, "invalid prec value '" + t.literal () + + "' in Oracle type declaration"); + } + + // Store seconds precision in scale since prec holds + // the days precision for INTERVAL DAY TO SECOND. + // + if (r.type == sql_type::INTERVAL_DS) + { + r.scale = true; + r.scale_value = static_cast<short> (v); + } + else + { + r.prec = true; + r.prec_value = v; + } + + t = l.next (); + } + + // Parse the scale if present. + // + if (t.punctuation () == sql_token::p_comma) + { + // Scale can only be specified for NUMBER. + // + if (r.type != sql_type::NUMBER) + { + return error (ct, "invalid scale in Oracle type " + "declaration"); + } + + t = l.next (); + + if (t.type () != sql_token::t_int_lit) + { + return error (ct, "integer scale expected in Oracle type " + "declaration"); + } + + short v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + return error (ct, "invalid scale value '" + t.literal () + + "' in Oracle type declaration"); + } + + r.scale = true; + r.scale_value = v; + + t = l.next (); + } + else if (t.type () == sql_token::t_identifier) + { + const string& id (context::upcase (t.identifier ())); + + if (id == "CHAR") + r.byte_semantics = false; + else if (id != "BYTE") + { + return error (ct, "invalid keyword '" + t.literal () + + "' in Oracle type declaration"); + } + + t = l.next (); + } + + if (t.punctuation () != sql_token::p_rparen) + { + return error (ct, "expected ')' in Oracle type declaration"); + } + else + t = l.next (); + } + + s = r.type == sql_type::invalid ? parse_identifier : parse_done; + continue; + } + case parse_done: + { + return error (ct, "unexepected '" + t.literal () + "' in Oracle " + "type declaration"); + break; + } + } + } + + // Some prefixes can also be type names if not followed by the actual + // type name. + // + if (r.type == sql_type::invalid) + { + if (!prefix.empty ()) + { + if (prefix == "CHAR" || prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + r.byte_semantics = true; + r.prec = true; + r.prec_value = 1; + } + else if (prefix == "NCHAR" || + prefix == "NATIONAL CHAR" || + prefix == "NATIONAL CHARACTER") + { + r.type = sql_type::NCHAR; + r.byte_semantics = false; + r.prec = true; + r.prec_value = 1; + } + else if (prefix == "TIMESTAMP") + { + r.type = sql_type::TIMESTAMP; + r.prec = true; + r.prec_value = 6; + } + else + return error (ct, "incomplete Oracle type declaration: '" + + prefix + "'"); + } + else + return error (ct, "invalid Oracle type declaration"); + } + + return r; + } + catch (sql_lexer::invalid_input const& e) + { + return error (ct, "invalid Oracle type declaration: " + e.message); + } + } + } +} diff --git a/odb/odb/relational/oracle/context.hxx b/odb/odb/relational/oracle/context.hxx new file mode 100644 index 0000000..6c55853 --- /dev/null +++ b/odb/odb/relational/oracle/context.hxx @@ -0,0 +1,188 @@ +// file : odb/relational/oracle/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_ORACLE_CONTEXT_HXX +#define ODB_RELATIONAL_ORACLE_CONTEXT_HXX + +#include <map> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace oracle + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Numeric types. + // + NUMBER, + FLOAT, + + // Floating point types. + // + BINARY_FLOAT, + BINARY_DOUBLE, + + // Date-time types. + // + DATE, + TIMESTAMP, + INTERVAL_YM, + INTERVAL_DS, + + // String and binary types. + // + CHAR, + NCHAR, + VARCHAR2, + NVARCHAR2, + RAW, + + // LOB types. + // + BLOB, + CLOB, + NCLOB, + + // Invalid type. + // + invalid + }; + + sql_type () : + type (invalid), prec (false), scale (false), byte_semantics (true) + { + } + + core_type type; + + bool prec; + unsigned short prec_value; // Oracle max value is 4000. + + bool scale; + short scale_value; // Oracle min value is -84. Max value is 127. + + bool byte_semantics; + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string, custom_db_types const* = 0); + + public: + // If necessary, unwraps. + // + static bool + unsigned_integer (semantics::type&); + + public: + // Construct sequence name from a given table name. + // + qname + sequence_name (qname const& table); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual string + quote_id_impl (qname const&) const; + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_ORACLE_CONTEXT_HXX diff --git a/odb/odb/relational/oracle/header.cxx b/odb/odb/relational/oracle/header.cxx new file mode 100644 index 0000000..bf50bb2 --- /dev/null +++ b/odb/odb/relational/oracle/header.cxx @@ -0,0 +1,230 @@ +// file : odb/relational/oracle/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +namespace relational +{ + namespace oracle + { + namespace header + { + namespace relational = relational::header; + + struct image_type: relational::image_type, context + { + image_type (base const& x): base (x) {}; + + virtual void + image_extra (type& c) + { + if (!(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + + // If this is a polymorphic type, only add callback to the root. + // + if (poly_root == 0 || poly_root == &c) + { + bool gc (options.generate_query ()); + + if (gc) + os << "oracle::change_callback change_callback_;" + << endl; + + os << "oracle::change_callback*" << endl + << "change_callback ()" + << "{"; + + if (gc) + os << "return &change_callback_;"; + else + os << "return 0;"; + + os << "}"; + } + } + } + }; + entry<image_type> image_type_; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_int32 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_int64 (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_big_int (member_info& mi) + { + // Each significant base-100 digit requires a byte of storage + // in the manitissa. The default precision is 38 decimal digits, + // which is equivalent to 19 base-100 digits. + // + size_t n (19); + + if (mi.st->prec) + n = mi.st->prec_value / 2 + mi.st->prec_value % 2; + + // We require an additional byte for each of the exponent and + // negative value terminator values. + // + n += 2; + + os << "char " << mi.var << "value[" << n << "];" + << "ub2 " << mi.var << "size;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_double (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_big_float (member_info& mi) + { + // big_float is mapped to the OCI type SQLT_NUM, which requires 21 + // bytes of storage. + // + os << "char " << mi.var << "value[21];" + << "ub2 " << mi.var << "size;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << "char " << mi.var << "value[7];" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_timestamp (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_interval_ym (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_interval_ds (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + size_t n (mi.st->prec ? mi.st->prec_value : 1); + + // National characters can be either UTF-8 or UTF-16 encoded, + // both of which have a maximum character encoding size of 4 + // bytes. Database character set can also be UTF-8 so if the + // size is specified in characters, then conservatively assume + // each character can take up to 4 bytes. + // + if (!mi.st->byte_semantics) // N*CHAR always has CHAR semantics. + n *= 4; + + if (mi.st->type == sql_type::VARCHAR2 || + mi.st->type == sql_type::NVARCHAR2) + n = n > 4000 ? 4000 : n; + else + n = n > 2000 ? 2000 : n; + + os << "char " << mi.var << "value[" << n << "];" + << "ub2 " << mi.var << "size;" + << "sb2 " << mi.var << "indicator;" + << endl; + } + + virtual void + traverse_lob (member_info& mi) + { + os << "mutable " << image_type << " " << mi.var << "callback;" + << "sb2 " << mi.var << "indicator;" + << "oracle::lob " << mi.var << "lob;" + << endl; + } + }; + entry<image_member> image_member_; + + struct class1: relational::class1 + { + class1 (base const& x): base (x) {} + + virtual void + object_public_extra_pre (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (poly_derived || (abst && !poly)) + return; + + // Bulk operations batch size. + // + { + unsigned long long b (c.count ("bulk") + ? c.get<unsigned long long> ("bulk") + : 1); + + os << "static const std::size_t batch = " << b << "UL;" + << endl; + } + } + }; + entry<class1> class1_entry_; + } + } +} diff --git a/odb/odb/relational/oracle/inline.cxx b/odb/odb/relational/oracle/inline.cxx new file mode 100644 index 0000000..1b6d606 --- /dev/null +++ b/odb/odb/relational/oracle/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/oracle/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "indicator == -1;"; + else + os << "i." << mi.var << "indicator = -1;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/oracle/model.cxx b/odb/odb/relational/oracle/model.cxx new file mode 100644 index 0000000..b65e201 --- /dev/null +++ b/odb/odb/relational/oracle/model.cxx @@ -0,0 +1,64 @@ +// file : odb/relational/oracle/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to Oracle NUMBER. + // + sql_type const& t (parse_sql_type (column_type (), m, false)); + if (t.type != sql_type::NUMBER) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to Oracle NUMBER" << endl; + + throw operation_failed (); + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); + + ostringstream ostr; + + if (e.enum_ ().unsigned_ ()) + ostr << e.value (); + else + ostr << static_cast<long long> (e.value ()); + + return ostr.str (); + } + + virtual void + primary_key (sema_rel::primary_key& pk) + { + if (pk.auto_ ()) + pk.extra ()["sequence"] = sequence_name (table_.name ()).string (); + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/odb/relational/oracle/schema.cxx b/odb/odb/relational/oracle/schema.cxx new file mode 100644 index 0000000..75100b1 --- /dev/null +++ b/odb/odb/relational/oracle/schema.cxx @@ -0,0 +1,696 @@ +// file : odb/relational/oracle/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <map> +#include <utility> // pair + +#include <odb/diagnostics.hxx> + +#include <odb/relational/schema.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +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> 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> 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> 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> 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<sema_rel::table&> (in.scope ())); + sema_rel::qname n (t.name ().qualifier ()); + n.append (in.name ()); + return quote_id (n); + } + }; + entry<drop_index> 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<primary_key&> (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> 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 <typename N> + struct scope + { + typedef std::map<N, pair<N, location> > 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<typename map::iterator, bool> 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<sema_rel::qname> tables; + scope<sema_rel::uname> fkeys; // Global but can't have schema. + scope<sema_rel::qname> indexes; + scope<sema_rel::qname> sequences; + scope<sema_rel::uname> 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<scopes*> (context::extra)) + s->columns.check (c.get<location> ("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> 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<scopes*> (context::extra)) + s->fkeys.check (fk.get<location> ("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<scopes*> (context::extra)) + s->fkeys.check (fk.get<location> ("cxx-location"), fk.name ()); + + os << endl + << " ADD CONSTRAINT "; + create (fk); + } + }; + entry<create_foreign_key> 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<sema_rel::table&> (in.scope ())); + sema_rel::qname n (t.name ().qualifier ()); + n.append (in.name ()); + + // Check name trunction and conflicts. + // + if (scopes* s = static_cast<scopes*> (context::extra)) + s->indexes.check (in.get<location> ("cxx-location"), n); + + return quote_id (n); + } + }; + entry<create_index> 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<scopes*> (context::extra)) + { + if (pass_ == 1) + { + s->tables.check (t.get<location> ("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<primary_key&> (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<scopes*> (context::extra)) + s->sequences.check (pk->get<location> ("cxx-location"), n); + + pre_statement (); + os_ << "CREATE SEQUENCE " << quote_id (n) << endl + << " START WITH 1 INCREMENT BY 1" << endl; + post_statement (); + } + } + } + }; + entry<create_table> 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> 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> 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<sema_rel::drop_foreign_key> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()); + + instance<drop_foreign_key> dfc (*this); + trav_rel::unames n (*dfc); + names (at, n); + os << endl; + + post_statement (); + } + + if (check<sema_rel::add_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD ("; + + instance<create_column> 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<alter_column> ac (*this, tl); + trav_rel::unames n (*ac); + names (at, n); + os << ")" << endl; + + post_statement (); + } + } + }; + entry<alter_table_pre> 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<sema_rel::drop_column> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " DROP ("; + + instance<drop_column> 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<alter_column> ac (*this, fl); + trav_rel::unames n (*ac); + names (at, n); + os << ")" << endl; + + post_statement (); + } + + if (check<sema_rel::add_foreign_key> (at)) + { + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()); + + instance<create_foreign_key> cfc (*this); + trav_rel::unames n (*cfc); + names (at, n); + os << endl; + + post_statement (); + } + } + }; + entry<alter_table_post> 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> version_table_; + } + } +} diff --git a/odb/odb/relational/oracle/source.cxx b/odb/odb/relational/oracle/source.cxx new file mode 100644 index 0000000..adf9864 --- /dev/null +++ b/odb/odb/relational/oracle/source.cxx @@ -0,0 +1,646 @@ +// file : odb/relational/oracle/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/oracle/common.hxx> +#include <odb/relational/oracle/context.hxx> + +using namespace std; + +namespace relational +{ + namespace oracle + { + namespace source + { + namespace relational = relational::source; + + struct query_parameters: relational::query_parameters, context + { + query_parameters (base const& x): base (x), i_ (0) {} + + virtual string + next (semantics::data_member&, const string&, const string&) + { + ostringstream ss; + ss << ":" << ++i_; + + return ss.str (); + } + + virtual string + auto_id (semantics::data_member&, const string&, const string&) + { + return quote_id (sequence_name (table_)) + ".nextval"; + } + + private: + size_t i_; + }; + entry<query_parameters> query_parameters_; + + namespace + { + const char* string_buffer_types[] = + { + "oracle::bind::string", // CHAR + "oracle::bind::nstring", // NCHAR + "oracle::bind::string", // VARCHAR2 + "oracle::bind::nstring", // NVARCHAR2 + "oracle::bind::raw" // RAW + }; + + const char* lob_buffer_types[] = + { + "oracle::bind::blob", + "oracle::bind::clob", + "oracle::bind::nclob" + }; + } + + // + // bind + // + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_int32 (member_info& mi) + { + os << b << ".type = oracle::bind::" << + (unsigned_integer (mi.t) ? "uinteger" : "integer") << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".capacity = 4;" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_int64 (member_info& mi) + { + os << b << ".type = oracle::bind::" << + (unsigned_integer (mi.t) ? "uinteger" : "integer") << ";" + << b << ".buffer= &" << arg << "." << mi.var << "value;" + << b << ".capacity = 8;" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_big_int (member_info& mi) + { + os << b << ".type = oracle::bind::number;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = static_cast<ub4> (sizeof (" << arg << + "." << mi.var << "value));" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << b << ".type = oracle::bind::binary_float;" + << b << ".buffer= &" << arg << "." << mi.var << "value;" + << b << ".capacity = 4;" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_double (member_info& mi) + { + os << b << ".type = oracle::bind::binary_double;" + << b << ".buffer= &" << arg << "." << mi.var << "value;" + << b << ".capacity = 8;" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_big_float (member_info& mi) + { + os << b << ".type = oracle::bind::number;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = static_cast<ub4> (sizeof (" << arg << "." << + mi.var << "value));" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_date (member_info& mi) + { + os << b << ".type = oracle::bind::date;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = static_cast<ub4> (sizeof (" << arg << "." << + mi.var << "value));" + << b << ".size = 0;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_timestamp (member_info& mi) + { + os << b << ".type = oracle::bind::timestamp;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_interval_ym (member_info& mi) + { + os << b << ".type = oracle::bind::interval_ym;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_interval_ds (member_info& mi) + { + os << b << ".type = oracle::bind::interval_ds;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << b << ".type = " << + string_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = static_cast<ub4> (sizeof (" << arg << + "." << mi.var << "value));" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;"; + } + + virtual void + traverse_lob (member_info& mi) + { + os << b << ".type = " << + lob_buffer_types[mi.st->type - sql_type::BLOB] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "lob;" + << b << ".indicator = &" << arg << "." << mi.var << "indicator;" + << b << ".callback = &" << arg << "." << mi.var << "callback;" + << endl; + } + }; + entry<bind_member> bind_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + check_accessor (member_info& mi, member_access& ma) + { + // We cannot use accessors that return by-value for LOB + // members. + // + if ((mi.st->type == sql_type::BLOB || + mi.st->type == sql_type::CLOB || + mi.st->type == sql_type::NCLOB) && + ma.by_value) + { + error (ma.loc) << "accessor returning a value cannot be used " + << "for a data member of Oracle LOB type" << endl; + info (ma.loc) << "accessor returning a const reference is required" + << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "indicator = -1;"; + } + + virtual void + traverse_int32 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_int64 (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_big_int (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;" + << "i." << mi.var << "size = static_cast<ub2> (size);"; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_double (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_big_float (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;" + << "i." << mi.var << "size = static_cast<ub2> (size);"; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_timestamp (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_interval_ym (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_interval_ds (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;" + << "i." << mi.var << "size = static_cast<ub2> (size);"; + } + + virtual void + traverse_lob (member_info& mi) + { + os << "i." << mi.var << "lob.position = 0;" + << traits << "::set_image (" << endl + << "i." << mi.var << "callback.callback.param," << endl + << "i." << mi.var << "callback.context.param," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "indicator = is_null ? -1 : 0;"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "indicator == -1"; + } + + virtual void + check_modifier (member_info& mi, member_access& ma) + { + // We cannot use by-value modifier for LOB members. + // + if ((mi.st->type == sql_type::BLOB || + mi.st->type == sql_type::CLOB || + mi.st->type == sql_type::NCLOB) && + ma.placeholder ()) + { + error (ma.loc) << "modifier accepting a value cannot be used " + << "for a data member of Oracle LOB type" << endl; + info (ma.loc) << "modifier returning a non-const reference is " + << "required" << endl; + info (mi.m.location ()) << "data member is defined here" << endl; + throw operation_failed (); + } + } + + virtual void + traverse_int32 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_int64 (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_big_int (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_double (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_big_float (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_date (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_timestamp (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_interval_ym (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_interval_ds (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + + virtual void + traverse_lob (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "callback.callback.result," << endl + << "i." << mi.var << "callback.context.result," << endl + << "i." << mi.var << "indicator == -1);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct container_traits: relational::container_traits, context + { + container_traits (base const& x): base (x) {} + + virtual void + cache_result (string const&) + { + // Caching is not necessary since Oracle can execute several + // interleaving statements. + // + } + + virtual void + init_value_extra () + { + os << "sts.select_statement ().stream_result ();" + << endl; + } + }; + entry<container_traits> container_traits_; + + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + }; + entry<section_traits> section_traits_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual void + init_image_pre (type& c) + { + if (options.generate_query () && + !(composite (c) || (abstract (c) && !polymorphic (c)))) + { + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + if (poly_derived) + os << "{" + << "root_traits::image_type& ri (root_image (i));" + << endl; + + string i (poly_derived ? "ri" : "i"); + + os << "if (" << i << ".change_callback_.callback != 0)" << endl + << "(" << i << ".change_callback_.callback) (" << + i << ".change_callback_.context);"; + + if (poly_derived) + os << "}"; + else + os << endl; + } + } + + virtual void + init_value_extra () + { + os << "st.stream_result ();"; + } + + virtual string + persist_statement_extra (type& c, + relational::query_parameters& qp, + persist_position p) + { + string r; + + if (p == persist_after_values) + { + data_member_path* id (id_member (c)); + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // Top-level auto id. + // + if (id != 0 && !poly_derived && auto_ (*id)) + { + semantics::data_member& idb (*id->back ()); + + const string& name (column_qname (*id)); + const string& type (column_type (idb)); + + r = "RETURNING " + convert_from (name, type, idb) + + " INTO " + qp.next (idb, name, type); + } + } + + return r; + } + + virtual string + select_trailer (type& c) + { + view_query const& vq (c.get<view_query> ("query")); + + if (vq.for_update && vq.distinct) + { + error (vq.loc) + << "Oracle does not support FOR UPDATE with DISTINCT" << endl; + throw operation_failed (); + } + + return base::select_trailer (c); + } + }; + entry<class_> class_entry_; + } + } +} diff --git a/odb/odb/relational/pgsql/common.cxx b/odb/odb/relational/pgsql/common.cxx new file mode 100644 index 0000000..6a59954 --- /dev/null +++ b/odb/odb/relational/pgsql/common.cxx @@ -0,0 +1,351 @@ +// file : odb/relational/pgsql/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/pgsql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + switch (mi.st->type) + { + // Integral types. + // + case sql_type::BOOLEAN: + case sql_type::SMALLINT: + case sql_type::INTEGER: + case sql_type::BIGINT: + { + traverse_integer (mi); + break; + } + + // Float types. + // + case sql_type::REAL: + case sql_type::DOUBLE: + { + traverse_float (mi); + break; + } + case sql_type::NUMERIC: + { + traverse_numeric (mi); + break; + } + + // Data-time types. + // + case sql_type::DATE: + case sql_type::TIME: + case sql_type::TIMESTAMP: + { + traverse_date_time (mi); + break; + } + + // String and binary types. + // + case sql_type::CHAR: + case sql_type::VARCHAR: + case sql_type::TEXT: + case sql_type::BYTEA: + { + traverse_string (mi); + break; + } + case sql_type::BIT: + { + traverse_bit (mi); + break; + } + case sql_type::VARBIT: + { + traverse_varbit (mi); + break; + } + // Other types. + // + case sql_type::UUID: + { + traverse_uuid (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + namespace + { + const char* integer_types[] = + { + "bool", + "short", + "int", + "long long" + }; + + const char* float_types[] = + { + "float", + "double" + }; + + const char* date_time_types[] = + { + "int", + "long long", + "long long" + }; + } + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) + { + } + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_pgsql >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info& mi) + { + type_ += integer_types[mi.st->type - sql_type::BOOLEAN]; + } + + void member_image_type:: + traverse_float (member_info& mi) + { + type_ = float_types[mi.st->type - sql_type::REAL]; + } + + void member_image_type:: + traverse_numeric (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_date_time (member_info& mi) + { + type_ = date_time_types[mi.st->type - sql_type::DATE]; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_bit (member_info&) + { + type_ = "unsigned char*"; + } + + void member_image_type:: + traverse_varbit (member_info&) + { + type_ = "details::ubuffer"; + } + + void member_image_type:: + traverse_uuid (member_info&) + { + type_ = "unsigned char*"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + namespace + { + const char* integer_database_id[] = + { + "id_boolean", + "id_smallint", + "id_integer", + "id_bigint" + }; + + const char* float_database_id[] = + { + "id_real", + "id_double" + }; + + const char* date_time_database_id[] = + { + "id_date", + "id_time", + "id_timestamp" + }; + + const char* char_bin_database_id[] = + { + "id_string", // CHAR + "id_string", // VARCHAR + "id_string", // TEXT, + "id_bytea" // BYTEA + }; + } + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) {} + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) {} + + string member_database_type_id:: + database_type_id (type& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_integer (member_info& mi) + { + type_id_ = string ("pgsql::") + + integer_database_id[mi.st->type - sql_type::BOOLEAN]; + } + + void member_database_type_id:: + traverse_float (member_info& mi) + { + type_id_ = string ("pgsql::") + + float_database_id[mi.st->type - sql_type::REAL]; + } + + void member_database_type_id:: + traverse_numeric (member_info&) + { + type_id_ = "pgsql::id_numeric"; + } + + void member_database_type_id:: + traverse_date_time (member_info& mi) + { + type_id_ = string ("pgsql::") + + date_time_database_id[mi.st->type - sql_type::DATE]; + } + + void member_database_type_id:: + traverse_string (member_info& mi) + { + type_id_ = string ("pgsql::") + + char_bin_database_id[mi.st->type - sql_type::CHAR]; + } + + void member_database_type_id:: + traverse_bit (member_info&) + { + type_id_ = "pgsql::id_bit"; + } + + void member_database_type_id:: + traverse_varbit (member_info&) + { + type_id_ = "pgsql::id_varbit"; + } + + void member_database_type_id:: + traverse_uuid (member_info&) + { + type_id_ = "pgsql::id_uuid"; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/pgsql/common.hxx b/odb/odb/relational/pgsql/common.hxx new file mode 100644 index 0000000..1d383bf --- /dev/null +++ b/odb/odb/relational/pgsql/common.hxx @@ -0,0 +1,159 @@ +// file : odb/relational/pgsql/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_PGSQL_COMMON_HXX +#define ODB_RELATIONAL_PGSQL_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +namespace relational +{ + namespace pgsql + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the pgsql namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_integer (member_info&) + { + } + + virtual void + traverse_float (member_info&) + { + } + + virtual void + traverse_numeric (member_info&) + { + } + + virtual void + traverse_date_time (member_info&) + { + } + + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_bit (member_info&) + { + } + + virtual void + traverse_varbit (member_info&) + { + } + + virtual void + traverse_uuid (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_numeric (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_varbit (member_info&); + + virtual void + traverse_uuid (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (type&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_float (member_info&); + + virtual void + traverse_numeric (member_info&); + + virtual void + traverse_date_time (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_bit (member_info&); + + virtual void + traverse_varbit (member_info&); + + virtual void + traverse_uuid (member_info&); + + private: + string type_id_; + }; + } +} +#endif // ODB_RELATIONAL_PGSQL_COMMON_HXX diff --git a/odb/odb/relational/pgsql/context.cxx b/odb/odb/relational/pgsql/context.cxx new file mode 100644 index 0000000..7f99f5d --- /dev/null +++ b/odb/odb/relational/pgsql/context.cxx @@ -0,0 +1,786 @@ +// file : odb/relational/pgsql/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> +#include <sstream> + +#include <odb/diagnostics.hxx> + +#include <odb/sql-token.hxx> +#include <odb/sql-lexer.hxx> + +#include <odb/relational/pgsql/context.hxx> +#include <odb/relational/pgsql/common.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + namespace + { + struct type_map_entry + { + char const* const cxx_type; + char const* const db_type; + char const* const db_id_type; + bool const null; + }; + + type_map_entry type_map[] = + { + {"bool", "BOOLEAN", 0, false}, + + {"char", "CHAR(1)", 0, false}, + {"signed char", "SMALLINT", 0, false}, + {"unsigned char", "SMALLINT", 0, false}, + + {"short int", "SMALLINT", 0, false}, + {"short unsigned int", "SMALLINT", 0, false}, + + {"int", "INTEGER", 0, false}, + {"unsigned int", "INTEGER", 0, false}, + + {"long int", "BIGINT", 0, false}, + {"long unsigned int", "BIGINT", 0, false}, + + {"long long int", "BIGINT", 0, false}, + {"long long unsigned int", "BIGINT", 0, false}, + + {"float", "REAL", 0, false}, + {"double", "DOUBLE PRECISION", 0, false}, + + {"::std::string", "TEXT", 0, false}, + + {"::size_t", "BIGINT", 0, false}, + {"::std::size_t", "BIGINT", 0, false} + }; + } + + context* context::current_; + + context:: + ~context () + { + if (current_ == this) + current_ = 0; + } + + context:: + context (ostream& os, + semantics::unit& u, + options_type const& ops, + features_type& f, + sema_rel::model* m) + : root_context (os, u, ops, f, data_ptr (new (shared) data (os))), + base_context (static_cast<data*> (root_context::data_.get ()), m), + data_ (static_cast<data*> (base_context::data_)) + { + assert (current_ == 0); + current_ = this; + + generate_grow = true; + need_alias_as = true; + insert_send_auto_id = false; + delay_freeing_statement_result = false; + need_image_clone = false; + generate_bulk = true; + global_index = true; + global_fkey = false; + data_->bind_vector_ = "pgsql::bind*"; + data_->truncated_vector_ = "bool*"; + + // Populate the C++ type to DB type map. + // + for (size_t i (0); i < sizeof (type_map) / sizeof (type_map_entry); ++i) + { + type_map_entry const& e (type_map[i]); + + type_map_type::value_type v ( + e.cxx_type, + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); + + data_->type_map_.insert (v); + } + } + + context:: + context () + : data_ (current ().data_) + { + } + + namespace + { + struct has_grow: traversal::class_ + { + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + // Ignore transient bases. + // + if (!(context::object (c) || context::composite (c))) + return; + + if (section_ == 0 && c.count ("pgsql-grow")) + r_ = c.get<bool> ("pgsql-grow"); + else + { + // r_ should be false. + // + inherits (c); + + if (!r_) + names (c); + + if (section_ == 0) + c.set ("pgsql-grow", r_); + } + } + + private: + bool& r_; + user_section* section_; + traversal::inherits inherits_; + }; + + struct has_grow_member: member_base + { + has_grow_member (bool& r, user_section* section = 0) + : relational::member_base (0, 0, string (), string (), section), + r_ (r) {} + + has_grow_member (bool& r, + user_section* section, + semantics::type* t, + const custom_cxx_type* ct, + string const& key_prefix = string ()) + : relational::member_base (t, ct, string (), key_prefix, section), + r_ (r) {} + + virtual bool + pre (member_info& mi) + { + // If we have a key prefix (container), then it can't be in a + // section (while mi.m can). The same for top-level -- if we got + // called, then we shouldn't ignore it. + // + return !key_prefix_.empty () || top_level_ || + (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + + virtual void + traverse_composite (member_info& mi) + { + // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. + // + r_ = r_ || context::grow (dynamic_cast<semantics::class_&> (mi.t)); + } + + virtual void + traverse_numeric (member_info&) + { + r_ = true; + } + + virtual void + traverse_string (member_info&) + { + r_ = true; + } + + virtual void + traverse_varbit (member_info&) + { + r_ = true; + } + + private: + bool& r_; + }; + } + + bool context:: + grow_impl (semantics::class_& c, user_section* section) + { + if (section == 0 && c.count ("pgsql-grow")) + return c.get<bool> ("pgsql-grow"); + + bool r (false); + has_grow ct (r, section); + has_grow_member mt (r, section); + traversal::names names; + ct >> names >> mt; + ct.traverse (c); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m) + { + bool r (false); + has_grow_member mt (r); + mt.traverse (m, true); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m, + semantics::type& t, + const custom_cxx_type* ct, + string const& kp) + { + bool r (false); + has_grow_member mt (r, 0, &t, ct, kp); + mt.traverse (m, true); + return r; + } + + string const& context:: + convert_expr (string const& sqlt, semantics::data_member& m, bool to) + { + sql_type const& t (parse_sql_type (sqlt, m)); + return to ? t.to : t.from; + } + + string context:: + quote_id_impl (qname const& id) const + { + string r; + + bool f (true); + for (qname::iterator i (id.begin ()); i < id.end (); ++i) + { + if (i->empty ()) + continue; + + // Warn if the name is greater than the (NAMEDATALEN - 1) limit, + // which is 63 in the default PG build. + // + if (i->size () > 63) + { + cerr << "warning: SQL name '" << *i << "' is longer than " + << "the default PostgreSQL name limit of 63 characters " + << "and may be truncated" << endl; + + cerr << "info: consider shortening it using #pragma db " + << "table/column/index or --*-regex options" << endl; + } + + if (f) + f = false; + else + r += '.'; + + r += '"'; + r += *i; + r += '"'; + } + + return r; + } + + string context:: + statement_name (string const& type, string const& name, semantics::node& n) + { + // Put the type first so that in the case of truncation it + // remains thus lowering the chance of a clash. + // + string r (type); + r += '_'; + r += name; + + r = transform_name (r, sql_name_statement); + + // Warn if the name is greater than the (NAMEDATALEN - 1) limit, + // which is 63 in the default PG build. + // + // Note that we have to do it in addition to the above since this + // name doesn't go through quote_id(). + // + if (r.size () > 63) + { + location const& l (n.location ()); + + warn (l) << "prepared statement name '" << r << "' is longer than " + << "the default PostgreSQL name limit of 63 characters " + << "and may be truncated" << endl; + + info (l) << "consider shortening the corresponding namespace " + << "name, class name, or data member name" << endl; + + info (l) << "or shortening the statement name itself using the " + << "--statement-regex option" << endl; + } + + return r; + } + + string context:: + database_type_impl (semantics::type& t, + semantics::names* hint, + bool id, + bool* null) + { + string r (base_context::database_type_impl (t, hint, id, null)); + + if (!r.empty ()) + return r; + + using semantics::array; + + // char[N] mapping. + // + if (array* a = dynamic_cast<array*> (&t)) + { + semantics::type& bt (a->base_type ()); + if (bt.is_a<semantics::fund_char> ()) + { + unsigned long long n (a->size ()); + + if (n == 0) + return r; + else if (n == 1) + r = "CHAR("; + else + { + r = "VARCHAR("; + n--; + } + + ostringstream ostr; + ostr << n; + r += ostr.str (); + r += ')'; + } + } + + return r; + } + + // + // SQL type parsing. + // + + sql_type const& context:: + parse_sql_type (string const& t, semantics::data_member& m, bool custom) + { + // If this proves to be too expensive, we can maintain a cache of + // parsed types across contexts. + // + data::sql_type_cache::iterator i (data_->sql_type_cache_.find (t)); + + if (i != data_->sql_type_cache_.end () + && (custom ? i->second.custom_cached : i->second.straight_cached)) + { + return (custom ? i->second.custom : i->second.straight); + } + else + { + try + { + sql_type st ( + parse_sql_type ( + t, + custom ? &unit.get<custom_db_types> ("custom-db-types") : 0)); + + if (custom) + return data_->sql_type_cache_[t].cache_custom (st); + else + return data_->sql_type_cache_[t].cache_straight (st); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } + } + } + + inline sql_type + error (bool fail, string const& m) + { + if (!fail) + return sql_type (); + else + throw context::invalid_sql_type (m); + } + + sql_type context:: + parse_sql_type (string sqlt, custom_db_types const* ct) + { + try + { + sql_type r; + + // First run the type through the custom mapping, if requested. + // + if (ct != 0) + { + for (custom_db_types::const_iterator i (ct->begin ()); + i != ct->end (); ++i) + { + custom_db_type const& t (*i); + + if (t.type.match (sqlt)) + { + r.to = t.type.replace (sqlt, t.to); + r.from = t.type.replace (sqlt, t.from); + sqlt = t.type.replace (sqlt, t.as); + break; + } + } + } + + sql_lexer l (sqlt); + + // While most type names use single identifier, there are + // a couple of exceptions to this rule: + // + // BIT VARYING (VARBIT) + // CHARACTER VARYING (VARRCHAR) + // DOUBLE PRECISION (DOUBLE) + // TIME WITH TIME ZONE (not currently supported) + // TIMESTAMP WITH TIME ZONE (not currently supported) + // + + enum state + { + parse_prefix, + parse_name, + parse_range, + parse_suffix, + parse_done + }; + + state s (parse_prefix); + string prefix; + bool flt (false); + + for (sql_token t (l.next ()); + s != parse_done && t.type () != sql_token::t_eos; + t = l.next ()) + { + sql_token::token_type tt (t.type ()); + + switch (s) + { + case parse_prefix: + { + if (tt == sql_token::t_identifier) + { + string const& id (context::upcase (t.identifier ())); + + if (id == "BIT" || + id == "CHARACTER" || + id == "DOUBLE") + { + prefix = id; + s = parse_name; + continue; + } + } + + s = parse_name; + } + // Fall through. + case parse_name: + { + if (tt == sql_token::t_identifier) + { + bool match (true); + string const& id (context::upcase (t.identifier ())); + + // + // Numeric types. + // + if (id == "BOOL" || id == "BOOLEAN") + { + r.type = sql_type::BOOLEAN; + } + else if (id == "SMALLINT" || id == "INT2") + { + r.type = sql_type::SMALLINT; + } + else if (id == "INT" || + id == "INTEGER" || + id == "INT4") + { + r.type = sql_type::INTEGER; + } + else if (id == "BIGINT") + { + r.type = sql_type::BIGINT; + } + else if (id == "REAL" || id == "FLOAT4") + { + r.type = sql_type::REAL; + } + else if ((id == "PRECISION" && prefix == "DOUBLE") || + id == "FLOAT8") + { + r.type = sql_type::DOUBLE; + } + else if (id == "FLOAT") + { + // Assign a type only once we know the precision of the + // float. + // + flt = true; + } + else if (id == "NUMERIC" || id == "DECIMAL") + { + r.type = sql_type::NUMERIC; + } + // + // Date-time types. + // + else if (id == "DATE") + { + r.type = sql_type::DATE; + } + else if (id == "TIME") + { + r.type = sql_type::TIME; + } + else if (id == "TIMETZ") + { + return error (ct, "PostgreSQL time zones are not currently " + "supported"); + } + else if (id == "TIMESTAMP") + { + r.type = sql_type::TIMESTAMP; + } + else if (id == "TIMESTAMPTZ") + { + return error (ct, "PostgreSQL time zones are not currently " + "supported"); + } + // + // String and binary types. + // + else if (id == "CHAR") + { + r.type = sql_type::CHAR; + } + else if (id == "VARCHAR") + { + r.type = sql_type::VARCHAR; + } + else if (id == "TEXT") + { + r.type = sql_type::TEXT; + } + else if (id == "VARYING") + { + if (prefix == "BIT") + r.type = sql_type::VARBIT; + else if (prefix == "CHARACTER") + r.type = sql_type::VARCHAR; + } + else if (id == "BYTEA") + { + r.type = sql_type::BYTEA; + } + else if (id == "VARBIT") + { + r.type = sql_type::VARBIT; + } + // + // Other types. + // + else if (id == "UUID") + { + r.type = sql_type::UUID; + } + else + match = false; + + if (match) + { + s = parse_range; + continue; + } + } + + // Some prefixes can also be type names if not followed + // by the actual type name. + // + if (!prefix.empty ()) + { + if (prefix == "BIT") + { + r.type = sql_type::BIT; + } + else if (prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + } + } + + if (r.type == sql_type::invalid) + { + return error ( + ct, + tt == sql_token::t_identifier + ? "unknown PostgreSQL type '" + t.identifier () + "'" + : "expected PostgreSQL type name"); + } + + s = parse_range; + } + // Fall through. + case parse_range: + { + if (t.punctuation () == sql_token::p_lparen) + { + t = l.next (); + + if (t.type () != sql_token::t_int_lit) + { + return error (ct, "integer range expected in PostgreSQL " + "type declaration"); + } + + unsigned int v; + istringstream is (t.literal ()); + + if (!(is >> v && is.eof ())) + { + return error (ct, "invalid range value '" + t.literal () + + "' in PostgreSQL type declaration"); + } + + r.range = true; + r.range_value = v; + + t = l.next (); + + if (t.punctuation () == sql_token::p_comma) + { + // We have the second range value. Skip it. + // + l.next (); + t = l.next (); + } + + if (t.punctuation () != sql_token::p_rparen) + { + return error (ct, "expected ')' in PostgreSQL type " + "declaration"); + } + + s = parse_suffix; + continue; + } + + s = parse_suffix; + } + // Fall through. + case parse_suffix: + { + if (r.type == sql_type::TIME || r.type == sql_type::TIMESTAMP) + { + string const& id1 (context::upcase (t.identifier ())); + + if (id1 == "WITH") + { + t = l.next (); + tt = t.type (); + + if (tt == sql_token::t_identifier) + { + string const& id2 (context::upcase (t.identifier ())); + + if (id2 == "TIME") + { + t = l.next (); + tt = t.type (); + + if (tt == sql_token::t_identifier) + { + string const& id3 (context::upcase (t.identifier ())); + + if (id3 == "ZONE") + { + // This code shall not fall through. + // + return error (ct, "PostgreSQL time zones are not " + "currently supported"); + } + } + } + } + } + } + + return error ( + ct, + tt == sql_token::t_identifier + ? "unknown PostgreSQL type '" + t.identifier () + "'" + : "unknown PostgreSQL type"); + } + case parse_done: + { + assert (false); + break; + } + } + } + + if (s == parse_name && !prefix.empty ()) + { + // Some prefixes can also be type names if not followed + // by the actual type name. + // + if (prefix == "BIT") + { + r.type = sql_type::BIT; + } + else if (prefix == "CHARACTER") + { + r.type = sql_type::CHAR; + } + } + + if (flt) + { + r.type = r.range && r.range_value < 25 ? + sql_type::REAL : + sql_type::DOUBLE; + } + + if (r.type == sql_type::invalid) + return error (ct, "incomplete PostgreSQL type declaration"); + + // If range is omitted for CHAR or BIT types, it defaults to 1. + // + if ((r.type == sql_type::CHAR || r.type == sql_type::BIT) && !r.range) + { + r.range = true; + r.range_value = 1; + } + + return r; + } + catch (sql_lexer::invalid_input const& e) + { + return error (ct, "invalid PostgreSQL type declaration: " + e.message); + } + } + } +} diff --git a/odb/odb/relational/pgsql/context.hxx b/odb/odb/relational/pgsql/context.hxx new file mode 100644 index 0000000..64e0b1a --- /dev/null +++ b/odb/odb/relational/pgsql/context.hxx @@ -0,0 +1,192 @@ +// file : odb/relational/pgsql/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_PGSQL_CONTEXT_HXX +#define ODB_RELATIONAL_PGSQL_CONTEXT_HXX + +#include <map> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace pgsql + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + // Integral types. + // + BOOLEAN, + SMALLINT, + INTEGER, + BIGINT, + + // Float types. + // + REAL, + DOUBLE, + NUMERIC, + + // Data-time types. + // + DATE, + TIME, + TIMESTAMP, + + // String and binary types. + // + CHAR, + VARCHAR, + TEXT, + BYTEA, + BIT, + VARBIT, + + // Other types. + // + UUID, + + // Invalid type. + // + invalid + }; + + sql_type () : type (invalid), range (false) {} + + core_type type; + + // VARBIT maximum length is 2^31 - 1 bit. String types can hold a + // maximum of 1GB of data. + // + bool range; + unsigned int range_value; + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string, custom_db_types const* = 0); + + public: + // Construct statement name from a given type and name. + // + string + statement_name (string const& type, + string const& name, + semantics::node&); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual string + quote_id_impl (qname const&) const; + + virtual bool + grow_impl (semantics::class_&, user_section*); + + virtual bool + grow_impl (semantics::data_member&); + + virtual bool + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&); + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type&, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_PGSQL_CONTEXT_HXX diff --git a/odb/odb/relational/pgsql/header.cxx b/odb/odb/relational/pgsql/header.cxx new file mode 100644 index 0000000..c3efc3e --- /dev/null +++ b/odb/odb/relational/pgsql/header.cxx @@ -0,0 +1,285 @@ +// file : odb/relational/pgsql/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +namespace relational +{ + namespace pgsql + { + namespace header + { + namespace relational = relational::header; + + struct class1: relational::class1 + { + class1 (base const& x): base (x) {} + + virtual void + object_public_extra_post (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (abst && !poly) + return; + + data_member_path* id (id_member (c)); + semantics::data_member* optimistic (context::optimistic (c)); + + column_count_type const& cc (column_count (c)); + + size_t update_columns ( + cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update); + + // Statement names. + // + os << "static const char persist_statement_name[];"; + + if (id != 0) + { + if (poly_derived) + os << "static const char* const find_statement_names[" << + (abst ? "1" : "depth") << "];"; + else + os << "static const char find_statement_name[];"; + + if (poly && !poly_derived) + os << "static const char find_discriminator_statement_name[];"; + + if (update_columns != 0) + os << "static const char update_statement_name[];"; + + os << "static const char erase_statement_name[];"; + + if (optimistic != 0) + os << "static const char optimistic_erase_statement_name[];"; + } + + // Query statement name. + // + if (options.generate_query ()) + os << "static const char query_statement_name[];" + << "static const char erase_query_statement_name[];"; + + os << endl; + + // Statement types. + // + os << "static const unsigned int persist_statement_types[];"; + + if (id != 0) + { + os << "static const unsigned int find_statement_types[];"; + + if (update_columns != 0) + os << "static const unsigned int update_statement_types[];"; + + if (optimistic != 0) + os << "static const unsigned int " << + "optimistic_erase_statement_types[];"; + } + + os << endl; + + if (poly_derived) + return; + + // Bulk operations batch size. + // + { + unsigned long long b (c.count ("bulk") + ? c.get<unsigned long long> ("bulk") + : 1); + + os << "static const std::size_t batch = " << b << "UL;" + << endl; + } + } + + virtual void + view_public_extra_post (type&) + { + // Statement names. + // + os << "static const char query_statement_name[];" + << endl; + } + }; + entry<class1> class1_entry_; + + struct container_traits: relational::container_traits, context + { + container_traits (base const& x): base (x) {} + + virtual void + container_public_extra_pre (semantics::data_member& m, + semantics::type& t) + { + if (!object (c_) || (abstract (c_) && !polymorphic (c_))) + return; + + bool smart (!inverse (m, "value") && !unordered (m) && + container_smart (t)); + + // Container statement names. + // + os << "static const char select_name[];" + << "static const char insert_name[];"; + + if (smart) + os << "static const char update_name[];"; + + os << "static const char delete_name[];" + << endl; + + // Container statement types. + // + os << "static const unsigned int insert_types[];"; + + if (smart) + os << "static const unsigned int update_types[];" + << "static const unsigned int delete_types[];"; + + os << endl; + } + }; + entry<container_traits> container_traits_; + + struct section_traits: relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_public_extra_post (user_section& s) + { + semantics::class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + + if (!poly && (abstract (c_) || + s.special == user_section::special_version)) + return; + + bool load (s.total != 0 && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + // Statement names. + // + if (load || load_opt) + os << "static const char select_name[];" + << endl; + + if (update || update_opt) + os << "static const char update_name[];" + << endl; + + // Statement types. + // + if (update || update_opt) + os << "static const unsigned int update_types[];"; + } + }; + entry<section_traits> section_traits_; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_numeric (member_info& mi) + { + // Exchanged as strings. Can have up to 1000 digits not counting + // '-' and '.'. + // + + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Additional 4 bytes at the beginning of the array specify + // the number of significant bits in the image. This number + // is stored in network byte order. + // + unsigned int n (4 + mi.st->range / 8 + (mi.st->range % 8 ? 1 : 0)); + + os << "unsigned char " << mi.var << "value[" << n << "];" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_varbit (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_uuid (member_info& mi) + { + // UUID is a 16-byte sequence. + // + os << "unsigned char " << mi.var << "value[16];" + << "bool " << mi.var << "null;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/pgsql/inline.cxx b/odb/odb/relational/pgsql/inline.cxx new file mode 100644 index 0000000..08688c3 --- /dev/null +++ b/odb/odb/relational/pgsql/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/pgsql/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "null;"; + else + os << "i." << mi.var << "null = true;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/pgsql/model.cxx b/odb/odb/relational/pgsql/model.cxx new file mode 100644 index 0000000..092f8bb --- /dev/null +++ b/odb/odb/relational/pgsql/model.cxx @@ -0,0 +1,101 @@ +// file : odb/relational/pgsql/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/diagnostics.hxx> + +#include <odb/relational/model.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual void + traverse_object (semantics::class_& c) + { + base::traverse_object (c); + + if (context::top_object == &c) + { + // Make sure that the auto id type is INTEGER or BIGINT. + // + if (pkey_ != 0 && pkey_->auto_ ()) + { + // Should be a single column. + // + sema_rel::column& c (pkey_->contains_begin ()->column ()); + + // This should never fail since we have already parsed this. + // + sql_type const& t (parse_sql_type (c.type ())); + + if (t.type != sql_type::INTEGER && t.type != sql_type::BIGINT) + { + location const& l (c.get<location> ("cxx-location")); + error (l) << "automatically assigned object id must map " + << "to PostgreSQL INTEGER or BIGINT" << endl; + throw operation_failed (); + } + } + } + } + + virtual string + default_bool (semantics::data_member&, bool v) + { + return v ? "TRUE" : "FALSE"; + } + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to an integer type. + // + switch (parse_sql_type (column_type (), m, false).type) + { + case sql_type::SMALLINT: + case sql_type::INTEGER: + case sql_type::BIGINT: + break; + default: + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to PostgreSQL integer type" << endl; + + throw operation_failed (); + } + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); + + ostringstream ostr; + + if (e.enum_ ().unsigned_ ()) + ostr << e.value (); + else + ostr << static_cast<long long> (e.value ()); + + return ostr.str (); + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/odb/relational/pgsql/schema.cxx b/odb/odb/relational/pgsql/schema.cxx new file mode 100644 index 0000000..b9c3f2e --- /dev/null +++ b/odb/odb/relational/pgsql/schema.cxx @@ -0,0 +1,266 @@ +// file : odb/relational/pgsql/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + namespace schema + { + namespace relational = relational::schema; + using relational::table_set; + + // + // Drop. + // + + struct drop_table: relational::drop_table, context + { + drop_table (base const& x): base (x) {} + + 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; + + pre_statement (); + os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") << + quote_id (t.name ()) << " CASCADE" << endl; + post_statement (); + } + }; + entry<drop_table> drop_table_; + + // + // Create. + // + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} + + virtual void + type (sema_rel::column& c, bool auto_) + { + if (auto_) + { + // This should never fail since we have already parsed this. + // + sql_type const& t (parse_sql_type (c.type ())); + + // The model creation code makes sure it is one of these type. + // + if (t.type == sql_type::INTEGER) + os << "SERIAL"; + else if (t.type == sql_type::BIGINT) + os << "BIGSERIAL"; + } + else + base::type (c, auto_); + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (base const& x): base (x) {} + + virtual void + deferrable (sema_rel::deferrable d) + { + os << endl + << " INITIALLY " << d; + } + }; + entry<create_foreign_key> create_foreign_key_; + + struct create_index: relational::create_index, context + { + create_index (base const& x): base (x) {} + + virtual void + create (sema_rel::index& in) + { + os << "CREATE "; + + if (!in.type ().empty ()) + { + // Handle the CONCURRENTLY keyword. + // + string const& t (in.type ()); + + if (t == "concurrently" || t == "CONCURRENTLY") + { + os << "INDEX " << t; + } + else + { + size_t p (t.rfind (' ')); + string s (t, (p != string::npos ? p + 1 : 0), string::npos); + + if (s == "concurrently" || s == "CONCURRENTLY") + os << string (t, 0, p) << " INDEX " << s; + else + os << t << " INDEX"; + } + } + else + os << "INDEX"; + + os << " " << name (in) << endl + << " ON " << table_name (in); + + if (!in.method ().empty ()) + os << " USING " << in.method (); + + os << " ("; + columns (in); + os << ")" << endl; + + if (!in.options ().empty ()) + os << ' ' << in.options () << endl; + } + }; + entry<create_index> create_index_; + + // + // Alter. + // + + struct alter_column: relational::alter_column, context + { + alter_column (base const& x): base (x) {} + + virtual void + alter (sema_rel::column& c) + { + os << quote_id (c.name ()) << " " << + (c.null () ? "DROP" : "SET") << " NOT NULL"; + } + }; + entry<alter_column> alter_column_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x): base (x) {} + + // PostgreSQL prior to 9.1 doesn't support IF NOT EXISTS in + // CREATE TABLE. We also cannot use IF-ELSE construct in plain + // SQL. To make it at least work for a single schema, we are + // going to drop the schema version table after the DROP + // statements and then unconditionally create it after CREATE. + // + virtual void + create_table () + { + if (options.pgsql_server_version () >= pgsql_version (9, 1)) + { + pre_statement (); + + os << "CREATE TABLE IF NOT EXISTS " << qt_ << " (" << endl + << " " << qn_ << " TEXT NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT NOT NULL," << endl + << " " << qm_ << " BOOLEAN NOT NULL)" << endl; + + post_statement (); + } + } + + virtual void + drop () + { + pre_statement (); + + if (options.pgsql_server_version () >= pgsql_version (9, 1)) + os << "DELETE FROM " << qt_ << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + else + os << "DROP TABLE IF EXISTS " << qt_ << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + if (options.pgsql_server_version () >= pgsql_version (9, 1)) + { + os << "INSERT INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " SELECT " << qs_ << ", " << v << ", FALSE" << endl + << " WHERE NOT EXISTS (" << endl + << " SELECT 1 FROM " << qt_ << " WHERE " << qn_ << " = " << + qs_ << ")" << endl; + } + else + { + os << "CREATE TABLE " << qt_ << " (" << endl + << " " << qn_ << " TEXT NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " BIGINT NOT NULL," << endl + << " " << qm_ << " BOOLEAN NOT NULL)" << endl; + + post_statement (); + pre_statement (); + + os << "INSERT INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", FALSE)" << endl; + } + + post_statement (); + } + + virtual void + migrate_pre (sema_rel::version v) + { + pre_statement (); + + os << "UPDATE " << qt_ << endl + << " SET " << qv_ << " = " << v << ", " << qm_ << " = TRUE" << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + virtual void + migrate_post () + { + pre_statement (); + + os << "UPDATE " << qt_ << endl + << " SET " << qm_ << " = FALSE" << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/pgsql/source.cxx b/odb/odb/relational/pgsql/source.cxx new file mode 100644 index 0000000..b881e48 --- /dev/null +++ b/odb/odb/relational/pgsql/source.cxx @@ -0,0 +1,1140 @@ +// file : odb/relational/pgsql/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/source.hxx> + +#include <odb/relational/pgsql/common.hxx> +#include <odb/relational/pgsql/context.hxx> + +using namespace std; + +namespace relational +{ + namespace pgsql + { + namespace source + { + namespace relational = relational::source; + + struct query_parameters: relational::query_parameters + { + query_parameters (base const& x): base (x), i_ (0) {} + + virtual string + next (semantics::data_member&, const string&, const string&) + { + ostringstream ss; + ss << "$" << ++i_; + + return ss.str (); + } + + virtual string + auto_id (semantics::data_member&, const string&, const string&) + { + return "DEFAULT"; + } + + private: + size_t i_; + }; + entry<query_parameters> query_parameters_; + + namespace + { + const char* integer_buffer_types[] = + { + "pgsql::bind::boolean_", + "pgsql::bind::smallint", + "pgsql::bind::integer", + "pgsql::bind::bigint" + }; + + const char* float_buffer_types[] = + { + "pgsql::bind::real", + "pgsql::bind::double_" + }; + + const char* char_bin_buffer_types[] = + { + "pgsql::bind::text", // CHAR + "pgsql::bind::text", // VARCHAR + "pgsql::bind::text", // TEXT + "pgsql::bind::bytea" // BYTEA + }; + + const char* date_time_buffer_types[] = + { + "pgsql::bind::date", + "pgsql::bind::time", + "pgsql::bind::timestamp" + }; + + const char* oids[] = + { + "pgsql::bool_oid", // BOOLEAN + "pgsql::int2_oid", // SMALLINT + "pgsql::int4_oid", // INTEGER + "pgsql::int8_oid", // BIGINT + "pgsql::float4_oid", // REAL + "pgsql::float8_oid", // DOUBLE + "pgsql::numeric_oid", // NUMERIC + "pgsql::date_oid", // DATE + "pgsql::time_oid", // TIME + "pgsql::timestamp_oid", // TIMESTAMP + "pgsql::text_oid", // CHAR + "pgsql::text_oid", // VARCHAR + "pgsql::text_oid", // TEXT + "pgsql::bytea_oid", // BYTEA + "pgsql::bit_oid", // BIT + "pgsql::varbit_oid", // VARBIT + "pgsql::uuid_oid" // UUID + }; + } + + struct statement_oids: object_columns_base, context + { + statement_oids (statement_kind sk, + bool first = true, + object_section* section = 0) + : object_columns_base (first, column_prefix (), section), sk_ (sk) + { + } + + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section for + // SELECT statements. + // + return section_ == 0 || + *section_ == s || + (sk_ == statement_select && + *section_ == main_section && + !s.separate_load ()); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore certain columns depending on what kind statement we are + // generating. See object_columns in common source generator for + // details. + // + if (!(inverse (m, key_prefix_) && sk_ != statement_select)) + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, + string const&, + bool first) + { + // Ignore certain columns depending on what kind statement we are + // generating. See object_columns in common source generator for + // details. + // + if (id ()) + { + if (sk_ == statement_update || + (sk_ == statement_insert && auto_ (m))) + return false; + } + + if (sk_ == statement_update && + readonly (member_path_, member_scope_)) + return false; + + if ((sk_ == statement_insert || sk_ == statement_update) && + version (m)) + return false; + + if (!first) + os << ',' << endl; + + os << oids[parse_sql_type (column_type (), m).type]; + + return true; + } + + private: + statement_kind sk_; + }; + + // + // bind + // + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_integer (member_info& mi) + { + os << b << ".type = " << + integer_buffer_types[mi.st->type - sql_type::BOOLEAN] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << b << ".type = " << + float_buffer_types[mi.st->type - sql_type::REAL] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_numeric (member_info& mi) + { + os << b << ".type = pgsql::bind::numeric;" + << b << ".buffer = " << arg << "." << mi.var << "value.data_ptr ();" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << b << ".type = " << + date_time_buffer_types[mi.st->type - sql_type::DATE] << ";" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << b << ".type = " << + char_bin_buffer_types[mi.st->type - sql_type::CHAR] << ";" + << b << ".buffer = " << arg << "." << mi.var << "value.data_ptr ();" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_bit (member_info& mi) + { + os << b << ".type = pgsql::bind::bit;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".capacity = sizeof (" << arg << "." << mi.var << "value);" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_varbit (member_info& mi) + { + os << b << ".type = pgsql::bind::varbit;" + << b << ".buffer = " << arg << "." << mi.var << "value.data_ptr ();" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_uuid (member_info& mi) + { + os << b << ".type = pgsql::bind::uuid;" + << b << ".buffer = " << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + }; + entry<bind_member> bind_member_; + + // + // grow + // + + struct grow_member: relational::grow_member_impl<sql_type>, + member_base + { + grow_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_float (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_numeric (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_date_time (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_bit (member_info&) + { + os << e << " = 0;" + << endl; + } + + virtual void + traverse_varbit (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_uuid (member_info&) + { + os << e << " = 0;" + << endl; + } + }; + entry<grow_member> grow_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "null = true;"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_numeric (member_info& mi) + { + // @@ Optimization: can remove growth check if buffer is fixed. + // + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = size;" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = size;" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_bit (member_info& mi) + { + os << "std::size_t size (0);" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "sizeof (i." << mi.var << "value)," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = size;"; + } + + virtual void + traverse_varbit (member_info& mi) + { + os << "std::size_t size (0);" + << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "i." << mi.var << "size = size;" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_uuid (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value, is_null, " << member << ");" + << "i." << mi.var << "null = is_null;"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "null"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_float (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_numeric (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_date_time (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_bit (member_info& mi) + { + // Presented as byte. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_varbit (member_info& mi) + { + // Presented as bytea. + // + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_uuid (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct class_: relational::class_, context + { + class_ (base const& x): base (x) {} + + virtual string + persist_statement_extra (type& c, + relational::query_parameters&, + persist_position p) + { + string r; + + if (p == persist_after_values) + { + data_member_path* id (id_member (c)); + + type* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + // Top-level auto id. + // + if (id != 0 && !poly_derived && auto_ (*id)) + r = "RETURNING " + + convert_from (column_qname (*id), *id->back ()); + } + + return r; + } + + virtual void + object_extra (type& c) + { + bool abst (abstract (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + if (abst && !poly) + return; + + data_member_path* id (id_member (c)); + semantics::data_member* optimistic (context::optimistic (c)); + + column_count_type const& cc (column_count (c)); + + size_t update_columns ( + cc.total - cc.id - cc.inverse - cc.readonly - cc.separate_update); + + string const& n (class_fq_name (c)); + string const& fn (flat_name (n)); + string traits ("access::object_traits_impl< " + n + ", id_pgsql >"); + + os << "const char " << traits << "::" << endl + << "persist_statement_name[] = " << + strlit (statement_name ("persist", fn, c)) << ";" + << endl; + + if (id != 0) + { + if (poly_derived) + { + os << "const char* const " << traits << "::" << endl + << "find_statement_names[] =" + << "{"; + + for (size_t i (0), n (abst ? 1 : polymorphic_depth (c)); + i < n; + ++i) + { + if (i != 0) + os << "," << endl; + + ostringstream ostr; + ostr << "find_" << i; + os << strlit (statement_name (ostr.str (), fn, c)); + } + + os << "};"; + } + else + os << "const char " << traits << "::" << endl + << "find_statement_name[] = " << + strlit (statement_name ("find", fn, c)) << ";" + << endl; + + if (poly && !poly_derived) + os << "const char " << traits << "::" << endl + << "find_discriminator_statement_name[] = " << + strlit (statement_name ("find_discriminator", fn, c)) << ";" + << endl; + + if (update_columns != 0) + os << "const char " << traits << "::" << endl + << "update_statement_name[] = " << + strlit (statement_name ("update", fn, c)) << ";" + << endl; + + os << "const char " << traits << "::" << endl + << "erase_statement_name[] = " << + strlit (statement_name ("erase", fn, c)) << ";" + << endl; + + if (optimistic != 0) + os << "const char " << traits << "::" << endl + << "optimistic_erase_statement_name[] = " << + strlit (statement_name ("erase_optimistic", fn, c)) << ";" + << endl; + } + + // Query statement name. + // + if (options.generate_query ()) + { + os << "const char " << traits << "::" << endl + << "query_statement_name[] = " << + strlit (statement_name ("query", fn, c)) << ";" + << endl + << "const char " << traits << "::" << endl + << "erase_query_statement_name[] = " << + strlit (statement_name ("erase_query", fn, c)) << ";" + << endl; + } + + // Statement types. + // + + // persist_statement_types. + // + { + os << "const unsigned int " << traits << "::" << endl + << "persist_statement_types[] =" + << "{"; + + statement_oids st (statement_insert); + st.traverse (c); + + // Empty array is not portable. So add a dummy member if we + // are not sending anything with the insert statement. + // + if (cc.total == cc.inverse + cc.optimistic_managed + + (id != 0 && !poly_derived && auto_ (*id) ? cc.id : 0)) + os << "0"; + + os << "};"; + } + + // find_statement_types. + // + if (id != 0) + { + os << "const unsigned int " << traits << "::" << endl + << "find_statement_types[] =" + << "{"; + + statement_oids st (statement_select, true); + st.traverse (*id); + + os << "};"; + } + + // update_statement_types. + // + if (id != 0 && update_columns != 0) + { + os << "const unsigned int " << traits << "::" << endl + << "update_statement_types[] =" + << "{"; + + { + statement_oids st (statement_update, true, &main_section); + st.traverse (c); + } + + // Not the same as update_columns. + // + bool first (cc.total == cc.id + cc.inverse + cc.readonly + + cc.separate_update + cc.optimistic_managed); + + statement_oids st (statement_where, first); + st.traverse (*id); + + if (optimistic != 0) + st.traverse (*optimistic); + + os << "};"; + } + + if (id != 0 && optimistic != 0) + { + os << "const unsigned int " << traits << "::" << endl + << "optimistic_erase_statement_types[] =" + << "{"; + + statement_oids st (statement_where); + st.traverse (*id); + st.traverse (*optimistic); + + os << "};"; + } + } + + virtual void + extra_statement_cache_extra_args (bool c, bool s) + { + bool u (c || s); + + os << "," << endl + << db << "::native_binding&" << (u ? " idn" : "") << "," << endl + << "const unsigned int*" << (u ? " idt" : ""); + } + + virtual void + view_extra (type& c) + { + string const& n (class_fq_name (c)); + string const& fn (flat_name (n)); + string traits ("access::view_traits_impl< " + n + ", id_pgsql >"); + + os << "const char " << traits << "::" << endl + << "query_statement_name[] = " << + strlit (statement_name ("query", fn, c)) << ";" + << endl; + } + + virtual void + object_query_statement_ctor_args (type&, + string const& q, + bool process, + bool prep) + { + os << "sts.connection ()," << endl; + + if (prep) + os << "n," << endl; + else + os << "query_statement_name," << endl; + + os << "text," << endl + << process << "," << endl // Process. + << "true," << endl // Optimize. + << q << ".parameter_types ()," << endl + << q << ".parameter_count ()," << endl + << q << ".parameters_binding ()," << endl + << "imb"; + } + + virtual void + object_erase_query_statement_ctor_args (type&) + { + os << "conn," << endl + << "erase_query_statement_name," << endl + << "text," << endl + << "q.parameter_types ()," << endl + << "q.parameter_count ()," << endl + << "q.parameters_binding ()"; + } + + virtual void + view_query_statement_ctor_args (type&, + string const& q, + bool process, + bool prep) + { + os << "sts.connection ()," << endl; + + if (prep) + os << "n," << endl; + else + os << "query_statement_name," << endl; + + os << q << ".clause ()," << endl + << process << "," << endl // Process. + << "true," << endl // Optimize. + << q << ".parameter_types ()," << endl + << q << ".parameter_count ()," << endl + << q << ".parameters_binding ()," << endl + << "imb"; + } + + virtual void + post_query_ (type&, bool once_off) + { + if (once_off) + os << "st->deallocate ();"; + } + }; + entry<class_> class_entry_; + + struct container_traits : relational::container_traits, context + { + container_traits (base const& x): base (x) {} + + virtual void + container_extra (semantics::data_member& m, semantics::type& t) + { + if (!object (c_) || (abstract (c_) && !polymorphic (c_))) + return; + + container_kind_type ck (container_kind (t)); + + string const& pn (public_name (m)); + string scope (scope_ + "::" + flat_prefix_ + pn + "_traits"); + + data_member_path* imp (inverse (m, "value")); + bool inv (imp != 0); + + bool smart (!inv && !unordered (m) && container_smart (t)); + + // Statment names. + // + + // Prefix top-object name to avoid conflicts with inherited + // member statement names. + // + string fn ( + flat_name ( + class_fq_name (*top_object) + "_" + flat_prefix_ + pn)); + + os << "const char " << scope << "::" << endl + << "select_name[] = " << + strlit (statement_name ("select", fn, m)) << ";" + << endl + << "const char " << scope << "::" << endl + << "insert_name[] = " << + strlit (statement_name ("insert", fn, m)) << ";" + << endl; + + if (smart) + os << "const char " << scope << "::" << endl + << "update_name[] = " << + strlit (statement_name ("update", fn, m)) << ";" + << endl; + + os << "const char " << scope << "::" << endl + << "delete_name[] = " << + strlit (statement_name ("delete", fn, m)) << ";" + << endl; + + // Statement types. + // + + semantics::type& vt (container_vt (m)); + semantics::type& idt (container_idt (m)); + + // insert statement types. + // + { + os << "const unsigned int " << scope << "::" << endl + << "insert_types[] =" + << "{"; + + if (!inv) + { + statement_oids so (statement_insert); + + so.traverse (m, idt, "id", "object_id"); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + so.traverse (m, container_it (m), "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + so.traverse (m, container_kt (m), "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + so.traverse (m, vt, "value", "value"); + } + else + // MSVC does not allow zero length arrays or uninitialized + // non-extern const values. + // + os << "0"; + + os << "};"; + } + + // update statement types. + // + if (smart) + { + os << "const unsigned int " << scope << "::" << endl + << "update_types[] =" + << "{"; + + { + // Use insert instead of update to include read-only members. + // + statement_oids so (statement_insert); + so.traverse (m, vt, "value", "value"); + } + + statement_oids so (statement_where, false); + so.traverse (m, idt, "id", "object_id"); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + so.traverse (m, container_it (m), "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + //so.traverse (m, container_kt (t), "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + //so.traverse (m, vt, "value", "value"); + break; + } + } + + os << "};"; + } + + // delete statement types. + // + if (smart) + { + os << "const unsigned int " << scope << "::" << endl + << "delete_types[] =" + << "{"; + + statement_oids so (statement_where); + so.traverse (m, idt, "id", "object_id"); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + so.traverse (m, container_it (m), "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + //so.traverse (m, container_kt (t), "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + //so.traverse (m, vt, "value", "value"); + break; + } + } + + os << "};"; + } + } + }; + entry<container_traits> container_traits_; + + struct section_traits : relational::section_traits, context + { + section_traits (base const& x): base (x) {} + + virtual void + section_extra (user_section& s) + { + semantics::class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + + if (!poly && (abstract (c_) || + s.special == user_section::special_version)) + return; + + semantics::data_member* opt (optimistic (c_)); + + bool load (s.total != 0 && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + string name (public_name (*s.member)); + string scope (scope_ + "::" + name + "_traits"); + + // Statment names. + // + + // Prefix object name to avoid conflicts with inherited member + // statement names. + // + string fn (flat_name (class_fq_name (c_) + "_" + name)); + + if (load || load_opt) + os << "const char " << scope << "::" << endl + << "select_name[] = " << + strlit (statement_name ("select", fn, *s.member)) << ";" + << endl; + + if (update || update_opt) + os << "const char " << scope << "::" << endl + << "update_name[] = " << + strlit (statement_name ("update", fn, *s.member)) << ";" + << endl; + + // Statement types. + // + if (update || update_opt) + { + os << "const unsigned int " << scope << "::" << endl + << "update_types[] =" + << "{"; + + { + statement_oids st (statement_update, true, &s); + st.traverse (c_); + } + + statement_oids st (statement_where, !update); + st.traverse (*id_member (c_)); + + if (s.optimistic ()) // Note: not update_opt. + st.traverse (*opt); + + os << "};"; + } + } + }; + entry<section_traits> section_traits_; + + struct container_cache_init_members: + relational::container_cache_init_members + { + container_cache_init_members (base const& x): base (x) {} + + virtual void + extra_members () + { + os << ", idn, idt"; + } + }; + entry<container_cache_init_members> container_cache_init_members_; + + struct section_cache_init_members: + relational::section_cache_init_members + { + section_cache_init_members (base const& x): base (x) {} + + virtual void + extra_members () + { + os << ", idn, idt"; + } + }; + entry<section_cache_init_members> section_cache_init_members_; + } + } +} diff --git a/odb/odb/relational/processor.cxx b/odb/odb/relational/processor.cxx new file mode 100644 index 0000000..0f60359 --- /dev/null +++ b/odb/odb/relational/processor.cxx @@ -0,0 +1,1564 @@ +// file : odb/relational/processor.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <vector> +#include <algorithm> + +#include <odb/diagnostics.hxx> +#include <odb/lookup.hxx> +#include <odb/cxx-lexer.hxx> +#include <odb/common.hxx> + +#include <odb/relational/context.hxx> +#include <odb/relational/processor.hxx> + +using namespace std; + +namespace relational +{ + namespace + { + // Indirect (dynamic) context values. + // + static string + id_column_type () + { + context& c (context::current ()); + data_member_path& id (*context::id_member (*c.top_object)); + return id.back ()->get<string> ("column-id-type"); + } + + struct data_member: traversal::data_member, context + { + virtual void + traverse (semantics::data_member& m) + { + if (transient (m)) + return; + + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + semantics::type* wt; + semantics::names* whint (0); + if ((wt = wrapper (t, whint))) + wt = &utype (*wt, whint); + + // Determine the member kind. + // + enum {simple, composite, container, unknown} kind (unknown); + + // See if this is a composite value type. + // + if (composite_wrapper (t)) + kind = composite; + + // If not, see if it is a simple value. + // + if (kind == unknown) + { + string type, id_type; + + if (m.count ("id-type")) + id_type = m.get<string> ("id-type"); + + if (m.count ("type")) + { + type = m.get<string> ("type"); + + if (id_type.empty ()) + id_type = type; + } + + if (semantics::class_* c = object_pointer (t)) + { + // An object pointer in view doesn't really have a "column" + // so pretend that it has already been handled. + // + if (view_member (m)) + kind = simple; + else + { + // This is an object pointer. The column type is the pointed-to + // object id type. + // + semantics::data_member& id (*id_member (*c)->back ()); + + semantics::names* idhint; + semantics::type& idt (utype (id, idhint)); + + // The id type can be a composite value type. + // + if (composite_wrapper (idt)) + kind = composite; + else + { + semantics::type* wt; + semantics::names* whint (0); + if ((wt = wrapper (idt, whint))) + wt = &utype (*wt, whint); + + if (type.empty () && id.count ("id-type")) + type = id.get<string> ("id-type"); + + if (type.empty () && id.count ("type")) + type = id.get<string> ("type"); + + // The rest should be identical to the code for the id_type in + // the else block. + // + if (type.empty () && idt.count ("id-type")) + type = idt.get<string> ("id-type"); + + if (type.empty () && wt != 0 && wt->count ("id-type")) + type = wt->get<string> ("id-type"); + + if (type.empty () && idt.count ("type")) + type = idt.get<string> ("type"); + + if (type.empty () && wt != 0 && wt->count ("type")) + type = wt->get<string> ("type"); + + if (type.empty ()) + type = database_type (idt, idhint, true); + + if (type.empty () && wt != 0) + type = database_type (*wt, whint, true); + + id_type = type; + } + } + } + else + { + if (id_type.empty () && t.count ("id-type")) + id_type = t.get<string> ("id-type"); + + if (id_type.empty () && wt != 0 && wt->count ("id-type")) + id_type = wt->get<string> ("id-type"); + + if (type.empty () && t.count ("type")) + type = t.get<string> ("type"); + + if (type.empty () && wt != 0 && wt->count ("type")) + type = wt->get<string> ("type"); + + if (id_type.empty ()) + id_type = type; + + if (id_type.empty ()) + id_type = database_type (t, hint, true); + + if (id_type.empty () && wt != 0) + id_type = database_type (*wt, whint, true); + + bool null (false); + if (type.empty ()) + type = database_type (t, hint, false, &null); + + if (type.empty () && wt != 0) + type = database_type (*wt, whint, false, &null); + + // Use id mapping for discriminators. + // + if (id (m) || discriminator (m)) + type = id_type; + // Allow NULL if requested by the default mapping. + // + else if (null && !m.count ("not-null")) + m.set ("null", true); + } + + if (kind == unknown && !type.empty ()) + { + m.set ("column-type", type); + m.set ("column-id-type", id_type); + + // Issue a warning if we are relaxing null-ness. + // + if (m.count ("null") && t.count ("not-null")) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " warning: data member declared null while its type is " + << "declared not null" << endl; + } + + kind = simple; + } + } + + // If not a simple value, see if this is a container. + // + if (kind == unknown && context::container (m)) + { + process_container (m, (wt != 0 ? *wt : t)); + kind = container; + } + + // If it is none of the above then we have an error. + // + if (kind == unknown) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: unable to map C++ type '" << t.fq_name (hint) + << "' used in data member '" << m.name () << "' to a " + << db.name () << " database type" << endl; + + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " info: use '#pragma db type' to specify the database type" + << endl; + + throw operation_failed (); + } + + if (m.count ("polymorphic-ref")) + { + // Copy the column name from the root's id member, if specified. + // + { + semantics::class_& r (*object_pointer (t)); + semantics::data_member& id (*id_member (r)->front ()); + + if (id.count ("column")) + m.set ("column", id.get<table_column> ("column")); + } + + m.set ("not-null", true); + m.set ("deferrable", + sema_rel::deferrable (sema_rel::deferrable::not_deferrable)); + m.set ("on-delete", sema_rel::foreign_key::cascade); + } + + process_index (m); + } + + // Convert index/unique specifiers to the index entry in the object. + // + void + process_index (semantics::data_member& m) + { + bool ip (m.count ("index")); + bool up (m.count ("unique")); + + if (ip || up) + { + using semantics::class_; + class_& c (dynamic_cast<class_&> (m.scope ())); + + indexes& ins (c.count ("index") + ? c.get<indexes> ("index") + : c.set ("index", indexes ())); + + index in; + in.loc = m.get<location_t> ( + ip ? "index-location" : "unique-location"); + + if (up) + in.type = "UNIQUE"; + + index::member im; + im.loc = in.loc; + im.name = m.name (); + im.path.push_back (&m); + in.members.push_back (im); + + // Insert it in the location order. + // + ins.insert ( + lower_bound (ins.begin (), ins.end (), in, index_comparator ()), + in); + } + } + + void + process_container_value (semantics::type& t, + semantics::names* hint, + semantics::data_member& m, + string const& prefix, + bool obj_ptr) + { + if (composite_wrapper (t)) + return; + + semantics::names* wh (0); + semantics::type* wt (wrapper (t, wh)); + + string type; + semantics::type& ct (utype (m)); + + // Custom mapping can come from these places (listed in the order + // of priority): member, container type, value type. To complicate + // things a bit, for object references, it can also come from the + // member and value type of the id member. + // + if (m.count (prefix + "-type")) + type = m.get<string> (prefix + "-type"); + + if (type.empty () && ct.count (prefix + "-type")) + type = ct.get<string> (prefix + "-type"); + + semantics::class_* c; + if (obj_ptr && (c = object_pointer (t))) + { + // This is an object pointer. The column type is the pointed-to + // object id type. + // + semantics::data_member& id (*id_member (*c)->back ()); + + semantics::names* idhint; + semantics::type& idt (utype (id, idhint)); + + // Nothing to do if this is a composite value type. + // + if (composite_wrapper (idt)) + return; + + semantics::type* wt (0); + semantics::names* whint (0); + if ((wt = wrapper (idt, whint))) + wt = &utype (*wt, whint); + + if (type.empty () && id.count ("id-type")) + type = id.get<string> ("id-type"); + + if (type.empty () && id.count ("type")) + type = id.get<string> ("type"); + + // The rest of the code is identical to the else block except here + // we have to check for "id-type" before checking for "type". + // + + if (type.empty () && idt.count ("id-type")) + type = idt.get<string> ("id-type"); + + if (type.empty () && wt != 0 && wt->count ("id-type")) + type = wt->get<string> ("id-type"); + + if (type.empty () && idt.count ("type")) + type = idt.get<string> ("type"); + + if (type.empty () && wt != 0 && wt->count ("type")) + type = wt->get<string> ("type"); + + if (type.empty ()) + type = database_type (idt, idhint, true); + + if (type.empty () && wt != 0) + type = database_type (*wt, whint, true); + } + else + { + if (type.empty () && t.count ("type")) + type = t.get<string> ("type"); + + if (type.empty () && wt != 0 && wt->count ("type")) + type = wt->get<string> ("type"); + + bool null (false); + if (type.empty ()) + type = database_type (t, hint, false, &null); + + if (type.empty () && wt != 0) + type = database_type (*wt, wh, false, &null); + + // Allow NULL if requested by the default mapping. + // + if (null && !m.count (prefix + "-not-null")) + m.set (prefix + "-null", true); + } + + if (!type.empty ()) + { + m.set (prefix + "-column-type", type); + m.set (prefix + "-column-id-type", type); + return; + } + + // We do not support nested containers so skip that test. + // + + // If it is none of the above then we have an error. + // + string fq_type (t.fq_anonymous () ? "<anonymous>" : t.fq_name ()); + + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: unable to map C++ type '" << fq_type << "' used in " + << "data member '" << m.name () << "' to a " << db.name () + << " database type" << endl; + + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " info: use '#pragma db " << prefix << "_type' to specify the " + << "database type" << endl; + + throw operation_failed (); + } + + void + process_container (semantics::data_member& m, semantics::type& t) + { + container_kind_type ck (t.get<container_kind_type> ("container-kind")); + + semantics::names* vh (0); + semantics::names* ih (0); + semantics::names* kh (0); + + semantics::type* vt (&utype (m, vh, "value")); + semantics::type* it (ck == ck_ordered ? &utype (m, ih, "index") : 0); + semantics::type* kt (ck == ck_map || ck == ck_multimap + ? &utype (m, kh, "key") + : 0); + + // Process member data. + // + m.set ("id-column-type", &id_column_type); + + process_container_value (*vt, vh, m, "value", true); + + if (it != 0) + process_container_value (*it, ih, m, "index", false); + + if (kt != 0) + process_container_value (*kt, kh, m, "key", true); + } + }; + + // + // + struct view_data_member: traversal::data_member, context + { + view_data_member (semantics::class_& c) + : view_ (c), + query_ (c.get<view_query> ("query")), + amap_ (c.get<view_alias_map> ("alias-map")), + omap_ (c.get<view_object_map> ("object-map")) + { + } + + struct assoc_member + { + semantics::data_member* m; + view_object* vo; + }; + + typedef vector<assoc_member> assoc_members; + + virtual void + traverse (semantics::data_member& m) + { + using semantics::data_member; + + if (transient (m)) + return; + + semantics::type& t (utype (m)); + + // Object pointers are associated with objects. + // + if (object_pointer (t)) + return; + + data_member* src_m (0); // Source member. + + // Resolve member references in column expressions. + // + if (m.count ("column")) + { + // Column literal. + // + if (query_.kind != view_query::condition) + { + warn (m.get<location_t> ("column-location")) + << "db pragma column ignored in a view with " + << (query_.kind == view_query::runtime ? "runtime" : "complete") + << " query" << endl; + } + + return; + } + else if (m.count ("column-expr")) + { + column_expr& e (m.get<column_expr> ("column-expr")); + + if (query_.kind != view_query::condition) + { + warn (e.loc) + << "db pragma column ignored in a view with " + << (query_.kind == view_query::runtime ? "runtime" : "complete") + << " query" << endl; + return; + } + + for (column_expr::iterator i (e.begin ()); i != e.end (); ++i) + { + // This code is quite similar to translate_expression in the + // source generator. + // + try + { + using semantics::scope; + using semantics::class_; + + if (i->kind != column_expr_part::reference) + continue; + + lex_.start (i->value); + + string tl; + tree tn; + cpp_ttype tt (lex_.next (tl, &tn)); + + data_member* m (0); + view_object* vo (0); + + // Check if this is an alias. + // + if (tt == CPP_NAME) + { + view_alias_map::iterator j (amap_.find (tl)); + + if (j != amap_.end ()) + { + vo = j->second; + + // Skip '::'. + // + if (lex_.next (tl, &tn) != CPP_SCOPE) + { + error (i->loc) << "member name expected after an alias " << + "in db pragma column" << endl; + throw operation_failed (); + } + + if (lex_.next (tl, &tn) != CPP_NAME) + throw lookup::invalid_name (); + + m = &vo->obj->lookup<data_member> ( + tl, scope::include_hidden); + + tt = lex_.next (tl, &tn); + } + } + + // If it is not an alias, do the normal lookup. + // + if (vo == 0) + { + // Also get the object type. We need to do it so that + // we can get the correct (derived) table name (the + // member itself can come from a base class). + // + scope* s; + string name; + cpp_ttype ptt; // Not used. + m = &lookup::resolve_scoped_name<data_member> ( + lex_, tt, tl, tn, ptt, + dynamic_cast<scope&> (*unit.find (i->scope)), + name, + false, + &s); + + view_object_map::iterator j ( + omap_.find (dynamic_cast<class_*> (s))); + + if (j == omap_.end ()) + { + error (i->loc) << "name '" << name << "' in db pragma " << + "column does not refer to a data member of a " << + "persistent class that is used in this view" << endl; + throw operation_failed (); + } + + vo = j->second; + } + + i->member_path.push_back (m); + + // Figure out the table name/alias for this member. + // + if (class_* root = polymorphic (*vo->obj)) + { + // If the object is polymorphic, then figure out which of the + // bases this member comes from and use the corresponding + // table. + // + class_* c (&static_cast<class_&> (m->scope ())); + + // If this member's class is not polymorphic (root uses reuse + // inheritance), then use the root table. + // + if (!polymorphic (*c)) + c = root; + + // In a polymorphic hierarchy we have several tables and the + // provided alias is used as a prefix together with the table + // name to form the actual alias. + // + qname const& t (table_name (*c)); + + if (vo->alias.empty ()) + i->table = t; + else + i->table = qname (vo->alias + "_" + t.uname ()); + } + else + i->table = vo->alias.empty () + ? table_name (*vo->obj) + : qname (vo->alias); + + // Finally, resolve nested members if any. + // + for (; tt == CPP_DOT; tt = lex_.next (tl, &tn)) + { + lex_.next (tl, &tn); // Get CPP_NAME. + + // Check that the outer member is composite and also + // unwrap it while at it. + // + class_* comp (composite_wrapper (utype (*m))); + if (comp == 0) + { + error (i->loc) << "data member '" << m->name () << "' " << + "specified in db pragma column is not composite" << endl; + throw operation_failed (); + } + + m = &comp->lookup<data_member> (tl, class_::include_hidden); + i->member_path.push_back (m); + } + + // If the expression is just this reference, then we have + // a source member. + // + if (e.size () == 1) + src_m = m; + } + catch (lookup::invalid_name const&) + { + error (i->loc) << "invalid name in db pragma column" << endl; + throw operation_failed (); + } + catch (semantics::unresolved const& e) + { + if (e.type_mismatch) + error (i->loc) << "name '" << e.name << "' in db pragma " << + "column does not refer to a data member" << endl; + else + error (i->loc) << "unable to resolve data member '" << + e.name << "' specified with db pragma column" << endl; + + throw operation_failed (); + } + catch (semantics::ambiguous const& e) + { + error (i->loc) << "data member name '" << e.first.name () << + "' specified with db pragma column is ambiguous" << endl; + + info (e.first.named ().location ()) << "could resolve to " << + "this data member" << endl; + + info (e.second.named ().location ()) << "or could resolve " << + "to this data member" << endl; + + throw operation_failed (); + } + } + + // Check that the source member is not transient or inverse. Also + // check that the C++ types are the same (sans cvr-qualification + // and wrapping) and issue a warning if they differ. In rare cases + // where this is not a mistake, the user can use a phony expression + // (e.g., "" + person:name) to disable the warning. Note that in + // this case there will be no type pragma copying, which is probably + // ok seeing that the C++ types are different. + // + // + if (src_m != 0) + { + string reason; + + if (transient (*src_m)) + reason = "transient"; + else if (inverse (*src_m)) + reason = "inverse"; + + if (!reason.empty ()) + { + error (e.loc) + << "object data member '" << src_m->name () << "' specified " + << "in db pragma column is " << reason << endl; + throw operation_failed (); + } + + if (!member_resolver::check_types (utype (*src_m), utype (m))) + { + warn (e.loc) + << "object data member '" << src_m->name () << "' specified " + << "in db pragma column has a different type compared to the " + << "view data member" << endl; + + info (src_m->file (), src_m->line (), src_m->column ()) + << "object data member is defined here" << endl; + + info (m.file (), m.line (), m.column ()) + << "view data member is defined here" << endl; + } + } + } + // This member has no column information. If we are generating our + // own query, try to find a member with the same (or similar) name + // in one of the associated objects. + // + else if (query_.kind == view_query::condition) + { + view_objects& objs (view_.get<view_objects> ("objects")); + + assoc_members exact_members, pub_members; + member_resolver resolver (exact_members, pub_members, m); + + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + if (i->kind == view_object::object) + resolver.traverse (*i); + } + + assoc_members& members ( + !exact_members.empty () ? exact_members : pub_members); + + // Issue diagnostics if we didn't find any or found more + // than one. + // + if (members.empty ()) + { + error (m.file (), m.line (), m.column ()) + << "unable to find a corresponding data member for '" + << m.name () << "' in any of the associated objects" << endl; + + info (m.file (), m.line (), m.column ()) + << "use db pragma column to specify the corresponding data " + << "member or column name" << endl; + + throw operation_failed (); + } + else if (members.size () > 1) + { + error (m.file (), m.line (), m.column ()) + << "corresponding data member for '" << m.name () << "' is " + << "ambiguous" << endl; + + info (m.file (), m.line (), m.column ()) + << "candidates are:" << endl; + + for (assoc_members::const_iterator i (members.begin ()); + i != members.end (); + ++i) + { + info (i->m->file (), i->m->line (), i->m->column ()) + << " '" << i->m->name () << "' in object '" + << i->vo->name () << "'" << endl; + } + + info (m.file (), m.line (), m.column ()) + << "use db pragma column to resolve this ambiguity" << endl; + + throw operation_failed (); + } + + // Synthesize the column expression for this member. + // + assoc_member const& am (members.back ()); + + column_expr& e (m.set ("column-expr", column_expr ())); + e.push_back (column_expr_part ()); + column_expr_part& ep (e.back ()); + + ep.kind = column_expr_part::reference; + + + // If this object is polymorphic, then figure out which of the + // bases this member comes from and use the corresponding table. + // + using semantics::class_; + + if (class_* root = polymorphic (*am.vo->obj)) + { + class_* c (&static_cast<class_&> (am.m->scope ())); + + // If this member's class is not polymorphic (root uses reuse + // inheritance), then use the root table. + // + if (!polymorphic (*c)) + c = root; + + // In a polymorphic hierarchy we have several tables and the + // provided alias is used as a prefix together with the table + // name to form the actual alias. + // + qname const& t (table_name (*c)); + + if (am.vo->alias.empty ()) + ep.table = t; + else + ep.table = qname (am.vo->alias + "_" + t.uname ()); + } + else + ep.table = am.vo->alias.empty () + ? table_name (*am.vo->obj) + : qname (am.vo->alias); + + ep.member_path.push_back (am.m); + + src_m = am.m; + } + + // If we have the source member and don't have the type pragma of + // our own, but the source member does, then copy the columnt type + // over. In case the source member is a pointer, also check the id + // member. + // + if (src_m != 0 && !m.count ("type")) + { + if (src_m->count ("type")) + m.set ("column-type", src_m->get<string> ("column-type")); + else if (semantics::class_* c = object_pointer (utype (*src_m))) + { + semantics::data_member& id (*id_member (*c)->back ()); + + if (id.count ("type")) + m.set ("column-type", id.get<string> ("column-type")); + } + } + + // Check the return statements above if you add any extra logic + // here. + } + + struct member_resolver: traversal::class_ + { + member_resolver (assoc_members& members, + assoc_members& pub_members, + semantics::data_member& m) + : member_ (members, pub_members, m) + { + *this >> names_ >> member_; + *this >> inherits_ >> *this; + } + + void + traverse (view_object& vo) + { + member_.vo_ = &vo; + + // First look for an exact match. + // + { + member_.exact_ = true; + member_.found_ = false; + traverse (*vo.obj); + } + + // If we didn't find an exact match, then look for a public + // name match. + // + if (!member_.found_) + { + member_.exact_ = false; + traverse (*vo.obj); + } + } + + virtual void + traverse (type& c) + { + if (!object (c)) + return; // Ignore transient bases. + + names (c); + + // If we already found a match in one of the derived classes, + // don't go into bases to get the standard "hiding" behavior. + // + if (!member_.found_) + inherits (c); + } + + public: + static bool + check_types (semantics::type& ot, semantics::type& vt) + { + using semantics::type; + + // Require that the types be the same sans the wrapping and + // cvr-qualification. If the object member type is a pointer, + // use the id type of the pointed-to object. + // + type* t1; + + if (semantics::class_* c = object_pointer (ot)) + t1 = &utype (*id_member (*c)); + else + t1 = &ot; + + type* t2 (&vt); + + if (type* wt1 = context::wrapper (*t1)) + t1 = &utype (*wt1); + + if (type* wt2 = context::wrapper (*t2)) + t2 = &utype (*wt2); + + if (t1 != t2) + return false; + + return true; + } + + private: + struct data_member: traversal::data_member + { + data_member (assoc_members& members, + assoc_members& pub_members, + semantics::data_member& m) + : members_ (members), + pub_members_ (pub_members), + name_ (m.name ()), + pub_name_ (context::current ().public_name (m)), + type_ (utype (m)) + { + } + + virtual void + traverse (type& m) + { + if (exact_) + { + if (name_ == m.name () && check (m)) + { + assoc_member am; + am.m = &m; + am.vo = vo_; + members_.push_back (am); + found_ = true; + } + } + else + { + if (pub_name_ == context::current ().public_name (m) && + check (m)) + { + assoc_member am; + am.m = &m; + am.vo = vo_; + pub_members_.push_back (am); + found_ = true; + } + } + } + + bool + check (semantics::data_member& m) + { + // Make sure that the found node can possibly match. + // + if (context::transient (m) || + context::inverse (m) || + m.count ("polymorphic-ref")) + return false; + + return check_types (utype (m), type_); + } + + assoc_members& members_; + assoc_members& pub_members_; + + string name_; + string pub_name_; + semantics::type& type_; + + view_object* vo_; + bool exact_; + bool found_; + }; + + traversal::names names_; + data_member member_; + traversal::inherits inherits_; + }; + + private: + semantics::class_& view_; + view_query& query_; + view_alias_map& amap_; + view_object_map& omap_; + cxx_string_lexer lex_; + }; + + struct class_: traversal::class_, context + { + class_ () + : typedefs_ (true) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + member_names_ >> member_; + } + + virtual void + traverse (type& c) + { + class_kind_type k (class_kind (c)); + + if (k == class_other) + return; + + names (c); // Process nested classes. + names (c, member_names_); + + if (k == class_object) + traverse_object (c); + else if (k == class_view) + traverse_view (c); + } + + // + // Object. + // + + virtual void + traverse_object (type& c) + { + // Remove the bulk pragma if this database doesn't support bulk + // operations. + // + if (c.count ("bulk") && !generate_bulk) + c.remove ("bulk"); + + // Process indexes. Here we need to do two things: resolve member + // names to member paths and assign names to unnamed indexes. We + // are also going to handle the special container indexes. + // + indexes& ins (c.count ("index") + ? c.get<indexes> ("index") + : c.set ("index", indexes ())); + + for (indexes::iterator i (ins.begin ()); i != ins.end ();) + { + index& in (*i); + + // This should never happen since a db index pragma without + // the member specifier will be treated as a member pragma. + // + assert (!in.members.empty ()); + + // First resolve member names. + // + index::members_type::iterator j (in.members.begin ()); + for (; j != in.members.end (); ++j) + { + index::member& im (*j); + + if (!im.path.empty ()) + continue; // Already resolved. + + im.path = resolve_data_members (c, im.name, im.loc, lex_); + + if (container (*im.path.back ())) + break; + } + + // Add the table prefix if this database has global index names. + // + if (!in.name.empty () && global_index) + in.name = table_name_prefix (class_scope (c)) + in.name; + + // Handle container indexes. + // + if (j != in.members.end ()) + { + // Do some sanity checks. + // + if (in.members.size () != 1) + { + error (in.loc) << "multiple data members specified for a " + << "container index" << endl; + throw operation_failed (); + } + + string tl; + if (lex_.next (tl) != CPP_DOT || lex_.next (tl) != CPP_NAME || + (tl != "id" && tl != "index")) + { + error (j->loc) << ".id or .index special member expected in a " + << "container index" << endl; + throw operation_failed (); + } + + string n (tl); + + if (lex_.next (tl) != CPP_EOF) + { + error (j->loc) << "unexpected text after ." << n << " in " + << "db pragma member" << endl; + throw operation_failed (); + } + + // Move this index to the container member. + // + j->path.back ()->set (n + "-index", *i); + i = ins.erase (i); + continue; + } + + // Now assign the name if the index is unnamed. We have to + // add table name as a prefix here since there is not way + // to distinguish between user-assigned and auto-derived + // names in the model. + // + if (in.name.empty ()) + { + // Make sure there is only one member. + // + if (in.members.size () > 1) + { + error (in.loc) << "unnamed index with more than one data " + << "member" << endl; + throw operation_failed (); + } + + // Generally, we want the index name to be based on the column + // name. This is straightforward for single-column members. In + // case of a composite member, 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 n (column_prefix (in.members.front ().path, true).prefix); + + if (n.empty ()) + n = public_name_db (*in.members.front ().path.back ()); + else if (n[n.size () - 1] == '_') + n.resize (n.size () - 1); // Remove trailing underscore. + + in.name = index_name (table_name (c), n); + } + + ++i; + } + } + + // + // View. + // + + struct relationship + { + semantics::data_member* member; + string name; + view_object* pointer; + view_object* pointee; + }; + + typedef vector<relationship> relationships; + + virtual void + traverse_view (type& c) + { + bool has_q (c.count ("query")); + bool has_o (c.count ("objects")); + + // Determine the kind of query template we've got. + // + view_query& vq (has_q + ? c.get<view_query> ("query") + : c.set ("query", view_query ())); + if (has_q) + { + if (!vq.literal.empty ()) + { + string q (upcase (vq.literal)); + + //@@ We need to recognize database-specific list of prefixes. For + // example, PG has WITH. Alternatively (or in addition) we could + // do the same comment trick (e.g., /*SELECT*/ to treat it as a + // SELECT-like queiry). + // + if (q.compare (0, 7, "SELECT ") == 0) + vq.kind = view_query::complete_select; + else if (q.compare (0, 5, "EXEC ") == 0 || + q.compare (0, 5, "CALL ") == 0 || + q.compare (0, 8, "EXECUTE ") == 0) + vq.kind = view_query::complete_execute; + // + // Hint for databases that use SELECT for stored procedure + // calls (e.g., PostgreSQL). + // + else if (q.compare (0, 8, "/*CALL*/") == 0) + { + vq.literal = string (vq.literal, q[8] == ' ' ? 9 : 8); + vq.kind = view_query::complete_execute; + } + else + vq.kind = view_query::condition; + } + else if (!vq.expr.empty ()) + { + // If the first token in the expression is a string and + // it starts with "SELECT " or is equal to "SELECT" or + // one of the stored procedure call keywords, then we + // have a complete query. + // + if (vq.expr.front ().type == CPP_STRING) + { + string q (upcase (vq.expr.front ().literal)); + + if (q.compare (0, 7, "SELECT ") == 0 || q == "SELECT") + vq.kind = view_query::complete_select; + else if (q.compare (0, 5, "EXEC ") == 0 || q == "EXEC" || + q.compare (0, 5, "CALL ") == 0 || q == "CALL" || + q.compare (0, 8, "EXECUTE ") == 0 || q == "EXECUTE") + vq.kind = view_query::complete_execute; + else if (q.compare (0, 8, "/*CALL*/") == 0) + { + vq.expr.front ().literal = + string (vq.expr.front ().literal, q[8] == ' ' ? 9 : 8); + vq.kind = view_query::complete_execute; + } + else + vq.kind = view_query::condition; + } + else + vq.kind = view_query::condition; + } + else + vq.kind = (vq.distinct || vq.for_update) + ? view_query::condition // The query(distinct) case. + : view_query::runtime; + } + else + vq.kind = has_o ? view_query::condition : view_query::runtime; + + if ((vq.distinct || vq.for_update) && vq.kind != view_query::condition) + { + error (vq.loc) + << "result modifier specified for " + << (vq.kind == view_query::runtime ? "runtime" : "native") + << " query" << endl; + + throw operation_failed (); + } + + // We cannot have an incomplete query if there are not objects + // to derive the rest from. + // + if (vq.kind == view_query::condition && !has_o) + { + error (c.file (), c.line (), c.column ()) + << "view '" << class_fq_name (c) << "' has an incomplete query " + << "template and no associated objects" << endl; + + info (c.file (), c.line (), c.column ()) + << "use db pragma query to provide a complete query template" + << endl; + + info (c.file (), c.line (), c.column ()) + << "or use db pragma object to associate one or more objects " + << "with the view" + << endl; + + throw operation_failed (); + } + + // Process join conditions. + // + if (has_o) + { + view_objects& objs (c.get<view_objects> ("objects")); + + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + if (i == objs.begin () && i->join != view_object::left) + { + error (i->loc) + << "no join type can be specified for the first associated " + << (i->kind == view_object::object ? "object" : "table") + << endl; + throw operation_failed (); + } + + if (i->kind != view_object::object) + { + // Make sure we have join conditions for tables unless it + // is the first entry. + // + if (i != objs.begin () && i->cond.empty ()) + { + error (i->loc) + << "missing join condition in db pragma table" << endl; + + throw operation_failed (); + } + + continue; + } + + // If we have to generate the query and there was no JOIN + // condition specified by the user, try to come up with one + // automatically based on object relationships. CROSS JOIN + // has no condition. + // + if (vq.kind == view_query::condition && + i->cond.empty () && + i != objs.begin () && + i->join != view_object::cross) + { + relationships rs; + + // Check objects specified prior to this one for any + // relationships. We don't examine objects that were + // specified after this one because that would require + // rearranging the JOIN order. + // + for (view_objects::iterator j (objs.begin ()); j != i; ++j) + { + if (j->kind != view_object::object) + continue; // Skip tables. + + // First see if any of the objects that were specified + // prior to this object point to it. + // + { + relationship_resolver r (rs, *i, true); + r.traverse (*j); + } + + // Now see if this object points to any of the objects + // specified prior to it. + // + { + relationship_resolver r (rs, *j, false); + r.traverse (*i); + } + } + + // Issue diagnostics if we didn't find any or found more + // than one. + // + if (rs.empty ()) + { + error (i->loc) + << "unable to find an object relationship involving " + << "object '" << i->name () << "' and any of the previously " + << "associated objects" << endl; + + info (i->loc) + << "use the join condition clause in db pragma object " + << "to specify a custom join condition" << endl; + + throw operation_failed (); + } + else if (rs.size () > 1) + { + error (i->loc) + << "object relationship for object '" << i->name () << "' " + << "is ambiguous" << endl; + + info (i->loc) + << "candidates are:" << endl; + + for (relationships::const_iterator j (rs.begin ()); + j != rs.end (); + ++j) + { + semantics::data_member& m (*j->member); + + info (m.file (), m.line (), m.column ()) + << " '" << j->name << "' " + << "in object '" << j->pointer->name () << "' " + << "pointing to '" << j->pointee->name () << "'" + << endl; + } + + info (i->loc) + << "use the join condition clause in db pragma object " + << "to resolve this ambiguity" << endl; + + throw operation_failed (); + } + + // Synthesize the condition. + // + relationship const& r (rs.back ()); + + string name (r.pointer->alias.empty () + ? class_fq_name (*r.pointer->obj) + : r.pointer->alias); + name += "::"; + name += r.name; + + lex_.start (name); + + string t; + for (cpp_ttype tt (lex_.next (t)); + tt != CPP_EOF; + tt = lex_.next (t)) + { + i->cond.push_back (cxx_token (lex_.location (), tt, t)); + } + } + } + } + + // Handle forced versioning. When versioning is forced, ignore + // it for native views. + // + if (force_versioned && vq.kind == view_query::condition) + c.set ("versioned", true); + + // Handle data members. + // + { + view_data_member t (c); + traversal::names n (t); + names (c, n); + } + } + + struct relationship_resolver: object_members_base + { + relationship_resolver (relationships& rs, + view_object& pointee, + bool forward) + // Look in polymorphic bases only for previously-associated + // objects since backward pointers from bases will result in + // the pathological case (we will have to join the base table + // first, which means we will get both bases and derived objects + // instead of just derived). + // + : object_members_base (false, false, true, forward), + relationships_ (rs), + // Ignore self-references if we are looking for backward + // pointers since they were already added to the list in + // the previous pass. + // + self_pointer_ (forward), + pointer_ (0), + pointee_ (pointee) + { + } + + void + traverse (view_object& pointer) + { + pointer_ = &pointer; + object_members_base::traverse (*pointer.obj); + } + + using object_members_base::traverse; // Unhide. + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore polymorphic id references. + // + if (m.count ("polymorphic-ref")) + return; + + // Ignore inverse sides of the same relationship to avoid + // phony conflicts caused by the direct side that will end + // up in the relationship list as well. Unless the inverse + // member is in the polymorphic base in which case we will + // miss it since we don't examine inside poly bases on the + // backwards scan (see above). + // + if (data_member_path* imp = inverse (m)) + { + if (&imp->front ()->scope () == &c) // Direct member. + return; + } + + // Ignore self-pointers if requested. + // + if (!self_pointer_ && pointer_->obj == &c) + return; + + if (pointee_.obj == &c) + { + relationships_.push_back (relationship ()); + relationships_.back ().member = &m; + relationships_.back ().name = member_prefix_ + m.name (); + relationships_.back ().pointer = pointer_; + relationships_.back ().pointee = &pointee_; + } + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + if (semantics::class_* c = + object_pointer (context::container_vt (m))) + { + if (inverse (m, "value")) + return; + + // Ignore self-pointers if requested. + // + if (!self_pointer_ && pointer_->obj == c) + return; + + if (pointee_.obj == c) + { + relationships_.push_back (relationship ()); + relationships_.back ().member = &m; + relationships_.back ().name = member_prefix_ + m.name (); + relationships_.back ().pointer = pointer_; + relationships_.back ().pointee = &pointee_; + } + } + } + + private: + relationships& relationships_; + bool self_pointer_; + view_object* pointer_; + view_object& pointee_; + }; + + private: + cxx_string_lexer lex_; + + traversal::defines defines_; + typedefs typedefs_; + + data_member member_; + traversal::names member_names_; + }; + } + + void + process () + { + context ctx; + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (true); + traversal::namespace_ ns; + class_ c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (true); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (ctx.unit); + } +} diff --git a/odb/odb/relational/processor.hxx b/odb/odb/relational/processor.hxx new file mode 100644 index 0000000..71b8643 --- /dev/null +++ b/odb/odb/relational/processor.hxx @@ -0,0 +1,15 @@ +// file : odb/relational/processor.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_PROCESSOR_HXX +#define ODB_RELATIONAL_PROCESSOR_HXX + +namespace relational +{ + // Issues diagnostics and throws operation_failed in case of an error. + // + void + process (); +} + +#endif // ODB_RELATIONAL_PROCESSOR_HXX diff --git a/odb/odb/relational/schema-source.cxx b/odb/odb/relational/schema-source.cxx new file mode 100644 index 0000000..5659485 --- /dev/null +++ b/odb/odb/relational/schema-source.cxx @@ -0,0 +1,281 @@ +// file : odb/relational/schema-source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema-source.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace schema + { + void + generate_source (sema_rel::changelog* log) + { + context ctx; + ostream& os (ctx.os); + database db (ctx.db); + options const& ops (ctx.options); + sema_rel::model& model (*ctx.model); + string const& schema_name (ops.schema_name ()[db]); + + if (log != 0 && ops.suppress_migration ()) + log = 0; + + bool schema_version ( + model.version () != 0 && !ctx.options.suppress_schema_version ()); + + instance<cxx_emitter> emitter; + emitter_ostream emitter_os (*emitter); + schema_format format (schema_format::embedded); + + if (!model.names_empty () || log != 0 || schema_version) + os << "namespace odb" + << "{"; + + // Schema. + // + if (!model.names_empty () || schema_version) + { + os << "static bool" << 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 (!ops.omit_drop ()) + { + bool close (false); + + os << "if (drop)" + << "{"; + + instance<drop_model> dmodel (*emitter, emitter_os, format); + instance<drop_table> dtable (*emitter, emitter_os, format); + trav_rel::qnames names; + dmodel >> names >> dtable; + + for (unsigned short pass (1); pass < 3; ++pass) + { + emitter->pass (pass); + dmodel->pass (pass); + dtable->pass (pass); + + dmodel->traverse (model); + + close = close || !emitter->empty (); + } + + if (schema_version) + { + instance<version_table> vt (*emitter, emitter_os, format); + vt->create_table (); + vt->drop (); + close = true; + } + + if (close) // Close the last case and the switch block. + os << "return false;" + << "}" // case + << "}"; // switch + + os << "}"; + } + + // Create. + // + if (!ops.omit_create ()) + { + bool close (false); + + if (ops.omit_drop ()) + os << "if (!drop)"; + else + os << "else"; + + os << "{"; + + instance<create_model> cmodel (*emitter, emitter_os, format); + instance<create_table> ctable (*emitter, emitter_os, format); + trav_rel::qnames names; + cmodel >> names >> ctable; + + for (unsigned short pass (1); pass < 3; ++pass) + { + emitter->pass (pass); + cmodel->pass (pass); + ctable->pass (pass); + + cmodel->traverse (model); + + close = close || !emitter->empty (); + } + + if (schema_version) + { + instance<version_table> vt (*emitter, emitter_os, format); + vt->create_table (); + vt->create (model.version ()); + close = true; + } + + if (close) // Close the last case and the switch block. + os << "return false;" + << "}" // case + << "}"; // switch + + os << "}"; + } + + os << "return false;" + << "}"; + + os << "static const schema_catalog_create_entry" << endl + << "create_schema_entry_ (" << endl + << "id_" << db << "," << endl + << context::strlit (schema_name) << "," << endl + << "&create_schema);" + << endl; + } + + // Migration. + // + if (log != 0) + { + // Create NULL migration entry for the base version so that we + // get the complete version range (base, current) at runtime. + // Code in schema_catalog relies on this. + // + os << "static const schema_catalog_migrate_entry" << endl + << "migrate_schema_entry_" << log->model ().version () << + "_ (" << endl + << "id_" << db << "," << endl + << context::strlit (schema_name) << "," << endl + << log->model ().version () << "ULL," << endl + << "0);" + << endl; + + for (sema_rel::changelog::contains_changeset_iterator i ( + log->contains_changeset_begin ()); + i != log->contains_changeset_end (); ++i) + { + sema_rel::changeset& cs (i->changeset ()); + + os << "static bool" << endl + << "migrate_schema_" << cs.version () << " (database& db, " << + "unsigned short pass, bool pre)" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);" + << "ODB_POTENTIALLY_UNUSED (pass);" + << "ODB_POTENTIALLY_UNUSED (pre);" + << endl; + + // Pre. + // + { + bool close (false); + + os << "if (pre)" + << "{"; + + instance<changeset_pre> changeset (*emitter, emitter_os, format); + instance<create_table> ctable (*emitter, emitter_os, format); + instance<alter_table_pre> atable (*emitter, emitter_os, format); + trav_rel::qnames names; + changeset >> names; + names >> ctable; + names >> atable; + + for (unsigned short pass (1); pass < 3; ++pass) + { + emitter->pass (pass); + changeset->pass (pass); + ctable->pass (pass); + atable->pass (pass); + + changeset->traverse (cs); + + close = close || !emitter->empty (); + } + + if (!ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*emitter, emitter_os, format); + vt->migrate_pre (cs.version ()); + close = true; + } + + if (close) // Close the last case and the switch block. + os << "return false;" + << "}" // case + << "}"; // switch + + os << "}"; + } + + // Post. + // + { + bool close (false); + + os << "else" + << "{"; + + instance<changeset_post> changeset (*emitter, emitter_os, format); + instance<drop_table> dtable (*emitter, emitter_os, format); + instance<alter_table_post> atable (*emitter, emitter_os, format); + trav_rel::qnames names; + changeset >> names; + names >> dtable; + names >> atable; + + for (unsigned short pass (1); pass < 3; ++pass) + { + emitter->pass (pass); + changeset->pass (pass); + dtable->pass (pass); + atable->pass (pass); + + changeset->traverse (cs); + + close = close || !emitter->empty (); + } + + if (!ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*emitter, emitter_os, format); + vt->migrate_post (); + close = true; + } + + if (close) // Close the last case and the switch block. + os << "return false;" + << "}" // case + << "}"; // switch + + os << "}"; + } + + os << "return false;" + << "}"; + + os << "static const schema_catalog_migrate_entry" << endl + << "migrate_schema_entry_" << cs.version () << "_ (" << endl + << "id_" << db << "," << endl + << context::strlit (schema_name) << "," << endl + << cs.version () << "ULL," << endl + << "&migrate_schema_" << cs.version () << ");" + << endl; + } + } + + if (!model.names_empty () || log != 0 || schema_version) + os << "}"; // namespace odb + } + } +} diff --git a/odb/odb/relational/schema-source.hxx b/odb/odb/relational/schema-source.hxx new file mode 100644 index 0000000..d2235f5 --- /dev/null +++ b/odb/odb/relational/schema-source.hxx @@ -0,0 +1,126 @@ +// file : odb/relational/schema-source.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SCHEMA_SOURCE_HXX +#define ODB_RELATIONAL_SCHEMA_SOURCE_HXX + +#include <odb/diagnostics.hxx> + +#include <odb/emitter.hxx> +#include <odb/relational/context.hxx> +#include <odb/relational/schema.hxx> + +namespace relational +{ + namespace schema + { + 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. + }; + } +} + +#endif // ODB_RELATIONAL_SCHEMA_SOURCE_HXX diff --git a/odb/odb/relational/schema.cxx b/odb/odb/relational/schema.cxx new file mode 100644 index 0000000..dd70bfa --- /dev/null +++ b/odb/odb/relational/schema.cxx @@ -0,0 +1,174 @@ +// file : odb/relational/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> +#include <limits> +#include <sstream> + +#include <odb/relational/schema.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +namespace relational +{ + namespace schema + { + void + generate_prologue () + { + instance<sql_file> file; + file->prologue (); + } + + void + generate_epilogue () + { + instance<sql_file> file; + file->epilogue (); + } + + void + generate_drop () + { + context ctx; + instance<sql_emitter> em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance<drop_model> model (*em, emos, f); + instance<drop_table> table (*em, emos, f); + trav_rel::qnames names; + + model >> names >> table; + + // Pass 1 and 2. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + model->pass (pass); + table->pass (pass); + + model->traverse (*ctx.model); + } + + if (ctx.model->version () != 0 && + !ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*em, emos, f); + vt->create_table (); + vt->drop (); + } + } + + void + generate_create () + { + context ctx; + instance<sql_emitter> em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance<create_model> model (*em, emos, f); + instance<create_table> table (*em, emos, f); + trav_rel::qnames names; + + model >> names >> table; + + // Pass 1 and 2. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + model->pass (pass); + table->pass (pass); + + model->traverse (*ctx.model); + } + + if (ctx.model->version () != 0 && + !ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*em, emos, f); + + if (ctx.options.omit_drop ()) + vt->create_table (); + + vt->create (ctx.model->version ()); + } + } + + void + generate_migrate_pre (sema_rel::changeset& cs) + { + context ctx; + instance<sql_emitter> em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance<changeset_pre> changeset (*em, emos, f); + instance<create_table> ctable (*em, emos, f); + instance<alter_table_pre> atable (*em, emos, f); + trav_rel::qnames names; + + changeset >> names; + names >> ctable; + names >> atable; + + // Pass 1 and 2. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + changeset->pass (pass); + ctable->pass (pass); + atable->pass (pass); + + changeset->traverse (cs); + } + + if (!ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*em, emos, f); + vt->migrate_pre (cs.version ()); + } + } + + void + generate_migrate_post (sema_rel::changeset& cs) + { + context ctx; + instance<sql_emitter> em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance<changeset_post> changeset (*em, emos, f); + instance<drop_table> dtable (*em, emos, f); + instance<alter_table_post> atable (*em, emos, f); + trav_rel::qnames names; + + changeset >> names; + names >> dtable; + names >> atable; + + // Pass 1 and 2. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + changeset->pass (pass); + dtable->pass (pass); + atable->pass (pass); + + changeset->traverse (cs); + } + + if (!ctx.options.suppress_schema_version ()) + { + instance<version_table> vt (*em, emos, f); + vt->migrate_post (); + } + } + } +} diff --git a/odb/odb/relational/schema.hxx b/odb/odb/relational/schema.hxx new file mode 100644 index 0000000..cd975b7 --- /dev/null +++ b/odb/odb/relational/schema.hxx @@ -0,0 +1,1606 @@ +// file : odb/relational/schema.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SCHEMA_HXX +#define ODB_RELATIONAL_SCHEMA_HXX + +#include <set> +#include <vector> +#include <cassert> + +#include <odb/emitter.hxx> +#include <odb/relational/common.hxx> +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace schema + { + typedef std::set<qname> table_set; + + struct common: virtual context + { + typedef ::emitter emitter_type; + + common (emitter_type& e, ostream& os, schema_format f) + : e_ (e), os_ (os), format_ (f) {} + + void + pre_statement () + { + e_.pre (); + diverge (os_); + } + + void + post_statement () + { + restore (); + e_.post (); + } + + emitter_type& + emitter () const + { + return e_; + } + + ostream& + stream () const + { + return os_; + } + + public: + // Find an entity corresponding to the drop node in alter_table. + // + template <typename T, typename D> + T& + find (D& d) + { + using sema_rel::model; + using sema_rel::changeset; + using sema_rel::table; + using sema_rel::alter_table; + + alter_table& at (dynamic_cast<alter_table&> (d.scope ())); + changeset& cs (dynamic_cast<changeset&> (at.scope ())); + model& bm (cs.base_model ()); + table* bt (bm.find<table> (at.name ())); + assert (bt != 0); + T* b (bt->find<T> (d.name ())); + assert (b != 0); + return *b; + } + + protected: + emitter_type& e_; + ostream& os_; + schema_format format_; + }; + + // + // Drop. + // + + // Only used in migration. + // + struct drop_column: trav_rel::drop_column, common + { + typedef drop_column base; + + drop_column (common const& c, bool* first = 0) + : common (c), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + drop_column (drop_column const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_) + { + } + + virtual void + drop_header () + { + // By default ADD COLUMN though some databases use just ADD. + // + os << "DROP COLUMN "; + } + + virtual void + traverse (sema_rel::drop_column& dc) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl + << " "; + drop_header (); + os << quote_id (dc.name ()); + } + + protected: + bool& first_; + bool first_data_; + }; + + // Normally only used in migration but some databases use it as a + // base to also drop foreign keys in schema. + // + struct drop_foreign_key: trav_rel::foreign_key, + trav_rel::drop_foreign_key, + trav_rel::add_foreign_key, // Override. + common + { + typedef drop_foreign_key base; + + // Schema constructor. + // + drop_foreign_key (common const& c, table_set& dropped, bool* first = 0) + : common (c), + dropped_ (&dropped), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + // Migration constructor. + // + drop_foreign_key (common const& c, bool* first = 0) + : common (c), + dropped_ (0), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + drop_foreign_key (drop_foreign_key const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + dropped_ (c.dropped_), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_) + { + } + + virtual void + drop_header () + { + os << "DROP CONSTRAINT "; + } + + virtual void + traverse (sema_rel::foreign_key& fk) + { + // If the table which we reference is droped before us, then + // we need to drop the constraint first. Similarly, if the + // referenced table is not part if this model, then assume + // it is dropped before us. In migration we always do this + // first. + // + sema_rel::table& t (dynamic_cast<sema_rel::table&> (fk.scope ())); + + if (dropped_ != 0) + { + sema_rel::qname const& rt (fk.referenced_table ()); + sema_rel::model& m (dynamic_cast<sema_rel::model&> (t.scope ())); + + if (dropped_->find (rt) == dropped_->end () && + m.find (rt) != m.names_end ()) + return; + } + + drop (t, fk); + } + + virtual void + drop (sema_rel::table& t, sema_rel::foreign_key& fk) + { + // When generating schema we would need to check if the key exists. + // So this implementation will need to be customize on the per- + // database level. + // + pre_statement (); + + os << "ALTER TABLE " << quote_id (t.name ()) << endl + << " "; + drop_header (); + os << quote_id (fk.name ()) << endl; + + post_statement (); + } + + virtual void + traverse (sema_rel::drop_foreign_key& dfk) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl; + drop (dfk); + } + + virtual void + drop (sema_rel::drop_foreign_key& dfk) + { + os << " "; + drop_header (); + os << quote_id (dfk.name ()); + } + + protected: + table_set* dropped_; + bool& first_; + bool first_data_; + }; + + // Currently only used in migration. + // + struct drop_index: trav_rel::drop_index, common + { + typedef drop_index base; + + enum index_type {unique, non_unique, all}; + + drop_index (common const& c, index_type t = all) + : common (c), type_ (t) {} + + virtual void + traverse (sema_rel::drop_index& di) + { + // Find the index we are dropping in the base model. + // + traverse (find<sema_rel::index> (di)); + } + + virtual void + traverse (sema_rel::index& in) + { + if (type_ == unique && + in.type ().find ("UNIQUE") == string::npos && + in.type ().find ("unique") == string::npos) + return; + + if (type_ == non_unique && ( + in.type ().find ("UNIQUE") != string::npos || + in.type ().find ("unique") != string::npos)) + return; + + pre_statement (); + drop (in); + post_statement (); + } + + virtual string + name (sema_rel::index& in) + { + return quote_id (in.name ()); + } + + virtual void + drop (sema_rel::index& in) + { + os << "DROP INDEX " << name (in) << endl; + } + + protected: + index_type type_; + }; + + struct drop_table: trav_rel::table, + trav_rel::drop_table, + trav_rel::add_table, // Override. + trav_rel::alter_table, // Override. + common + { + typedef drop_table base; + + drop_table (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + virtual void + drop (sema_rel::table& t, bool migration) + { + pre_statement (); + os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") << + quote_id (t.name ()) << endl; + post_statement (); + } + + virtual void + delete_ (sema_rel::qname const& rtable, + sema_rel::qname const& dtable, + sema_rel::primary_key& rkey, + sema_rel::primary_key& dkey) + { + pre_statement (); + + // This might not be the most efficient way for every database. + // + os << "DELETE FROM " << quote_id (rtable) << endl + << " WHERE EXISTS (SELECT 1 FROM " << quote_id (dtable) << endl + << " WHERE "; + + for (size_t i (0); i != rkey.contains_size (); ++i) + { + if (i != 0) + os << endl + << " AND "; + + os << quote_id (rtable) << "." << + quote_id (rkey.contains_at (i).column ().name ()) << " = " << + quote_id (dtable) << "." << + quote_id (dkey.contains_at (i).column ().name ()); + } + + os << ")" << endl; + + post_statement (); + } + + virtual void + traverse (sema_rel::table& t, bool migration) + { + // By default drop foreign keys referencing tables that would + // have already been dropped on the first pass. + // + if (pass_ == 1) + { + // Drop constraints. In migration this is always done on pass 1. + // + if (migration) + { + instance<drop_foreign_key> dfk (*this); + trav_rel::unames n (*dfk); + names (t, n); + } + else + { + dropped_.insert (t.name ()); // Add it before to cover self-refs. + + instance<drop_foreign_key> dfk (*this, dropped_); + trav_rel::unames n (*dfk); + names (t, n); + } + } + else + { + if (migration && t.extra ()["kind"] == "polymorphic derived object") + { + // If we are dropping a polymorphic derived object, then we + // also have to clean the base tables. Note that this won't + // trigger cascade deletion since we have dropped all the + // keys on pass 1. But we still need to do this in the base + // to root order in order not to trigger other cascades. + // + using sema_rel::model; + using sema_rel::table; + using sema_rel::primary_key; + using sema_rel::foreign_key; + + model& m (dynamic_cast<model&> (t.scope ())); + + table* p (&t); + do + { + // The polymorphic link is the first primary key. + // + for (table::names_iterator i (p->names_begin ()); + i != p->names_end (); ++i) + { + if (foreign_key* fk = dynamic_cast<foreign_key*> ( + &i->nameable ())) + { + p = m.find<table> (fk->referenced_table ()); + assert (p != 0); // Base table should be there. + break; + } + } + + primary_key& rkey (*p->find<primary_key> ("")); + primary_key& dkey (*t.find<primary_key> ("")); + assert (rkey.contains_size () == dkey.contains_size ()); + delete_ (p->name (), t.name (), rkey, dkey); + } + while (p->extra ()["kind"] != "polymorphic root object"); + } + + drop (t, migration); + } + } + + virtual void + traverse (sema_rel::table& t) + { + traverse (t, false); + } + + virtual void + traverse (sema_rel::drop_table& dt) + { + using sema_rel::model; + using sema_rel::changeset; + using sema_rel::table; + + // Find the table we are dropping in the base model. + // + changeset& cs (dynamic_cast<changeset&> (dt.scope ())); + model& bm (cs.base_model ()); + table* t (bm.find<table> (dt.name ())); + assert (t != 0); + traverse (*t, true); + } + + using add_table::traverse; // Unhide. + using alter_table::traverse; // Unhide. + + using table::names; + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + table_set dropped_; + }; + + struct drop_model: trav_rel::model, common + { + typedef drop_model base; + + drop_model (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) + { + } + + virtual void + traverse (sema_rel::model& m) + { + // Traverse named entities in the reverse order. This way we + // drop them in the order opposite to creating. + // + for (sema_rel::model::names_iterator begin (m.names_begin ()), + end (m.names_end ()); begin != end;) + dispatch (*--end); + } + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + // + // Create. + // + + struct create_column: trav_rel::column, + trav_rel::add_column, + trav_rel::alter_column, // Override. + common + { + typedef create_column base; + + create_column (common const& c, + bool override_null = true, + bool* first = 0) + : common (c), + override_null_ (override_null), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + create_column (create_column const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + override_null_ (c.override_null_), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_) + { + } + + virtual void + traverse (sema_rel::column& c) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl + << " "; + create (c); + } + + virtual void + add_header () + { + // By default ADD COLUMN though some databases use just ADD. + // + os << "ADD COLUMN "; + } + + virtual void + traverse (sema_rel::add_column& ac) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl + << " "; + add_header (); + create (ac); + } + + 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<sema_rel::primary_key*> (&i->key ()))) + break; + } + + os << quote_id (c.name ()) << " "; + + type (c, pk != 0 && pk->auto_ ()); + constraints (c, pk); + + if (!c.options ().empty ()) + os << " " << c.options (); + } + + virtual void + constraints (sema_rel::column& c, sema_rel::primary_key* pk) + { + null (c); + + if (!c.default_ ().empty ()) + os << " DEFAULT " << c.default_ (); + + // 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); + } + + virtual void + type (sema_rel::column& c, bool /*auto*/) + { + os << c.type (); + } + + virtual void + null (sema_rel::column& c) + { + bool n (c.null ()); + + // If we are adding a new column that doesn't allow NULL nor has + // a default value, add it as NULL. Later, after migration, we + // will convert it to NOT NULL. + // + if (override_null_ && c.is_a<sema_rel::add_column> () && + !n && c.default_ ().empty ()) + n = true; + + // Specify both cases explicitly for better readability, + // especially in ALTER COLUMN clauses. + // + os << (n ? " NULL" : " NOT NULL"); + } + + virtual void + primary_key () + { + os << " PRIMARY KEY"; + } + + virtual void + auto_ (sema_rel::primary_key&) + { + } + + protected: + bool override_null_; // Override NOT NULL in add_column. + bool& first_; + bool first_data_; + bool add_; + }; + + struct create_primary_key: trav_rel::primary_key, common + { + typedef create_primary_key base; + + create_primary_key (common const& c): common (c) {} + + 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; + + 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 (i != pk.contains_begin ()) + os << "," << endl + << " "; + + os << quote_id (i->column ().name ()); + } + + os << ")"; + } + }; + + struct create_foreign_key: trav_rel::foreign_key, + trav_rel::add_foreign_key, + common + { + typedef create_foreign_key base; + + // Schema constructor, pass 1. + // + create_foreign_key (common const& c, table_set& created, bool* first = 0) + : common (c), + created_ (&created), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + // Schema constructor, pass 2 and migration constructor. + // + create_foreign_key (common const& c, bool* first = 0) + : common (c), + created_ (0), + first_ (first != 0 ? *first : first_data_), + first_data_ (true) + { + } + + create_foreign_key (create_foreign_key const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + created_ (c.created_), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_) + { + } + + virtual void + traverse (sema_rel::foreign_key& fk) + { + if (created_ != 0) + { + // Pass 1. + // + // 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. + // + if (created_->find (fk.referenced_table ()) != created_->end ()) + { + traverse_create (fk); + fk.set (db.string () + "-fk-defined", true); // Mark it as defined. + } + } + else + { + // Pass 2. + // + if (!fk.count (db.string () + "-fk-defined")) + traverse_add (fk); + } + } + + virtual void + traverse_create (sema_rel::foreign_key& fk) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl + << " CONSTRAINT "; + create (fk); + } + + virtual void + traverse_add (sema_rel::foreign_key& fk) + { + if (first_) + first_ = false; + else + os << ","; + + os << endl; + add (fk); + } + + virtual void + traverse (sema_rel::add_foreign_key& afk) + { + traverse_add (afk); + } + + virtual void + add_header () + { + os << "ADD CONSTRAINT "; + } + + virtual void + add (sema_rel::foreign_key& fk) + { + os << " "; + add_header (); + create (fk); + } + + virtual void + create (sema_rel::foreign_key& fk) + { + using sema_rel::foreign_key; + + os << name (fk) << endl + << " FOREIGN KEY ("; + + for (foreign_key::contains_iterator i (fk.contains_begin ()); + i != fk.contains_end (); + ++i) + { + if (i != fk.contains_begin ()) + os << "," << endl + << " "; + + os << quote_id (i->column ().name ()); + } + + string tn (table_name (fk)); + string tn_pad (tn.size (), ' '); + + os << ")" << endl + << " REFERENCES " << tn << " ("; + + foreign_key::columns const& refs (fk.referenced_columns ()); + for (foreign_key::columns::const_iterator i (refs.begin ()); + i != refs.end (); + ++i) + { + if (i != refs.begin ()) + os << "," << endl + << " " << tn_pad; + + os << quote_id (*i); + } + + os << ")"; + + if (fk.on_delete () != foreign_key::no_action) + on_delete (fk.on_delete ()); + + if (!fk.not_deferrable ()) + deferrable (fk.deferrable ()); + } + + 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_type a) + { + using sema_rel::foreign_key; + + switch (a) + { + case foreign_key::no_action: + break; + case foreign_key::cascade: + { + os << endl + << " ON DELETE CASCADE"; + break; + } + case foreign_key::set_null: + { + os << endl + << " ON DELETE SET NULL"; + break; + } + } + } + + virtual void + deferrable (sema_rel::deferrable d) + { + os << endl + << " DEFERRABLE INITIALLY " << d; + } + + protected: + table_set* created_; + bool& first_; + bool first_data_; + }; + + struct create_index: trav_rel::index, common + { + typedef create_index base; + + enum index_type {unique, non_unique, all}; + + create_index (common const& c, index_type t = all) + : common (c), type_ (t) {} + + virtual void + traverse (sema_rel::index& in) + { + if (type_ == unique && + in.type ().find ("UNIQUE") == string::npos && + in.type ().find ("unique") == string::npos) + return; + + if (type_ == non_unique && ( + in.type ().find ("UNIQUE") != string::npos || + in.type ().find ("unique") != string::npos)) + return; + + 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<sema_rel::table&> (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: + index_type type_; + }; + + struct create_table: trav_rel::table, + trav_rel::alter_table, // Override. + common + { + typedef create_table base; + + using trav_rel::table::names; + + create_table (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + virtual void + create_pre (sema_rel::qname const& table) + { + os << "CREATE TABLE " << quote_id (table) << " ("; + } + + virtual void + create_post (sema_rel::table& t) + { + os << ")" << endl; + + if (!t.options ().empty ()) + os << " " << t.options () << endl; + } + + virtual void + create (sema_rel::table& t) + { + pre_statement (); + create_pre (t.name ()); + + instance<create_column> c (*this); + instance<create_primary_key> pk (*this); + + // We will always follow a column, so set first to false. + // + bool f (false); // (Im)perfect forwarding. + bool* pf (&f); // (Im)perfect forwarding. + instance<create_foreign_key> fk (*this, created_, pf); + + trav_rel::unames n; + n >> c; + n >> pk; + n >> fk; + + names (t, n); + + create_post (t); + post_statement (); + + // Create indexes. + // + { + instance<create_index> in (*this); + trav_rel::unames n (*in); + names (t, n); + } + } + + // See if there are any undefined foreign keys that we need to + // add with ALTER TABLE. + // + bool + check_undefined_fk (sema_rel::table& t) + { + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); ++i) + { + if (i->nameable ().is_a<sema_rel::foreign_key> () && + !i->nameable ().count (db.string () + "-fk-defined")) + return true; + } + return false; + } + + virtual void + traverse (sema_rel::table& t) + { + // By default add foreign keys referencing tables that haven't + // yet been defined on the second pass. + // + if (pass_ == 1) + { + // In migration we always add foreign keys on pass 2. + // + if (!t.is_a<sema_rel::add_table> ()) + created_.insert (t.name ()); // Add it before to cover self-refs. + + create (t); + } + else + { + // Add undefined foreign keys. + // + if (check_undefined_fk (t)) + { + pre_statement (); + os << "ALTER TABLE " << quote_id (t.name ()); + + instance<create_foreign_key> cfk (*this); + trav_rel::unames n (*cfk); + names (t, n); + os << endl; + + post_statement (); + } + } + } + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + table_set created_; + }; + + struct create_model: trav_rel::model, common + { + typedef create_model base; + + create_model (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + // + // Alter. + // + + struct alter_column: trav_rel::alter_column, + trav_rel::add_column, + common + { + typedef alter_column base; + + alter_column (common const& c, bool pre, bool* first = 0) + : common (c), + pre_ (pre), + first_ (first != 0 ? *first : first_data_), + first_data_ (true), + fl_ (false), + def_ (c, fl_) + { + } + + alter_column (alter_column const& c) + : root_context (), // @@ -Wextra + context (), + common (c), + pre_ (c.pre_), + first_ (&c.first_ != &c.first_data_ ? c.first_ : first_data_), + first_data_ (c.first_data_), + fl_ (false), + def_ (c, fl_) + { + } + + virtual void + alter_header () + { + os << "ALTER COLUMN "; + } + + virtual void + alter (sema_rel::column& c) + { + // By default use the whole definition. + // + def_->create (c); + } + + 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 << ","; + + os << endl + << " "; + alter_header (); + alter (c); + } + + virtual void + traverse (sema_rel::alter_column& ac) + { + assert (ac.null_altered ()); + traverse (static_cast<sema_rel::column&> (ac)); + } + + virtual void + traverse (sema_rel::add_column& ac) + { + // We initially add NOT NULL columns without default values as + // NULL. Now, after the migration, we convert them to NOT NULL. + // + if (!ac.null () && ac.default_ ().empty ()) + traverse (static_cast<sema_rel::column&> (ac)); + } + + protected: + bool pre_; + bool& first_; + bool first_data_; + bool fl_; // (Im)perfect forwarding. + instance<create_column> def_; + }; + + struct alter_table_common: trav_rel::alter_table, common + { + alter_table_common (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + template <typename T> + T* + check (sema_rel::alter_table& at) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + if (T* x = dynamic_cast<T*> (&i->nameable ())) + return x; + } + return 0; + } + + sema_rel::column* + check_alter_column_null (sema_rel::alter_table& at, bool v) + { + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::add_column; + using sema_rel::alter_column; + + if (alter_column* ac = dynamic_cast<alter_column*> (&i->nameable ())) + { + if (ac->null_altered () && ac->null () == v) + return ac; + } + + // If we are testing for NOT NULL, also look for new columns that + // we initially add as NULL and later convert to NOT NULL. + // + if (!v) + { + if (add_column* ac = dynamic_cast<add_column*> (&i->nameable ())) + { + if (!ac->null () && ac->default_ ().empty ()) + return ac; + } + } + } + return 0; + } + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + struct alter_table_pre: alter_table_common + { + typedef alter_table_pre base; + + alter_table_pre (emitter_type& e, ostream& os, schema_format f) + : alter_table_common (e, os, f) {} + + // Check if there will be any clauses in ALTER TABLE. + // + using alter_table_common::check; + + virtual bool + check (sema_rel::alter_table& at) + { + // If changing the below test, make sure to also update tests + // in database-specific code. + // + return + check<sema_rel::drop_foreign_key> (at) || + check<sema_rel::add_column> (at) || + check_alter_column_null (at, true); + } + + virtual void + alter (sema_rel::alter_table& at) + { + // By default we generate all the alterations in a single ALTER TABLE + // statement. Quite a few databases don't support this. + // + pre_statement (); + os << "ALTER TABLE " << quote_id (at.name ()); + + bool f (true); // Shared first flag. + bool* pf (&f); // (Im)perfect forwarding. + bool tl (true); // (Im)perfect forwarding. + instance<create_column> cc (*this, tl, pf); + instance<alter_column> ac (*this, tl, pf); + instance<drop_foreign_key> dfk (*this, pf); + trav_rel::unames n; + n >> cc; + n >> ac; + n >> dfk; + names (at, n); + os << endl; + + post_statement (); + } + + virtual void + traverse (sema_rel::alter_table& at) + { + if (pass_ == 1) + { + // Drop unique indexes. + // + { + drop_index::index_type it (drop_index::unique); + instance<drop_index> in (*this, it); + trav_rel::unames n (*in); + names (at, n); + } + + if (check (at)) + alter (at); + } + else + { + // Add non-unique indexes. + // + { + create_index::index_type it (create_index::non_unique); + instance<create_index> in (*this, it); + trav_rel::unames n (*in); + names (at, n); + } + } + } + }; + + struct changeset_pre: trav_rel::changeset, common + { + typedef changeset_pre base; + + changeset_pre (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + struct alter_table_post: alter_table_common + { + typedef alter_table_post base; + + alter_table_post (emitter_type& e, ostream& os, schema_format f) + : alter_table_common (e, os, f) {} + + // Check if there will be any clauses in ALTER TABLE. + // + using alter_table_common::check; + + virtual bool + check (sema_rel::alter_table& at) + { + // If changing the below test, make sure to also update tests + // in database-specific code. + // + return + check<sema_rel::add_foreign_key> (at) || + check<sema_rel::drop_column> (at) || + check_alter_column_null (at, false); + } + + virtual void + alter (sema_rel::alter_table& at) + { + // By default we generate all the alterations in a single ALTER TABLE + // statement. Quite a few databases don't support this. + // + pre_statement (); + os << "ALTER TABLE " << quote_id (at.name ()); + + bool f (true); // Shared first flag. + bool* pf (&f); // (Im)perfect forwarding. + bool fl (false); // (Im)perfect forwarding. + instance<drop_column> dc (*this, pf); + instance<alter_column> ac (*this, fl, pf); + instance<create_foreign_key> fk (*this, pf); + + trav_rel::unames n; + n >> dc; + n >> ac; + n >> fk; + names (at, n); + os << endl; + + post_statement (); + } + + virtual void + traverse (sema_rel::alter_table& at) + { + if (pass_ == 1) + { + // Drop non-unique indexes. + // + { + drop_index::index_type it (drop_index::non_unique); + instance<drop_index> in (*this, it); + trav_rel::unames n (*in); + names (at, n); + } + } + else + { + if (check (at)) + alter (at); + + // Add unique indexes. + // + { + create_index::index_type it (create_index::unique); + instance<create_index> in (*this, it); + trav_rel::unames n (*in); + names (at, n); + } + } + } + }; + + struct changeset_post: trav_rel::changeset, common + { + typedef changeset_post base; + + changeset_post (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f) {} + + virtual void + traverse (sema_rel::changeset& m) + { + // Traverse named entities in the reverse order. This way we + // drop them in the order opposite to creating. + // + for (sema_rel::changeset::names_iterator begin (m.names_begin ()), + end (m.names_end ()); begin != end;) + dispatch (*--end); + } + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + unsigned short pass_; + }; + + // + // Schema version table. + // + + struct version_table: common + { + typedef version_table base; + + version_table (emitter_type& e, ostream& os, schema_format f) + : common (e, os, f), + table_ (options.schema_version_table ()[db]), + qt_ (quote_id (table_)), + qs_ (quote_string (options.schema_name ()[db])), + qn_ (quote_id ("name")), + qv_ (quote_id ("version")), + qm_ (quote_id ("migration")) + { + } + + // Create the version table if it doesn't exist. + // + virtual void + create_table () {} + + // Remove the version entry. Called after the DROP statements. + // + virtual void + drop () + { + pre_statement (); + + os << "DELETE FROM " << qt_ << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + // Set the version. Called after the CREATE statements. + // + virtual void + create (sema_rel::version) {} + + // Set the version and migration state to true. Called after + // the pre migration statements. + // + virtual void + migrate_pre (sema_rel::version v) + { + pre_statement (); + + os << "UPDATE " << qt_ << endl + << " SET " << qv_ << " = " << v << ", " << qm_ << " = 1" << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + // Set migration state to false. Called after the post migration + // statements. + // + virtual void + migrate_post () + { + pre_statement (); + + os << "UPDATE " << qt_ << endl + << " SET " << qm_ << " = 0" << endl + << " WHERE " << qn_ << " = " << qs_ << endl; + + post_statement (); + } + + protected: + sema_rel::qname table_; + string qt_; // Quoted table. + string qs_; // Quoted schema name string. + string qn_; // Quoted name column. + string qv_; // Quoted version column. + string qm_; // Quoted migration column. + }; + + // + // 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 () + { + } + }; + } +} + +#endif // ODB_RELATIONAL_SCHEMA_HXX diff --git a/odb/odb/relational/source.cxx b/odb/odb/relational/source.cxx new file mode 100644 index 0000000..abc0a46 --- /dev/null +++ b/odb/odb/relational/source.cxx @@ -0,0 +1,6345 @@ +// file : odb/relational/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <map> + +#include <odb/gcc.hxx> + +#include <odb/lookup.hxx> +#include <odb/cxx-lexer.hxx> + +#include <odb/relational/source.hxx> +#include <odb/relational/generate.hxx> + +using namespace std; + +void relational::source::class_:: +traverse_object (type& c) +{ + using semantics::data_member; + + data_member_path* id (id_member (c)); + data_member* idf (id ? id->front () : 0); + data_member* idb (id ? id->back () : 0); + bool auto_id (id && auto_ (*id)); + bool base_id (id && &idf->scope () != &c); // Comes from base. + + data_member* opt (optimistic (c)); + + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + type* poly_base (poly_derived ? &polymorphic_base (c) : 0); + size_t poly_depth (poly_derived ? polymorphic_depth (c) : 1); + data_member* discriminator (poly ? context::discriminator (*poly_root) : 0); + + bool abst (abstract (c)); + bool reuse_abst (abst && !poly); + bool readonly (context::readonly (c)); + + bool grow (false); + bool grow_id (false); + + if (generate_grow) + { + grow = context::grow (c); + grow_id = (id ? context::grow (*idb) : false) || + (opt ? context::grow (*opt) : false); + } + + column_count_type const& cc (column_count (c)); + bool versioned (context::versioned (c)); + + // Schema name as a string literal or empty. + // + string schema_name (options.schema_name ()[db]); + if (!schema_name.empty ()) + schema_name = strlit (schema_name); + + string const& type (class_fq_name (c)); + string traits ("access::object_traits_impl< " + type + ", id_" + + db.string () + " >"); + + user_sections& uss (c.get<user_sections> ("user-sections")); + user_sections* buss (poly_base != 0 + ? &poly_base->get<user_sections> ("user-sections") + : 0); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + object_extra (c); + + // + // Query (abstract and concrete). + // + + // query_columns + // + if (options.generate_query ()) + query_columns_type_->traverse (c); + + // Statement cache (definition). + // + if (!reuse_abst && id != 0) + { + bool sections (false); + bool containers (has_a (c, test_container)); + + os << "struct " << traits << "::extra_statement_cache_type" + << "{"; + + instance<container_cache_members> cm; + cm->traverse (c); + + if (containers) + os << endl; + + instance<section_cache_members> sm; + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip the special version update section in reuse inheritance (we + // always treat it as abstract). + // + if (i->special == user_section::special_version && !poly) + continue; + + // Generate an entry for a readonly section in optimistic + // polymorphic root since it can be overridden and we may + // need to update the version. + // + if (!i->empty () || (poly && i->optimistic ())) + { + sm->traverse (*i); + sections = true; + } + } + + if (sections) + os << endl; + + os << "extra_statement_cache_type (" << endl + << db << "::connection&" << (containers || sections ? " c" : "") << + "," << endl + << "image_type&" << (sections ? " im" : "") << "," << endl + << "id_image_type&" << (sections ? " idim" : "") << "," << endl + << db << "::binding&" << (containers || sections ? " id" : "") << + "," << endl + << db << "::binding&" << (sections ? " idv" : ""); + + extra_statement_cache_extra_args (containers, sections); + + os << ")"; + + instance<container_cache_init_members> cim; + cim->traverse (c); + + instance<section_cache_init_members> sim (!containers); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + if (i->special == user_section::special_version && !poly) + continue; + + if (!i->empty () || (poly && i->optimistic ())) + sim->traverse (*i); + } + + os << "{" + << "}" + << "};"; + } + + // + // Containers (abstract and concrete). + // + + { + instance<container_traits> t (c); + t->traverse (c); + } + + // + // Sections (abstract and concrete). + // + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + instance<section_traits> t (c); + t->traverse (*i); + } + + // + // Functions (abstract and concrete). + // + + // id(), version() + // + if (!poly_derived && id != 0 && !base_id) + { + // id (id_image_type) + // + if (auto_id) + { + os << traits << "::id_type" << endl + << traits << "::" << endl + << "id (const id_image_type& i)" + << "{" + << db << "::database* db (0);" + << "ODB_POTENTIALLY_UNUSED (db);" + << endl + << "id_type id;"; + init_id_value_member_id_image_->traverse (*idb); + os << "return id;" + << "}"; + } + + // id (image) + // + if (options.generate_query ()) + { + os << traits << "::id_type" << endl + << traits << "::" << endl + << "id (const image_type& i)" + << "{" + << db << "::database* db (0);" + << "ODB_POTENTIALLY_UNUSED (db);" + << endl + << "id_type id;"; + + // Handle nested id. + // + if (id->size () > 1) + { + string var; + + for (data_member_path::const_iterator i (id->begin ()); + i != id->end (); + ++i) + { + // The same logic as in member_base. + // + if (!var.empty ()) + var += "value."; // Composite. + + string const& name ((*i)->name ()); + var += name; + + if (name[name.size () - 1] != '_') + var += '_'; + } + + instance<init_value_member> t ("id", var); + t->traverse (*idb); + } + else + init_id_value_member_->traverse (*idb); + + os << "return id;" + << "}"; + } + + // version (image) + // + if (opt != 0) + { + os << traits << "::version_type" << endl + << traits << "::" << endl + << "version (const image_type& i)" + << "{" + << "version_type v;"; + init_version_value_member_->traverse (*opt); + os << "return v;" + << "}"; + } + } + + // discriminator(image) + // + if (poly && !poly_derived) + { + os << traits << "::discriminator_type" << endl + << traits << "::" << endl + << "discriminator (const image_type& i)" + << "{" + << db << "::database* db (0);" + << "ODB_POTENTIALLY_UNUSED (db);" + << endl + << "discriminator_type d;"; + init_discriminator_value_member_->traverse (*discriminator); + os << "return d;" + << "}"; + } + + // grow () + // + if (generate_grow) + { + os << "bool " << traits << "::" << endl + << "grow (image_type& i," << endl + << truncated_vector << " t"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + if (poly_derived) + os << "," << endl + << "std::size_t d"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (t);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "bool grew (false);" + << endl; + + index_ = 0; + + if (poly_derived) + { + // Select column count for this class. + // + size_t cols (cc.total - cc.id); + + os << "// " << class_name (*poly_base) << " base" << endl + << "//" << endl + << "if (--d != 0)" + << "{" + << "if (base_traits::grow (*i.base, " << + "t + " << cols << "UL" << + (context::versioned (*poly_base) ? ", svm" : "") << + (poly_base != poly_root ? ", d" : "") << "))" << endl + << "i.base->version++;" + << "}"; + } + else + inherits (c, grow_base_inherits_); + + names (c, grow_member_names_); + + os << "return grew;" + << "}"; + } + + // bind (image_type) + // + os << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b," << endl; + + // If we are a derived type in a polymorphic hierarchy, then + // we get the external id binding. + // + if (poly_derived) + os << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl; + + os << "image_type& i," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + if (readonly) + os << "assert (sk != statement_update);" + << endl; + + os << "std::size_t n (0);" + << endl; + + if (poly_derived) + { + // The id reference comes first in the insert statement. + // + os << "// " << idf->name () << endl + << "//" << endl + << "if (sk == statement_insert)" + << "{" + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << "}"; + } + else + inherits (c, bind_base_inherits_); + + names (c, bind_member_names_); + + if (poly_derived) + { + // The id reference comes last in the update statement. + // + if (!readonly) + os << "// " << idf->name () << endl + << "//" << endl + << "if (sk == statement_update)" + << "{" + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << "}"; + + // Bind the image chain for the select statement. Seeing that + // this is the last statement in the function, we don't care + // about updating n. + // + os << "// " << class_name (*poly_base) << " base" << endl + << "//" << endl + << "if (sk == statement_select)" << endl + << "base_traits::bind (b + n, "; + + if (poly_base != poly_root) + os << "id, id_size, "; + + os << "*i.base, sk" << + (context::versioned (*poly_base) ? ", svm" : "") << ");"; + } + + os << "}"; + + // bind (id_image_type) + // + if (!poly_derived && id != 0 && !base_id) + { + os << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b, id_image_type& i" << + (opt != 0 ? ", bool bv" : "") << ")" + << "{" + << "std::size_t n (0);"; + + if (composite_wrapper (utype (*id))) + os << db << "::statement_kind sk (" << db << "::statement_select);"; + + bind_id_member_->traverse (*idb); + + if (opt != 0) + { + os << "if (bv)" + << "{" + << "n += " << column_count (c).id << ";" + << endl; + + bind_version_member_->traverse (*opt); + os << "}"; + } + + os << "}"; + } + + // init (image, object) + // + os << (generate_grow ? "bool " : "void ") << traits << "::" << endl + << "init (image_type& i," << endl + << "const object_type& o," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + if (readonly) + os << "assert (sk != statement_update);" + << endl; + + init_image_pre (c); + + if (generate_grow) + os << "bool grew (false);" + << endl; + + if (!poly_derived) + inherits (c, init_image_base_inherits_); + + names (c, init_image_member_names_); + + if (generate_grow) + os << "return grew;"; + + os << "}"; + + // init (object, image) + // + os << "void " << traits << "::" << endl + << "init (object_type& o," << endl + << "const image_type& i," << endl + << "database* db"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + if (poly_derived) + os << "," << endl + << "std::size_t d"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + if (poly_derived) + { + os << "// " << class_name (*poly_base) << " base" << endl + << "//" << endl + << "if (--d != 0)" << endl + << "base_traits::init (o, *i.base, db" << + (context::versioned (*poly_base) ? ", svm" : "") << + (poly_base != poly_root ? ", d" : "") << ");" + << endl; + } + else + inherits (c, init_value_base_inherits_); + + names (c, init_value_member_names_); + + os << "}"; + + // init (id_image, id) + // + if (id != 0 && !base_id) + { + os << "void " << traits << "::" << endl + << "init (id_image_type& i, const id_type& id" << + (opt != 0 ? ", const version_type* v" : "") << ")" + << "{"; + + if (grow_id) + os << "bool grew (false);"; + + if (composite_wrapper (utype (*id))) + os << db << "::statement_kind sk (" << db << "::statement_select);"; + + init_id_image_member_->traverse (*idb); + + if (opt != 0) + { + // Here we rely on the fact that init_image_member + // always wraps the statements in a block. + // + os << "if (v != 0)"; + init_version_image_member_->traverse (*opt); + } + + if (grow_id) + os << "if (grew)" << endl + << "i.version++;"; + + os << "}"; + } + + // The rest does not apply to reuse-abstract objects. + // + if (reuse_abst) + return; + + // + // Containers (concrete). + // + + // + // Sections (concrete). + // + + // Polymorphic map. + // + if (poly) + { + if (!poly_derived) + os << traits << "::map_type*" << endl + << traits << "::map;" + << endl; + + // Sections. Do we have any new sections or overrides? + // + bool sections ( + uss.count (user_sections::count_new | + user_sections::count_override | + user_sections::count_load | + user_sections::count_update | + user_sections::count_special_version | + (poly ? user_sections::count_optimistic : 0)) != 0); + if (sections) + { + string const& fn (flat_name (type)); + + size_t n (uss.count (user_sections::count_total | + user_sections::count_all | + user_sections::count_special_version)); + + map<size_t, user_section*> m; + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + m[i->index] = &*i; + + os << "static const "<< traits << "::" << (abst ? "abstract_" : "") << + "info_type::section_functions" << endl + << "section_functions_for_" << fn << "[] =" + << "{"; + + for (size_t i (0); i != n; ++i) + { + os << (i != 0 ? "," : "") << "{"; + + map<size_t, user_section*>::iterator j (m.find (i)); + + if (j != m.end ()) + { + user_section& s (*j->second); + string n (public_name (*s.member)); + + // load + // + if (s.load_empty ()) + os << "0"; + else + os << "&odb::section_load_impl< " << type << ", id_" << db << + ", " << traits << "::" << n << "_traits >"; + + os << "," << endl; + + // update + // + // Generate an entry for a readonly section in optimistic + // polymorphic root since it can be overridden and we may + // need to update the version. + // + if (s.update_empty () && !(poly && s.optimistic ())) + os << "0"; + else + os << "&odb::section_update_impl< " << type << ", id_" << db << + ", " << traits << "::" << n << "_traits >"; + } + else + os << "0," << endl + << "0"; + + os << "}"; + } + + os << "};"; + + os << "static const "<< traits << "::" << (abst ? "abstract_" : "") << + "info_type::section_list" << endl + << "section_list_for_" << fn << " =" + << "{" + << n << "UL," << endl + << "section_functions_for_" << fn + << "};"; + } + + // + // + os << "const " << traits << "::" << (abst ? "abstract_" : "") << + "info_type" << endl + << traits << "::info (" << endl + << "typeid (" << type << ")," << endl; + + if (poly_derived) + os << "&object_traits_impl< " << class_fq_name (*poly_base) << + ", id_" << db << " >::info," << endl; + else + os << "0," << endl; + + // Sections. + // + if (sections) + os << "§ion_list_for_" << flat_name (type); + else + os << "0"; + + if (!abst) + { + os << "," << endl + << strlit (string (type, 2, string::npos)) << "," << endl + << "&odb::create_impl< " << type << " >," << endl + << "&odb::dispatch_impl< " << type << ", id_" << db << " >," << endl; + + if (poly_derived) + os << "&statements_type::delayed_loader"; + else + os << "0"; + } + + os << ");" + << endl; + + if (!abst) + os << "static const " << traits << "::entry_type" << endl + << "polymorphic_entry_for_" << flat_name (type) << ";" + << endl; + } + + // + // Statements. + // + bool update_columns ( + cc.total != cc.id + cc.inverse + cc.readonly + cc.separate_update); + + qname table (table_name (c)); + string qtable (quote_id (table)); + + // persist_statement + // + { + string sep (versioned ? "\n" : " "); + + statement_columns sc; + { + statement_kind sk (statement_insert); // Imperfect forwarding. + instance<object_columns> ct (sk, sc); + ct->traverse (c); + process_statement_columns (sc, statement_insert, versioned); + } + + bool dv (sc.empty ()); // The DEFAULT VALUES syntax. + + os << "const char " << traits << "::persist_statement[] =" << endl + << strlit ("INSERT INTO " + qtable + sep) << endl; + + for (statement_columns::const_iterator b (sc.begin ()), i (b), + e (sc.end ()); i != e;) + { + string s; + + if (i == b) + s += '('; + s += i->column; + s += (++i != e ? ',' : ')'); + s += sep; + + os << strlit (s) << endl; + } + + instance<query_parameters> qp (statement_insert, table); + + string extra (persist_statement_extra (c, *qp, persist_after_columns)); + + if (!extra.empty ()) + os << strlit (extra + sep) << endl; + + string values; + if (!dv) + { + os << strlit ("VALUES" + sep) << endl; + + values += '('; + instance<persist_statement_params> pt (values, *qp, sep); + pt->traverse (c); + values += ')'; + } + else + values = "DEFAULT VALUES"; + + extra = persist_statement_extra (c, *qp, persist_after_values); + + if (!extra.empty ()) + values += sep; + + os << strlit (values); + + if (!extra.empty ()) + os << endl + << strlit (extra); + + os << ";" + << endl; + } + + // Index of the first empty SELECT statement in poly-derived + // statement list. All subsequent statements are also empty. + // The first statement can never be empty (contains id and + // type_id). + // + size_t empty_depth (0); + + if (id != 0) + { + string sep (versioned ? "\n" : " "); + + instance<object_columns_list> id_cols; + id_cols->traverse (*id); + + std::vector<size_t> find_column_counts (abst ? 1 : poly_depth); + + // find_statement + // + if (poly_derived) + os << "const char* const " << traits << "::find_statements[] =" + << "{"; + else + os << "const char " << traits << "::find_statement[] =" << endl; + + for (size_t d (poly_depth); d != 0;) + { + statement_columns sc; + { + statement_kind sk (statement_select); // Imperfect forwarding. + object_section* s (&main_section); // Imperfect forwarding. + instance<object_columns> t (qtable, sk, sc, d, s); + t->traverse (c); + process_statement_columns (sc, statement_select, versioned); + find_column_counts[poly_depth - d] = sc.size (); + } + + if (sc.size () != 0) + { + os << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + os << strlit ("FROM " + qtable + sep) << endl; + + if (d != 1) + { + bool f (false); // @@ (im)perfect forwarding + size_t d1 (d - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (c, f, d1); + j->traverse (polymorphic_base (c)); + + for (strings::const_iterator i (j->begin ()); i != j->end (); ++i) + os << strlit (*i + sep) << endl; + } + + { + // For the find statement, don't join section members. + // + bool f (false); // @@ (im)perfect forwarding + object_section* s (&main_section); // @@ (im)perfect forwarding + instance<object_joins> j (c, f, d, s); + j->traverse (c); + + for (strings::const_iterator i (j->begin ()); i != j->end (); ++i) + os << strlit (*i + sep) << endl; + } + + string where ("WHERE "); + instance<query_parameters> qp (statement_select, table); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where); + } + else + os << strlit (""); // Empty SELECT statement. + + if (abst) + break; + + if (--d != 0) + os << "," << endl + << endl; + } + + if (poly_derived) + os << "};"; + else + os << ";" + << endl; + + // find_column_counts + // + if (poly_derived) + { + os << "const std::size_t " << traits << "::find_column_counts[] =" + << "{"; + + for (std::vector<size_t>::iterator b (find_column_counts.begin ()), + i (b), e (find_column_counts.end ()); i != e;) + { + os << *i << "UL"; + + if (*i == 0 && empty_depth == 0) + empty_depth = i - b; + + if (++i != e) + os << ',' << endl; + } + + os << "};"; + } + + // find_discriminator_statement + // + if (poly && !poly_derived) + { + statement_columns sc; + { + statement_kind sk (statement_select); // Imperfect forwarding. + instance<object_columns> t (qtable, sk, sc); + t->traverse (*discriminator); + + if (opt != 0) + t->traverse (*opt); + + process_statement_columns (sc, statement_select, false); + } + + os << "const char " << traits << "::" << endl + << "find_discriminator_statement[] =" << endl + << strlit ("SELECT ") << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? ", " : " ")) << endl; + } + + os << strlit ("FROM " + qtable + " ") << endl; + + string where ("WHERE "); + instance<query_parameters> qp (statement_select, table); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where) << ";" + << endl; + } + + // update_statement + // + if (update_columns) + { + string sep (versioned ? "\n" : " "); + + instance<query_parameters> qp (statement_update, table); + + statement_columns sc; + { + query_parameters* p (qp.get ()); // Imperfect forwarding. + statement_kind sk (statement_update); // Imperfect forwarding. + object_section* s (&main_section); // Imperfect forwarding. + instance<object_columns> t (sk, sc, p, s); + t->traverse (c); + process_statement_columns (sc, statement_update, versioned); + } + + os << "const char " << traits << "::update_statement[] =" << endl + << strlit ("UPDATE " + qtable + sep) << endl + << strlit ("SET" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + // This didn't work out: cannot change the identity column. + // + //if (sc.empty ()) + //{ + // // We can end up with nothing to set if we need to "touch" a row + // // in order to increment its optimistic concurrency version. In + // // this case just do a dummy assignment based on the id column. + // // + // string const& c (quote_id (id_cols->begin ()->name)); + // os << strlit (c + "=" + c) << endl; + //} + + string extra (update_statement_extra (c)); + + if (!extra.empty ()) + os << strlit (extra + sep) << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + // Top-level version column. + // + if (opt != 0 && !poly_derived) + { + string name (column_qname (*opt, column_prefix ())); + string type (column_type (*opt)); + + where += " AND " + name + "=" + + convert_to (qp->next (*opt, name, type), type, *opt); + } + + os << strlit (where) << ";" + << endl; + } + + // erase_statement + // + { + instance<query_parameters> qp (statement_delete, table); + os << "const char " << traits << "::erase_statement[] =" << endl + << strlit ("DELETE FROM " + qtable + " ") << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where) << ";" + << endl; + } + + if (opt != 0 && !poly_derived) + { + instance<query_parameters> qp (statement_delete, table); + + os << "const char " << traits << "::optimistic_erase_statement[] " << + "=" << endl + << strlit ("DELETE FROM " + qtable + " ") << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + // Top-level version column. + // + string name (column_qname (*opt, column_prefix ())); + string type (column_type (*opt)); + + where += " AND " + name + "=" + + convert_to (qp->next (*opt, name, type), type, *opt); + + os << strlit (where) << ";" + << endl; + } + } + + // query_statement + // + bool query_optimize (false); + if (options.generate_query ()) + { + // query_statement + // + strings joins; + if (poly_depth != 1) + { + bool t (true); //@@ (im)perfect forwarding + size_t d (poly_depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (c, t, d); + j->traverse (polymorphic_base (c)); + joins = j->joins; + } + + if (id != 0) + { + // For the query statement we also join section members so that they + // can be used in the WHERE clause. + // + bool t (true); //@@ (im)perfect forwarding + instance<object_joins> j (c, t, poly_depth); + j->traverse (c); + joins.insert (joins.end (), j->begin (), j->end ()); + } + + query_optimize = !joins.empty (); + + statement_columns sc; + { + statement_kind sk (statement_select); //@@ Imperfect forwarding. + object_section* s (&main_section); //@@ Imperfect forwarding. + instance<object_columns> oc (qtable, sk, sc, poly_depth, s); + oc->traverse (c); + process_statement_columns ( + sc, statement_select, versioned || query_optimize); + } + + string sep (versioned || query_optimize ? "\n" : " "); + + os << "const char " << traits << "::query_statement[] =" << endl + << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + string prev ("FROM " + qtable); + + for (strings::const_iterator i (joins.begin ()); i != joins.end (); ++i) + { + os << strlit (prev + sep) << endl; + prev = *i; + } + + os << strlit (prev) << ";" + << endl; + + // erase_query_statement + // + os << "const char " << traits << "::erase_query_statement[] =" << endl + << strlit ("DELETE FROM " + qtable) << ";" + << endl; + + // table_name + // + os << "const char " << traits << "::table_name[] =" << endl + << strlit (qtable) << ";" // Use quoted name. + << endl; + } + + // persist () + // + size_t persist_containers (has_a (c, test_straight_container)); + bool persist_versioned_containers ( + persist_containers > + has_a (c, + test_straight_container | + exclude_deleted | exclude_added | exclude_versioned)); + + os << "void " << traits << "::" << endl + << "persist (database& db, " << (auto_id ? "" : "const ") << + "object_type& obj"; + + if (poly) + os << ", bool top, bool dyn"; + + os << ")" + << "{"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (top);" + << endl; + + os << "using namespace " << db << ";" + << endl; + + if (poly) + os << "if (dyn)" << endl + << "{" + << "const std::type_info& t (typeid (obj));" + << endl + << "if (t != info.type)" + << "{" + << "const info_type& pi (root_traits::map->find (t));" + << "pi.dispatch (info_type::call_persist, db, &obj, 0);" + << "return;" + << "}" + << "}"; + + // If we are database-poly-abstract but not C++-abstract, then make + // sure we are not trying to persist an instance of an abstract class. + // + if (abst && !c.abstract ()) + os << "if (top)" << endl + << "throw abstract_class ();" + << endl; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned || + persist_versioned_containers || + uss.count (user_sections::count_new | + user_sections::count_all | + user_sections::count_versioned_only) != 0) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Call callback (pre_persist). + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "callback (db," << endl + << (auto_id ? "static_cast<const object_type&> (obj)," : "obj,") << endl + << "callback_event::pre_persist);" + << endl; + } + + // Call our base if we are a derived type in a polymorphic + // hierarchy. + // + if (poly_derived) + os << "base_traits::persist (db, obj, false, false);" + << endl; + + os << "image_type& im (sts.image ());" + << "binding& imb (sts.insert_image_binding ());"; + + if (poly_derived) + os << "const binding& idb (sts.id_image_binding ());"; + + os << endl; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_insert" << (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "im.version++"; + + os << ";" + << endl; + + if (!poly_derived && auto_id && insert_send_auto_id) + { + string const& n (idf->name ()); + string var ("im." + n + (n[n.size () - 1] == '_' ? "" : "_")); + init_auto_id (*idf, var); // idf == idb, since auto + } + + os << "if ("; + + if (poly_derived) + os << "idb.version != sts.insert_id_binding_version () ||" << endl; + + os << "im.version != sts.insert_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, "; + + if (poly_derived) + os << "idb.bind, idb.count, "; + + os << "im, statement_insert" << (versioned ? ", svm" : "") << ");"; + + if (poly_derived) + os << "sts.insert_id_binding_version (idb.version);"; + + os << "sts.insert_image_version (im.version);" + << "imb.version++;" + << "}"; + + // Bind id image since that's where the returned id and/or version will + // be extracted. + // + if (!poly_derived) + { + bool bv (opt != 0 && optimistic_insert_bind_version (*opt)); + if (bv || auto_id) + { + os << "{" + << "id_image_type& i (sts.id_image ());" + << "binding& b (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || b.version == 0)" + << "{" + << "bind (b.bind, i);" + << "sts.id_image_version (i.version);" + << "b.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}" + << "}"; + } + } + + os << "insert_statement& st (sts.persist_statement ());" + << "if (!st.execute ())" << endl + << "throw object_already_persistent ();" + << endl; + + if (!poly_derived && auto_id) // idf == idb since auto + { + set_member (*idf, "obj", "id (sts.id_image ())", "db", "id_type"); + os << endl; + } + + // Set the optimistic concurrency version in the object member. + // + if (opt != 0 && !poly_derived) + { + // If we don't have auto id, then obj is a const reference. + // + set_member (*opt, + (auto_id ? "obj" : "const_cast<object_type&> (obj)"), + optimistic_version_init (*opt), + "", // No database. + "version_type"); + os << endl; + } + + // Initialize id_image and binding if we are a root of a polymorphic + // hierarchy or if we have non-inverse containers. + // + if (!poly_derived && (poly || persist_containers)) + { + // If this is a polymorphic root without containers, then we only + // need to do this if we are not a top-level call. If we are poly- + // abstract, then top will always be false. + // + if (poly && !persist_containers && !abst) + os << "if (!top)" + << "{"; + + os << "id_image_type& i (sts.id_image ());" + << "init (i, id (obj));" + << endl + << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + + if (poly && !persist_containers && !abst) + os << "}"; + } + + if (persist_containers) + { + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + instance<container_calls> t (container_calls::persist_call); + t->traverse (c); + } + + // Reset sections: loaded, unchanged. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + // Skip overridden sections; they have been reset by the base. + // + if (i->base != 0 && poly_derived) + continue; + + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" << endl; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + } + + // Call callback (post_persist). + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "callback (db," << endl + << (auto_id ? "static_cast<const object_type&> (obj)," : "obj,") << endl + << "callback_event::post_persist);"; + } + + os << "}"; + + // persist() bulk + // + if (c.count ("bulk-persist")) + { + os << "void " << traits << "::" << endl + << "persist (database& db," << endl + << (auto_id ? "" : "const ") << "object_type** objs," << endl + << "std::size_t n," << endl + << "multiple_exceptions& mex)" + << "{" + << "using namespace " << db << ";" + << endl; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned || + uss.count (user_sections::count_new | + user_sections::count_all | + user_sections::count_versioned_only) != 0) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + os << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "const object_type& obj (*objs[i]);" + << "callback (db, obj, callback_event::pre_persist);" + //@@ assumption: generate_grow is false or it only affects select (like + // in pgsql) so all we have to do is to increment image + // version if it grew. + //@@ assumption: insert_send_auto_id is false + << "image_type& im (sts.image (i));"; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_insert" << (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << " && i == 0)" << endl + << "im.version++"; + + os << ";" + << "}"; + + //@@ assumption: generate_grow: as above + os << "binding& imb (sts.insert_image_binding ());" + << "if (imb.version == 0)" + << "{" + << "bind (imb.bind, sts.image (), statement_insert" << + (versioned ? ", svm" : "") << ");" + << "imb.version++;" + << "}"; + + // Bind id image since that's where the returned id and/or version will + // be extracted. + // + bool bv (opt != 0 && optimistic_insert_bind_version (*opt)); + if (bv || auto_id) + { + os << "binding& idb (sts.id_image_binding ());" + //@@ assumption: generate_grow: as above + << "if (idb.version == 0)" + << "{" + << "bind (idb.bind, sts.id_image ());" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + os << "insert_statement& st (sts.persist_statement ());" + << "n = st.execute (n, mex);" // Actual number of rows attempted. + << endl; + + os << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "bool r (st.result (i));" // Sets current in mex. + << endl + << "if (mex[i] != 0)" << endl // Pending exception for this position. + << "continue;" + << endl + << "if (!r)" + << "{" + << "mex.insert (i, object_already_persistent ());" + << "continue;" + << "}" + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "continue;" + << endl + << (auto_id ? "" : "const ") << "object_type& obj (*objs[i]);" + << endl; + + // Extract auto id. + // + if (auto_id) // idb == idf, since auto + { + set_member (*idf, "obj", "id (sts.id_image (i))", "db", "id_type"); + os << endl; + } + + // Set the optimistic concurrency version. + // + if (opt != 0) + { + // If we don't have auto id, then obj is a const reference. + // + set_member (*opt, + (auto_id ? "obj" : "const_cast<object_type&> (obj)"), + optimistic_version_init (*opt, true), + "", // No database. + "version_type"); + os << endl; + } + + // Reset sections: loaded, unchanged. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" << endl; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + } + + os << "callback (db," << endl + << (auto_id ? "static_cast<const object_type&> (obj)," : "obj,") << endl + << "callback_event::post_persist);" + << "}" // for + << "}"; // persist () + } + + // update () + // + if (id != 0 && (!readonly || poly)) + { + size_t update_containers ( + has_a (c, test_readwrite_container, &main_section)); + + bool update_versioned_containers ( + update_containers > + has_a (c, + test_readwrite_container | + exclude_deleted | exclude_added | exclude_versioned, + &main_section)); + + // See if we have any sections that we might have to update. + // + bool sections (false); + bool versioned_sections (false); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // This test will automatically skip the special version update section. + // + if (i->update_empty () || i->update == user_section::update_manual) + continue; + + sections = true; + + if (added (*i->member) || deleted (*i->member)) + versioned_sections = true; + + // For change-updated sections, also check if we have any + // versioned smart containers. + // + if (i->update != user_section::update_change) + continue; + + if (has_a (c, test_smart_container, &*i) != + has_a (c, test_smart_container | exclude_deleted | exclude_added, &*i)) + versioned_sections = true; + } + + os << "void " << traits << "::" << endl + << "update (database& db, const object_type& obj"; + + if (poly) + os << ", bool top, bool dyn"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (top);"; + + os << endl + << "using namespace " << db << ";" + << "using " << db << "::update_statement;" + << endl; + + if (poly) + os << "if (dyn)" << endl + << "{" + << "const std::type_info& t (typeid (obj));" + << endl + << "if (t != info.type)" + << "{" + << "const info_type& pi (root_traits::map->find (t));" + << "pi.dispatch (info_type::call_update, db, &obj, 0);" + << "return;" + << "}" + << "}"; + + // If we are database-poly-abstract but not C++-abstract, then make + // sure we are not trying to update an instance of an abstract class. + // + if (abst && !c.abstract ()) + os << "if (top)" << endl + << "throw abstract_class ();" + << endl; + + // If we are readonly, then there is nothing else to do. + // + if (!readonly) + { + // Call callback (pre_update). + // + if (!abst) // If we are poly-abstract then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "callback (db, obj, callback_event::pre_update);" + << endl; + } + + bool sts (false); + + if ((versioned && update_columns) || + update_versioned_containers || + versioned_sections) + { + sts = true; + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));" + << endl; + } + + // If we have change-updated sections that contain change-tracking + // containers, then mark such sections as changed if any of the + // containers were changed. Do this before calling the base so that + // we account for the whole hierarchy before we actually start + // updating any sections (important for optimistic concurrency + // version). + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + user_section& s (*i); + + if (s.update != user_section::update_change) + continue; + + if (!has_a (c, test_smart_container, &s)) + continue; + + data_member& m (*s.member); + + os << "// " << m.name () << endl + << "//" << endl; + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")"; + } + + os << "{"; + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // VC++ cannot grok the constructor syntax. + // + os << "const odb::section& s = " << ma.translate ("obj") << ";" + << endl; + + os << "if ("; + + // Unless we are always loaded, test for being loaded. + // + if (s.load != user_section::load_eager) + os << "s.loaded () && "; + + // And for not already being changed. + // + os << "!s.changed ())" + << "{"; + + instance<container_calls> t (container_calls::section_call, &s); + t->traverse (c); + + os << "}" // if + << "}"; + } + + if (poly_derived) + { + bool readonly_base (context::readonly (*poly_base)); + + if (!sts && (readonly_base || + update_columns || + update_containers || + sections)) + { + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + } + + // Unless our base is readonly, call it first. + // + if (!readonly_base) + { + os << "base_traits::update (db, obj, false, false);" + << endl; + } + else + { + // Otherwise, we have to initialize the id image ourselves. If + // we don't have any columns, containers or sections to update, + // then we only have to do it if this is not a top-level call. + // If we are abstract, then top is always false. + // + if (!update_columns && !update_containers && !sections && !abst) + os << "if (!top)"; + + os << "{" + << "id_image_type& i (sts.id_image ());" + << "init (i, id (obj));" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;" + // Optimistic poly base cannot be readonly. + << "}" + << "}"; + } + + if (update_columns) + { + // Initialize the object image. + // + os << "image_type& im (sts.image ());"; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_update" << + (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "im.version++"; + + os << ";" + << endl; + + os << "const binding& idb (sts.id_image_binding ());" + << "binding& imb (sts.update_image_binding ());" + << "if (idb.version != sts.update_id_binding_version () ||" << endl + << "im.version != sts.update_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, idb.bind, idb.count, im, statement_update" << + (versioned ? ", svm" : "") << ");" + << "sts.update_id_binding_version (idb.version);" + << "sts.update_image_version (im.version);" + << "imb.version++;" + << "}"; + + os << "update_statement& st (sts.update_statement ());" + << "if ("; + + if (versioned) + os << "!st.empty () && "; + + os << "st.execute () == 0)" << endl + << "throw object_not_persistent ();" + << endl; + } + + // Otherwise, nothing else to do here if we don't have any columns + // to update. + } + else if (update_columns) + { + if (!sts) + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + + // Initialize id image. + // + if (opt != 0) + os << "const version_type& v (version (obj));"; + + os << "id_image_type& idi (sts.id_image ());" + << "init (idi, id (obj)" << (opt != 0 ? ", &v" : "") << ");" + << endl; + + // Initialize object image. + // + os << "image_type& im (sts.image ());"; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_update" << + (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "im.version++"; + + os << ";" + << endl; + + // The update binding is bound to two images (object and id) + // so we have to track both versions. + // + os << "bool u (false);" // Avoid incrementing version twice. + << "binding& imb (sts.update_image_binding ());" + << "if (im.version != sts.update_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_update" << + (versioned ? ", svm" : "") << ");" + << "sts.update_image_version (im.version);" + << "imb.version++;" + << "u = true;" + << "}"; + + // To update the id part of the update binding we have to do + // it indirectly via the id binding, which just points to the + // suffix of the update bind array (see object_statements). + // + os << "binding& idb (sts.id_image_binding ());" + << "if (idi.version != sts.update_id_image_version () ||" << endl + << "idb.version == 0)" + << "{" + // If the id binding is up-to-date, then that means update + // binding is too and we just need to update the versions. + // + << "if (idi.version != sts.id_image_version () ||" << endl + << "idb.version == 0)" + << "{" + << "bind (idb.bind, idi);" + // Update the id binding versions since we may use them later + // to update containers. + // + << "sts.id_image_version (idi.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}" + << "sts.update_id_image_version (idi.version);" + << endl + << "if (!u)" << endl + << "imb.version++;" + << "}"; + + os << "update_statement& st (sts.update_statement ());" + << "if ("; + + if (versioned) + os << "!st.empty () && "; + + os << "st.execute () == 0)" << endl; + + if (opt == 0) + os << "throw object_not_persistent ();"; + else + os << "throw object_changed ();"; + + os << endl; + } + else if (poly || update_containers || sections) + { + // We don't have any columns to update but we may still need + // to initialize the id image if we are polymorphic or have + // any containers or sections. + // + if (!sts) + os << db << "::transaction& tr (" << db << + "::transaction::current ());" + << db << "::connection& conn (tr.connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + + // The same rationale as a couple of screens up. + // + if (poly && !update_containers && !sections && !abst) + os << "if (!top)"; + + os << "{" + << "id_image_type& i (sts.id_image ());" + << "init (i, id (obj));" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;" + // Cannot be optimistic. + << "}" + << "}"; + } + + if (update_containers || sections) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + if (update_containers) + { + instance<container_calls> t (container_calls::update_call, + &main_section); + t->traverse (c); + } + + // Update the optimistic concurrency version in the object member. + // Do it before updating sections since they will update it as well. + // The same code as in section_traits. + // + if (opt != 0 && !poly_derived) + { + // Object is passed as const reference so we need to cast away + // constness. + // + const char* obj ("const_cast<object_type&> (obj)"); + string inc (optimistic_version_increment (*opt)); + + if (inc == "1") + inc_member (*opt, obj, "obj", "version_type"); + else + set_member (*opt, obj, inc, "", "version_type"); + + os << endl; + } + + // Update sections that are loaded and need updating. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + if (i->update_empty () || i->update == user_section::update_manual) + continue; + + // Here is the deal: for polymorphic section overrides, we could + // have used the same approach as in load_() where the class that + // defines the section data member initiates the section update. + // However, if we used this approach, then we would get what can + // be called "interleaved" update statements. That is, a section + // update would update the section data in "derived" tables before + // updating the main section in these derived tables. While this + // does not feel right, it can also cause more real problems if, + // for example, there are constraints or some such. Generally, + // we always want to update the main section data first for each + // table in the hierarchy. + // + // So what we are going to do instead is call section traits + // update at each override point (and indicate to it not to + // call base). One tricky aspect is who is going to reset the + // changed state. We have to do it at the top since otherwise + // overrides won't know the section has changed. Finding out + // whether we are the final override (in section terms, not + // object terms) is tricky. + // + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << "if ("; + + // Unless we are always loaded, test for being loaded. + // + string sep; + if (i->load != user_section::load_eager) + { + os << ma.translate ("obj") << ".loaded ()"; + sep = " && "; + } + + // If change-updated section, check that it has changed. + // + if (i->update == user_section::update_change) + os << sep << ma.translate ("obj") << ".changed ()"; + + os << ")" + << "{"; + + // Re-initialize the id+ver image with new version which may + // have changed. Here we take a bit of a shortcut and not + // re-bind the image since we know only version may have + // changed and that is always an integer (cannot grow). + // + if (opt != 0) + { + os << "const version_type& v (version (obj));" + << "init (sts.id_image (), id (obj), &v);" + << endl; + } + + os << public_name (m) << "_traits::update (esc, obj"; + + if (poly_derived && i->base != 0) + { + // Normally we don't want to call base (base object's update() + // would have called it; see comment above). There is one + // exception, however, and that is a readonly base section + // in optimistic class. In this case, base object's update() + // won't call it (it is readonly) but it needs to be called + // in order to increment the version. + // + bool base (false); + for (user_section* b (i->base); !base && b != 0; b = b->base) + { + if (b->total != b->inverse + b->readonly || + b->readwrite_containers) + break; // We have a readwrite base. + else if (b->optimistic ()) + base = true; // We have a readonly optimistic root. + } + + os << ", " << base; + } + + os << ");"; + + // Clear the change flag and arm the transaction rollback callback. + // + if (i->update == user_section::update_change) + { + // If polymorphic, do it only if we are the final override. + // + if (poly) + os << "if (root_traits::map->find (typeid (obj))." << + "final_section_update (info, " << i->index << "UL))" << endl; + + os << ma.translate ("obj") << ".reset (true, false, &tr);"; + } + + os << "}"; + + if (av != 0 || dv != 0) + os << "}"; + } + + // Call callback (post_update). + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + os << "callback (db, obj, callback_event::post_update);" + << "pointer_cache_traits::update (db, obj);"; + + if (poly) + os << "}"; + } + } // readonly + + os << "}"; + } + + // update () bulk + // + if (id != 0 && !readonly && c.count ("bulk-update")) + { + os << "void " << traits << "::" << endl + << "update (database& db," << endl + << "const object_type** objs," << endl + << "std::size_t n," << endl + << "multiple_exceptions& mex)" + << "{" + << "using namespace " << db << ";" + << "using " << db << "::update_statement;" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "const object_type& obj (*objs[i]);" + << "callback (db, obj, callback_event::pre_update);"; + + if (opt != 0) + os << "const version_type& v (version (obj));"; + + os << "init (sts.id_image (i), id (obj)" << (opt != 0 ? ", &v" : "") << ");"; + + //@@ assumption: generate_grow is false or it only affects select (like + // in pgsql) so all we have to do is to increment image + // version if it grew. + + os << "image_type& im (sts.image (i));"; + + if (generate_grow) + os << "if ("; + + os << "init (im, obj, statement_update" << (versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << " && i == 0)" << endl + << "im.version++"; + + os << ";" + << "}"; + + // Update bindings. + // + os << "binding& idb (sts.id_image_binding ());" + << "binding& imb (sts.update_image_binding ());" + << endl; + + //@@ assumption: generate_grow: as above + // + os << "bool u (false);" // Avoid incrementing version twice. + << "if (imb.version == 0)" + << "{" + << "bind (imb.bind, sts.image (), statement_update" << + (versioned ? ", svm" : "") << ");" + << "imb.version++;" + << "u = true;" + << "}"; + + //@@ assumption: generate_grow: as above + // + os << "if (idb.version == 0)" + << "{" + << "bind (idb.bind, sts.id_image ());" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << endl + << "if (!u)" << endl + << "imb.version++;" + << "}"; + + // We don't need the versioned check here since the statement cannot + // be empty; otherwise it would have been an empty object which is + // illegal. + // + os << "update_statement& st (sts.update_statement ());" + << "n = st.execute (n, mex);"; // Actual number of rows attempted. + + os << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "unsigned long long r (st.result (i));" // Also sets current in mex. + << endl + << "if (mex[i] != 0)" << endl // Pending exception from result(). + << "continue;" + << endl + << "if (r != 1)" + << "{" + << "mex.insert (i," << endl + //@@ assumption: result_unknown + << "(r == update_statement::result_unknown)," << endl + << (opt == 0 ? "object_not_persistent" : "object_changed") << " ());" + << "continue;" + << "}" + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "continue;" + << endl + << "const object_type& obj (*objs[i]);"; + + // Update the optimistic concurrency version in the object member. + // + if (opt != 0) + { + // Object is passed as const reference so we need to cast away + // constness. + // + const char* obj ("const_cast<object_type&> (obj)"); + string inc (optimistic_version_increment (*opt)); + + if (inc == "1") + inc_member (*opt, obj, "obj", "version_type"); + else + set_member (*opt, obj, inc, "", "version_type"); + + os << endl; + } + + os << "callback (db, obj, callback_event::post_update);" + << "pointer_cache_traits::update (db, obj);" + << "}" // for + << "}"; // update() + } + + // erase (id) + // + size_t erase_containers (has_a (c, test_straight_container)); + bool erase_versioned_containers ( + erase_containers > + has_a (c, test_straight_container | exclude_deleted | exclude_added)); + + if (id != 0) + { + os << "void " << traits << "::" << endl + << "erase (database& db, const id_type& id"; + + if (poly) + os << ", bool top, bool dyn"; + + os << ")" + << "{" + << "using namespace " << db << ";"; + + if (poly) + os << endl + << "ODB_POTENTIALLY_UNUSED (top);"; + + os << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl; + + // Get the discriminator and determine the dynamic type of + // this object. + // + if (poly) + { + os << "if (dyn)" << endl + << "{" + << "discriminator_type d;" + << "root_traits::discriminator_ (sts.root_statements (), id, &d);"; + + if (!abst) + os << endl + << "if (d != info.discriminator)" + << "{"; + + os << "const info_type& pi (root_traits::map->find (d));" + << endl + // Check that the dynamic type is derived from the static type. + // + << "if (!pi.derived (info))" << endl + << "throw object_not_persistent ();" + << endl + << "pi.dispatch (info_type::call_erase, db, 0, &id);" + << "return;"; + + if (!abst) + os << "}"; + + os << "}"; + } + + // Initialize id image. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + os << "id_image_type& i (sts.id_image ());" + << "init (i, id);" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + + if (poly) + os << "}"; + } + + // Erase containers first so that there are no reference + // violations (we don't want to rely on ON DELETE CASCADE + // here since in case of a custom schema, it might not be + // there). + // + if (erase_containers) + { + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"; + + if (erase_versioned_containers) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + instance<container_calls> t (container_calls::erase_id_call); + t->traverse (c); + } + + os << "if (sts.erase_statement ().execute () != 1)" << endl + << "throw object_not_persistent ();" + << endl; + + if (poly_derived) + { + // Call our base last (we erase polymorphic objects from base + // to derived in order not to trigger cascading deletes). + // + os << "base_traits::erase (db, id, false, false);" + << endl; + } + + // Remove from the object cache. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "pointer_cache_traits::erase (db, id);"; + } + + os << "}"; + } + + // erase (id) bulk + // + if (id != 0 && c.count ("bulk-erase")) + { + os << "std::size_t " << traits << "::" << endl + << "erase (database& db," << endl + << "const id_type** ids," << endl + << "std::size_t n," << endl + << "multiple_exceptions& mex)" + << "{" + << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl + << "for (std::size_t i (0); i != n; ++i)" << endl + << "init (sts.id_image (i), *ids[i]);" + << endl + << "binding& idb (sts.id_image_binding ());" + //@@ assumption: generate_grow is false or it only affects select (like + // in pgsql). + << "if (idb.version == 0)" + << "{" + << "bind (idb.bind, sts.id_image ());" + << "idb.version++;" + << (opt != 0 ? "sts.optimistic_id_image_binding ().version++;" : "") + << "}" + << "delete_statement& st (sts.erase_statement ());" + << "n = st.execute (n, mex);" // Set to actual number of rows attempted. + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "unsigned long long r (st.result (i));" // Sets current in mex. + << endl + << "if (mex[i] != 0)" << endl // Pending exception from result(). + << "continue;" + << endl + << "if (r != 1)" + << "{" + << "mex.insert (i," << endl + //@@ assumption: result_unknown + << "(r == delete_statement::result_unknown)," << endl + << "object_not_persistent ());" + << "continue;" + << "}" + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "continue;" + << "pointer_cache_traits::erase (db, *ids[i]);" + << "}" // for + << "return n;" + << "}"; // erase() + } + + // erase (object) + // + bool erase_smart_containers (has_a (c, test_smart_container)); + + if (id != 0 && (poly || opt != 0 || erase_smart_containers)) + { + os << "void " << traits << "::" << endl + << "erase (database& db, const object_type& obj"; + + if (poly) + os << ", bool top, bool dyn"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (top);"; + + os << endl; + + if (poly) + os << "if (dyn)" << endl + << "{" + << "const std::type_info& t (typeid (obj));" + << endl + << "if (t != info.type)" + << "{" + << "const info_type& pi (root_traits::map->find (t));" + << "pi.dispatch (info_type::call_erase, db, &obj, 0);" + << "return;" + << "}" + << "}"; + + // If we are database-poly-abstract but not C++-abstract, then make + // sure we are not trying to erase an instance of an abstract class. + // + if (abst && !c.abstract ()) + os << "if (top)" << endl + << "throw abstract_class ();" + << endl; + + if (opt != 0 || erase_smart_containers) + { + string rsts (poly_derived ? "rsts" : "sts"); + + os << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (poly_derived) + os << endl + << "root_statements_type& rsts (sts.root_statements ());" + << "ODB_POTENTIALLY_UNUSED (rsts);"; + + os << endl; + + // Call callback (pre_erase). + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" << endl; + + os << "callback (db, obj, callback_event::pre_erase);" + << endl; + } + + if (!abst || erase_containers) + { + os << "const id_type& id (object_traits_impl::id (obj));" + << endl; + } + + // Smart containers case. + // + if (opt == 0) + { + // Initialize id image. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + os << "id_image_type& i (" << rsts << ".id_image ());" + << "init (i, id);" + << endl; + + os << "binding& idb (" << rsts << ".id_image_binding ());" + << "if (i.version != " << rsts << ".id_image_version () || " << + "idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << rsts << ".id_image_version (i.version);" + << "idb.version++;" + // Cannot be optimistic. + << "}"; + + if (poly) + os << "}"; + } + + // Erase containers first so that there are no reference + // violations (we don't want to rely on ON DELETE CASCADE + // here since in case of a custom schema, it might not be + // there). + // + + if (erase_containers) + { + os << "extra_statement_cache_type& esc (" << + "sts.extra_statement_cache ());"; + + if (erase_versioned_containers) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + instance<container_calls> t (container_calls::erase_obj_call); + t->traverse (c); + } + + os << "if (sts.erase_statement ().execute () != 1)" << endl + << "throw object_not_persistent ();" + << endl; + } + // Optimistic case. + // + else + { + // Initialize id + managed column image. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + os << "const version_type& v (version (obj));" + << "id_image_type& i (" << rsts << ".id_image ());" + << "init (i, id, &v);" + << endl; + + os << "binding& idb (" << rsts << ".id_image_binding ());" + << "if (i.version != " << rsts << ".id_image_version () ||" << endl + << "idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << rsts << ".id_image_version (i.version);" + << "idb.version++;" + << rsts << ".optimistic_id_image_binding ().version++;" + << "}"; + + if (poly) + os << "}"; // if (top) + } + + // If this is a derived type in a polymorphic hierarchy, then we + // need to check the version (stored in root) before we go ahead + // and start deleting things. Also use the same code for root with + // containers since it is more efficient than the find_() method + // below. + // + bool svm (false); + if (poly_derived || (poly && erase_containers)) + { + // Only do the check in the top-level call. + // + if (!abst) // If we are poly-abstract, then top will always be false. + { + os << "if (top)" + << "{" + << "version_type v;" + << "root_traits::discriminator_ (" << rsts << ", id, 0, &v);" + << endl; + + os << "if (v != version (obj))" << endl + << "throw object_changed ();" + << "}"; + } + } + else if (erase_containers) + { + // Things get complicated here: we don't want to trash the + // containers and then find out that the versions don't match + // and we therefore cannot delete the object. After all, there + // is no guarantee that the user will abort the transaction. + // In fact, a perfectly reasonable scenario is to reload the + // object, re-check the condition, decide not to delete the + // object, and then commit the transaction. + // + // There doesn't seem to be anything better than first making + // sure we can delete the object, then deleting the container + // data, and then deleting the object. To check that we can + // delete the object we are going to use find_() and then + // compare the versions. A special-purpose SELECT query would + // have been more efficient but it would complicated and bloat + // things significantly. + // + + if (versioned) + { + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));" + << endl; + svm = true; + } + + os << "if (!find_ (sts, &id" << + (versioned ? ", svm" : "") << "))" << endl + << "throw object_changed ();" + << endl; + + if (delay_freeing_statement_result) + os << "sts.find_statement ().free_result ();" + << endl; + + os << "if (version (sts.image ()) != version (obj))" << endl + << "throw object_changed ();" + << endl; + } + + // Erase containers first so that there are no reference + // violations (we don't want to rely on ON DELETE CASCADE + // here since in case of a custom schema, it might not be + // there). + // + if (erase_containers) + { + os << "extra_statement_cache_type& esc (" << + "sts.extra_statement_cache ());"; + + if (erase_versioned_containers && !svm) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + instance<container_calls> t (container_calls::erase_obj_call); + t->traverse (c); + } + + const char* st ( + poly_derived ? "erase_statement" : "optimistic_erase_statement"); + + os << "if (sts." << st << " ().execute () != 1)" << endl + << "throw object_changed ();" + << endl; + } + + if (poly_derived) + { + // Call our base last (we erase polymorphic objects from base + // to derived in order not to trigger cascading deletes). + // + os << "base_traits::erase (db, obj, false, false);" + << endl; + } + + if (!abst) // If we are poly-abstract, then top will always be false. + { + if (poly) + os << "if (top)" + << "{"; + + // Note that we don't reset sections since the object is now + // transient and the state of a section in a transient object + // is undefined. + + // Remove from the object cache. + // + os << "pointer_cache_traits::erase (db, id);"; + + // Call callback (post_erase). + // + os << "callback (db, obj, callback_event::post_erase);"; + + if (poly) + os << "}"; + } + } + else + { + os << "callback (db, obj, callback_event::pre_erase);" + << "erase (db, id (obj), true, false);" + << "callback (db, obj, callback_event::post_erase);"; + } + + os << "}"; + } + + // erase (object) bulk + // + if (id != 0 && c.count ("bulk-erase")) + { + os << "void " << traits << "::" << endl + << "erase (database& db," << endl + << "const object_type** objs," << endl + << "std::size_t n," << endl + << "multiple_exceptions& mex)" + << "{"; + + // In non-optimistic case delegate to erase(id). + // + if (opt == 0) + { + os << "id_type a[batch];" + << "const id_type* p[batch];" + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "const object_type& obj (*objs[i]);" + << "callback (db, obj, callback_event::pre_erase);" + << "a[i] = id (obj);" + << "p[i] = a + i;" + << "}" + << "n = erase (db, p, n, mex);" + << endl + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "return;" + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "if (mex[i] == 0)" << endl // No pending exception. + << "callback (db, *objs[i], callback_event::post_erase);" + << "}"; // for + } + else + { + os << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "const object_type& obj (*objs[i]);" + << "callback (db, obj, callback_event::pre_erase);" + << "const version_type& v (version (obj));" + << "init (sts.id_image (i), id (obj), &v);" + << "}"; + + os << "binding& idb (sts.id_image_binding ());" + //@@ assumption: generate_grow is false or it only affects select + // (like in pgsql). + << "if (idb.version == 0)" + << "{" + << "bind (idb.bind, sts.id_image ());" + << "idb.version++;" + << "sts.optimistic_id_image_binding ().version++;" + << "}" + << "delete_statement& st (sts.optimistic_erase_statement ());" + << "n = st.execute (n, mex);" // Set to actual number of attempted. + << endl + << "for (std::size_t i (0); i != n; ++i)" + << "{" + << "unsigned long long r (st.result (i));" // Sets current in mex. + << endl + << "if (mex[i] != 0)" << endl // Pending exception from result(). + << "continue;" + << endl + << "if (r != 1)" + << "{" + << "mex.insert (i," << endl + //@@ assumption: result_unknown + << "(r == delete_statement::result_unknown)," << endl + << "object_changed ());" + << "continue;" + << "}" + << "if (mex.fatal ())" << endl // Don't do any extra work. + << "continue;" + << endl + << "const object_type& obj (*objs[i]);" + << "pointer_cache_traits::erase (db, id (obj));" + << "callback (db, obj, callback_event::post_erase);" + << "}"; // for + } + + os << "}"; // erase() + } + + // find (id) + // + if (id != 0 && c.default_ctor ()) + { + string rsts (poly_derived ? "rsts" : "sts"); + + os << traits << "::pointer_type" << endl + << traits << "::" << endl + << "find (database& db, const id_type& id)" + << "{" + << "using namespace " << db << ";" + << endl; + + // First check the session. + // + os << "{"; + + if (poly_derived) + os << "root_traits::pointer_type rp (pointer_cache_traits::find (" << + "db, id));" + << endl + << "if (!root_traits::pointer_traits::null_ptr (rp))" << endl + << "return" << endl + << " root_traits::pointer_traits::dynamic_pointer_cast<" << + "object_type> (rp);"; + else + os << "pointer_type p (pointer_cache_traits::find (db, id));" + << endl + << "if (!pointer_traits::null_ptr (p))" << endl + << "return p;"; + + os << "}"; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + if (poly_derived) + os << "root_statements_type& rsts (sts.root_statements ());"; + + os << endl + << "statements_type::auto_lock l (" << rsts << ");"; + + if (delay_freeing_statement_result) + os << "auto_result ar;"; + + if (poly) + os << "root_traits::discriminator_type d;"; + + os << endl + << "if (l.locked ())" + << "{" + << "if (!find_ (sts, &id" << (versioned ? ", svm" : "") << "))" << endl + << "return pointer_type ();"; + + if (delay_freeing_statement_result) + os << endl + << "ar.set (sts.find_statement (" << (poly_derived ? "depth" : "") << + "));"; + + if (poly) + os << "d = root_traits::discriminator (" << rsts << ".image ());"; + + os << "}"; + + if (poly) + { + // If statements are locked, then get the discriminator by + // executing a special SELECT statement. We need it to be + // able to create an object of the correct dynamic type + // which will be loaded later. + // + os << "else" << endl + << "root_traits::discriminator_ (" << rsts << ", id, &d);" + << endl; + + if (abst) + os << "const info_type& pi (root_traits::map->find (d));" + << endl; + else + os << "const info_type& pi (" << endl + << "d == info.discriminator ? info : root_traits::map->find (d));" + << endl; + } + + // Create the object. + // + if (poly_derived) + { + os << "root_traits::pointer_type rp (pi.create ());" + << "pointer_type p (" << endl + << "root_traits::pointer_traits::static_pointer_cast<object_type> " << + "(rp));" + << "pointer_traits::guard pg (p);" + << endl; + + // Insert it as a root pointer (for non-unique pointers, rp should + // still be valid and for unique pointers this is a no-op). + // + os << "pointer_cache_traits::insert_guard ig (" << endl + << "pointer_cache_traits::insert (db, id, rp));" + << endl; + } + else + { + if (poly) + os << "pointer_type p (pi.create ());"; + else + os << "pointer_type p (" << endl + << "access::object_factory<object_type, pointer_type>::create ());"; + + os << "pointer_traits::guard pg (p);" + << endl; + + os << "pointer_cache_traits::insert_guard ig (" << endl + << "pointer_cache_traits::insert (db, id, p));" + << endl; + } + + os << "object_type& obj (pointer_traits::get_ref (p));" + << endl + << "if (l.locked ())" + << "{" + << "select_statement& st (sts.find_statement (" << + (poly_derived ? "depth" : "") << "));" + << "ODB_POTENTIALLY_UNUSED (st);" + << endl; + + if (poly) + os << "callback_event ce (callback_event::pre_load);" + << "pi.dispatch (info_type::call_callback, db, &obj, &ce);"; + else + os << "callback (db, obj, callback_event::pre_load);"; + + os << "init (obj, sts.image (), &db" << (versioned ? ", svm" : "") << ");"; + + init_value_extra (); + + if (delay_freeing_statement_result) + os << "ar.free ();"; + + os << "load_ (sts, obj, false" << (versioned ? ", svm" : "") << ");"; + + if (poly) + // Load the dynamic part of the object unless static and dynamic + // types are the same. + // + os << endl + << "if (&pi != &info)" + << "{" + << "std::size_t d (depth);" + << "pi.dispatch (info_type::call_load, db, &obj, &d);" + << "}"; + + os << rsts << ".load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();"; + + if (poly) + os << "ce = callback_event::post_load;" + << "pi.dispatch (info_type::call_callback, db, &obj, &ce);"; + else + os << "callback (db, obj, callback_event::post_load);"; + + os << "pointer_cache_traits::load (ig.position ());" + << "}" + << "else" << endl + << rsts << ".delay_load (id, obj, ig.position ()" << + (poly ? ", pi.delayed_loader" : "") << ");" + << endl; + + os << "ig.release ();" + << "pg.release ();" + << "return p;" + << "}"; + } + + // find (id, obj) + // + if (id != 0) + { + string rsts (poly_derived ? "rsts" : "sts"); + + os << "bool " << traits << "::" << endl + << "find (database& db, const id_type& id, object_type& obj"; + + if (poly) + os << ", bool dyn"; + + os << ")" + << "{"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (dyn);" + << endl; + + os << "using namespace " << db << ";" + << endl; + + if (poly) + { + if (!abst) + os << "if (dyn)" << endl + << "{"; + + os << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t != info.type)" + << "{"; + + os << "const info_type& pi (root_traits::map->find (t));" + << "return pi.dispatch (info_type::call_find, db, &obj, &id);"; + + if (!abst) + os << "}" + << "}"; + } + + if (!abst) + { + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + if (poly_derived) + os << "root_statements_type& rsts (sts.root_statements ());"; + + // This can only be top-level call so auto_lock must succeed. + // + os << endl + << "statements_type::auto_lock l (" << rsts << ");" + << "assert (l.locked ()) /* Must be a top-level call. */;" + << endl; + + os << "if (!find_ (sts, &id" << + (versioned ? ", svm" : "") << "))" << endl + << "return false;" + << endl; + + os << "select_statement& st (sts.find_statement (" << + (poly_derived ? "depth" : "") << "));" + << "ODB_POTENTIALLY_UNUSED (st);" + << endl; + + if (delay_freeing_statement_result) + os << "auto_result ar (st);"; + + os << "reference_cache_traits::position_type pos (" << endl + << "reference_cache_traits::insert (db, id, obj));" + << "reference_cache_traits::insert_guard ig (pos);" + << endl + << "callback (db, obj, callback_event::pre_load);" + << "init (obj, sts.image (), &db" << + (versioned ? ", svm" : "") << ");"; + + init_value_extra (); + + if (delay_freeing_statement_result) + os << "ar.free ();"; + + os << "load_ (sts, obj, false" << (versioned ? ", svm" : "") << ");" + << rsts << ".load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();" + << "callback (db, obj, callback_event::post_load);" + << "reference_cache_traits::load (pos);" + << "ig.release ();" + << "return true;"; + } + + os << "}"; + } + + // reload () + // + if (id != 0) + { + string rsts (poly_derived ? "rsts" : "sts"); + + // This implementation is almost exactly the same as find(id, obj) + // above except that it doesn't interract with the object cache and, + // in case of optimistic concurrency, checks if the object actually + // needs to be reloaded. + // + os << "bool " << traits << "::" << endl + << "reload (database& db, object_type& obj"; + + if (poly) + os << ", bool dyn"; + + os << ")" + << "{"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (dyn);" + << endl; + + os << "using namespace " << db << ";" + << endl; + + if (poly) + { + if (!abst) + os << "if (dyn)" << endl + << "{"; + + os << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t != info.type)" + << "{"; + + os << "const info_type& pi (root_traits::map->find (t));" + << "return pi.dispatch (info_type::call_reload, db, &obj, 0);"; + + if (!abst) + os << "}" + << "}"; + } + + if (!abst) + { + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + if (poly_derived) + os << "root_statements_type& rsts (sts.root_statements ());"; + + // This can only be top-level call so auto_lock must succeed. + // + os << endl + << "statements_type::auto_lock l (" << rsts << ");" + << "assert (l.locked ()) /* Must be a top-level call. */;" + << endl; + + os << "const id_type& id (object_traits_impl::id (obj));" + << "if (!find_ (sts, &id" << + (versioned ? ", svm" : "") << "))" << endl + << "return false;" + << endl; + + os << "select_statement& st (sts.find_statement (" << + (poly_derived ? "depth" : "") << "));" + << "ODB_POTENTIALLY_UNUSED (st);" + << endl; + + if (delay_freeing_statement_result) + os << "auto_result ar (st);" + << endl; + + if (opt != 0) + { + const char* tr (poly_derived ? "root_traits::" : ""); + + os << "if (" << tr << "version (" << rsts << ".image ()) == " << + tr << "version (obj))" << endl + << "return true;" + << endl; + } + + os << "callback (db, obj, callback_event::pre_load);" + << "init (obj, sts.image (), &db" << + (versioned ? ", svm" : "") << ");"; + + init_value_extra (); + + if (delay_freeing_statement_result) + os << "ar.free ();"; + + os << "load_ (sts, obj, true" << (versioned ? ", svm" : "") << ");" + << rsts << ".load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();" + << "callback (db, obj, callback_event::post_load);" + << "return true;"; + } + + os << "}"; + } + + // load (section) [non-thunk version] + // + if (uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + { + string rsts (poly_derived ? "rsts" : "sts"); + + os << "bool " << traits << "::" << endl + << "load (connection& conn, object_type& obj, section& s" << + (poly ? ", const info_type* pi" : "") << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + if (poly) + { + // Resolve type information if we are doing indirect calls. + // + os << "bool top (pi == 0);" // Top-level call. + << "if (top)" + << "{" + << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t == info.type)" << endl + << "pi = &info;" + << "else" << endl; + + os << "pi = &root_traits::map->find (t);" + << "}"; + } + + // Lock the statements for the object itself. We need to do this since we + // are using the id image and loading of object pointers can overwrite it. + // + os << db << "::connection& c (static_cast<" << db << + "::connection&> (conn));" + << "statements_type& sts (c.statement_cache ()." << + "find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + if (poly_derived) + os << "root_statements_type& rsts (sts.root_statements ());"; + + os << endl + << "statements_type::auto_lock l (" << rsts << ");"; + + // It this is a top-level call, then auto_lock must succeed. + // + if (poly) + os << "if (top)" << endl; + + os << "assert (l.locked ()) /* Must be a top-level call. */;" + << endl; + + os << "bool r (false);" + << endl; + + // If our poly-base has load sections, then call the base version + // first. Besides checking for sections, it will also initialize + // the id image. + // + if (poly_derived && + buss->count (user_sections::count_total | + user_sections::count_load | + user_sections::count_load_empty) != 0) + { + os << "if (base_traits::load (conn, obj, s, pi))" << endl + << "r = true;" + << endl; + } + else + { + // Initialize id image (all this is equivalent to using rsts). + // + os << "id_image_type& i (sts.id_image ());" + << "init (i, id (obj));" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + // Resolve extra statements if we are doing direct calls. + // + if (!poly) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + // Dispatch. + // + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + if (i->load == user_section::load_eager) + continue; + + // Overridden sections are handled by the base. + // + if (poly_derived && i->base != 0) + continue; + + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << "if (!r && &s == &" << ma.translate ("obj") << ")" + << "{"; + + if (!poly) + os << public_name (m) << "_traits::load (esc, obj);"; + else + { + // If this is an empty section, then there may not be any + // overrides. + // + os << "info_type::section_load sl (" << + "pi->find_section_load (" << i->index << "UL));"; + + if (i->load_empty ()) + os << "if (sl != 0)" << endl; + + os << "sl (conn, obj, true);"; + } + + os << "r = true;" + << "}"; + } + + if (poly) + os << "if (top)" + << "{"; + + os << rsts << ".load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();"; + + if (poly) + os << "}"; + + os << "return r;" + << "}"; + } + + // update (section) [non-thunk version] + // + if (uss.count (user_sections::count_new | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + { + os << "bool " << traits << "::" << endl + << "update (connection& conn, const object_type& obj, " << + "const section& s" << (poly ? ", const info_type* pi" : "") << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + if (poly) + { + // Resolve type information if we are doing indirect calls. + // + os << "if (pi == 0)" // Top-level call. + << "{" + << "const std::type_info& t (typeid (obj));"; + + if (!abst) + os << endl + << "if (t == info.type)" << endl + << "pi = &info;" + << "else" << endl; + + os << "pi = &root_traits::map->find (t);" + << "}"; + } + + // If our poly-base has update sections, then call the base version + // first. Besides checking for sections, it will also initialize the + // id image. + // + if (poly_derived && + buss->count (user_sections::count_total | + user_sections::count_update | + user_sections::count_update_empty) != 0) + { + os << "if (base_traits::update (conn, obj, s, pi))" << endl + << "return true;" + << endl; + } + else + { + // Resolve extra statements if we are doing direct calls. + // + os << db << "::connection& c (static_cast<" << db << + "::connection&> (conn));" + << "statements_type& sts (c.statement_cache ()." << + "find_object<object_type> ());"; + + if (!poly) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());"; + + os << endl; + + // Initialize id image. This is not necessarily the root of the + // polymorphic hierarchy. + // + if (opt != 0) + os << "const version_type& v (version (obj));"; + + os << "id_image_type& i (sts.id_image ());"; + + os << "init (i, id (obj)" << (opt != 0 ? ", &v" : "") << ");" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + // Dispatch. + // + bool e (false); + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + // Overridden sections are handled by the base. + // + if (poly_derived && i->base != 0) + continue; + + // Skip read-only sections. Polymorphic sections are always + // (potentially) read-write. + // + if (!poly && i->update_empty ()) + continue; + + data_member& m (*i->member); + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + if (e) + os << "else "; + else + e = true; + + os << "if (&s == &" << ma.translate ("obj") << ")"; + + if (!poly) + os << public_name (m) << "_traits::update (esc, obj);"; + else + { + // If this is a readonly section, then there may not be any + // overrides. + // + os << "{" + << "info_type::section_update su (" << + "pi->find_section_update (" << i->index << "UL));"; + + if (i->update_empty ()) + { + // For optimistic section, also check that we are not the + // final override, since otherwise we will increment the + // version without updating anything. + // + if (i->optimistic ()) + os << "if (su != 0 && su != info.sections->functions[" << + i->index << "UL].update)" << endl; + else + os << "if (su != 0)" << endl; + } + + os << "su (conn, obj);" + << "}"; + } + } + + os << "else" << endl + << "return false;" + << endl + << "return true;" + << "}"; + } + + // find_ () + // + if (id != 0) + { + os << "bool " << traits << "::" << endl + << "find_ (statements_type& sts," << endl + << "const id_type* id"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + if (poly_derived && !abst) + os << "," << endl + << "std::size_t d"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + // Initialize id image. + // + if (poly_derived && !abst) + os << "if (d == depth)" + << "{"; + + os << "id_image_type& i (sts.id_image ());" + << "init (i, *id);" + << endl; + + os << "binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || idb.version == 0)" + << "{" + << "bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (opt != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + + if (poly_derived && !abst) + os << "}"; + + // Rebind data image. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding (" << + (poly_derived ? (abst ? "depth" : "d") : "") << "));" + << endl; + + if (poly_derived) + { + os << "if (imb.version == 0 ||" << endl + << "check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "}"; + } + + os << "select_statement& st (sts.find_statement (" << + (poly_derived ? (abst ? "depth" : "d") : "") << "));" + << endl; + + // The dynamic loader SELECT statement can be dynamically empty. + // + if (versioned && poly_derived && !abst) + os << "if (st.empty ())" << endl + << "return true;" + << endl; + + os << "st.execute ();" + << "auto_result ar (st);" + << "select_statement::result r (st.fetch ());" + << endl; + + if (grow) + { + os << "if (r == select_statement::truncated)" + << "{" + << "if (grow (im, sts.select_image_truncated ()" << + (versioned ? ", svm" : "") << + (poly_derived ? (abst ? ", depth" : ", d") : "") << "))" << endl + << "im.version++;" + << endl; + + if (poly_derived) + { + os << "if (check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "st.refetch ();" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version ())" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "st.refetch ();" + << "}"; + } + + os << "}"; + } + + // If we are delaying, only free the result if it is empty. + // + if (delay_freeing_statement_result) + os << "if (r != select_statement::no_data)" + << "{" + << "ar.release ();" + << "return true;" + << "}" + << "else" << endl + << "return false;"; + else + os << "return r != select_statement::no_data;"; + + os << "}"; + } + + // load_() + // + // Load containers, reset/reload sections. + // + size_t load_containers ( + has_a (c, test_container | include_eager_load, &main_section)); + + if (poly_derived || + load_containers || + uss.count (user_sections::count_new | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + { + bool load_versioned_containers ( + load_containers > + has_a (c, + test_container | include_eager_load | + exclude_deleted | exclude_added | exclude_versioned, + &main_section)); + + os << "void " << traits << "::" << endl + << "load_ (statements_type& sts," << endl + << "object_type& obj," << endl + << "bool reload"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + if (poly_derived) + os << "," << endl + << "std::size_t d"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (reload);"; + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + os << endl; + + if (poly_derived) + os << "if (--d != 0)" << endl + << "base_traits::load_ (sts.base_statements (), obj, reload" << + (context::versioned (*poly_base) ? ", svm" : "") << + (poly_base != poly_root ? ", d" : "") << ");" + << endl; + + if (load_containers || + (!poly && uss.count (user_sections::count_new | + user_sections::count_load) != 0)) + os << "extra_statement_cache_type& esc (sts.extra_statement_cache ());" + << endl; + + if (!versioned && ( + load_versioned_containers || + uss.count (user_sections::count_new | + user_sections::count_all | + user_sections::count_versioned_only) != 0)) + { + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));" + << endl; + } + + if (load_containers) + { + instance<container_calls> t (container_calls::load_call, &main_section); + t->traverse (c); + } + + for (user_sections::iterator i (uss.begin ()); i != uss.end (); ++i) + { + // Skip special sections. + // + if (i->special == user_section::special_version) + continue; + + // Skip overridden sections; they are handled by the base. + // + if (i->base != 0 && poly_derived) + continue; + + data_member& m (*i->member); + + // If the section is soft- added or deleted, check the version. + // + unsigned long long av (added (m)); + unsigned long long dv (deleted (m)); + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + + // Section access is always by reference. + // + member_access& ma (m.get<member_access> ("get")); + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + if (i->load == user_section::load_eager) + { + // Mark an eager section as loaded. + // + os << ma.translate ("obj") << ".reset (true, false);" + << endl; + continue; + } + + os << "if (reload)" + << "{" + // Reload sections that have been loaded, clear change state. + // + << "if (" << ma.translate ("obj") << ".loaded ())" + << "{"; + + if (!poly) + os << public_name (m) << "_traits::load (esc, obj);"; + else + { + os << "info_type::section_load sl (" << endl + << "root_traits::map->find (typeid (obj)).find_section_load (" << + i->index << "UL));"; + + if (i->load_empty ()) + os << "if (sl != 0)" << endl; + + os << "sl (sts.connection (), obj, true);"; + } + + os << ma.translate ("obj") << ".reset (true, false);" + << "}" + << "}" + << "else" << endl + // Reset to unloaded, unchanged state. + << ma.translate ("obj") << ".reset ();"; + + if (av != 0 || dv != 0) + os << "}"; + else + os << endl; + } + + os << "}"; + } + + // load_() + // + // Load the dynamic part of the object. We don't need it if we are + // poly-abstract. + // + if (poly_derived && !abst) + { + os << "void " << traits << "::" << endl + << "load_ (database& db, root_type& r, std::size_t d)" + << "{" + << "using namespace " << db << ";" + << endl + << "object_type& obj (static_cast<object_type&> (r));" + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());" + << endl + << "d = depth - d;" // Convert to distance from derived. + << endl; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));" + << endl; + + // Avoid trying to execute an empty SELECT statement. + // + if (empty_depth != 0) + os << "if (d > " << (poly_depth - empty_depth) << "UL)" + << "{"; + + os << "if (!find_ (sts, 0" << (versioned ? ", svm" : "") << ", d))" << endl + << "throw object_not_persistent ();" // Database inconsistency. + << endl; + + os << "select_statement& st (sts.find_statement (d));" + << "ODB_POTENTIALLY_UNUSED (st);" + << endl; + + // The resulting SELECT statement may end up being dynamically empty. + // + if (versioned) + os << "if (!st.empty ())" + << "{"; + + if (delay_freeing_statement_result) + os << "auto_result ar (st);"; + + os << "init (obj, sts.image (), &db" << (versioned ? ", svm" : "") << + ", d);"; + + init_value_extra (); + + if (delay_freeing_statement_result) + os << "ar.free ();"; + + if (versioned) + os << "}"; + + if (empty_depth != 0) + os << "}"; + + os << "load_ (sts, obj, false, " << (versioned ? "svm, " : "") << "d);" + << "}"; + } + + // discriminator_ () + // + if (poly && !poly_derived) + { + os << "void " << traits << "::" << endl + << "discriminator_ (statements_type& sts," << endl + << "const id_type& id," << endl + << "discriminator_type* pd"; + + if (opt != 0) + os << "," << endl + << "version_type* pv"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl; + + // Initialize id image. + // + os << "id_image_type& idi (sts.discriminator_id_image ());" + << "init (idi, id);" + << endl; + + os << "binding& idb (sts.discriminator_id_image_binding ());" + << "if (idi.version != sts.discriminator_id_image_version () ||" << endl + << "idb.version == 0)" + << "{" + << "bind (idb.bind, idi" << (opt != 0 ? ", false" : "") << ");" + << "sts.discriminator_id_image_version (idi.version);" + << "idb.version++;" + << "}"; + + // Rebind data image. + // + os << "discriminator_image_type& i (sts.discriminator_image ());" + << "binding& imb (sts.discriminator_image_binding ());" + << endl + << "if (i.version != sts.discriminator_image_version () ||" << endl + << "imb.version == 0)" + << "{" + // Generate bind code inline. For now discriminator is simple + // value so we don't need statement kind (sk). + // + << bind_vector << " b (imb.bind);" + << "std::size_t n (0);" + << "{"; + bind_discriminator_member_->traverse (*discriminator); + os << "}"; + + if (opt != 0) + { + os << "n++;" // For now discriminator is a simple value. + << "{"; + bind_version_member_->traverse (*opt); + os << "}"; + } + + os << "sts.discriminator_image_version (i.version);" + << "imb.version++;" + << "}"; + + os << "{" + << "select_statement& st (sts.find_discriminator_statement ());" + << "st.execute ();" + << "auto_result ar (st);" + << "select_statement::result r (st.fetch ());" + << endl + << "if (r == select_statement::no_data)" + << "{"; + + if (opt != 0) + os << "if (pv != 0)" << endl + << "throw object_changed ();" + << "else" << endl; + + os << "throw object_not_persistent ();" + << "}"; + + if (generate_grow && + (context::grow (*discriminator) || + (opt != 0 && context::grow (*opt)))) + { + os << "else if (r == select_statement::truncated)" + << "{"; + + // Generate grow code inline. + // + os << "bool grew (false);" + << truncated_vector << " t (sts.discriminator_image_truncated ());" + << endl; + + index_ = 0; + grow_discriminator_member_->traverse (*discriminator); + + if (opt != 0) + grow_version_member_->traverse (*opt); + + os << "if (grew)" << endl + << "i.version++;" + << endl; + + os << "if (i.version != sts.discriminator_image_version ())" + << "{" + // Generate bind code inline. The same code as above. + // + << bind_vector << " b (imb.bind);" + << "std::size_t n (0);" + << "{"; + bind_discriminator_member_->traverse (*discriminator); + os << "}"; + + if (opt != 0) + { + os << "n++;" // For now discriminator is a simple value. + << "{"; + bind_version_member_->traverse (*opt); + os << "}"; + } + + os << "sts.discriminator_image_version (i.version);" + << "imb.version++;" + << "st.refetch ();" + << "}" + << "}"; + } + + // Discriminator cannot be long data (no streaming). + // + os << "}"; + + // Initialize value inline instead of generating a separate + // init() function. For now discriminator is simple value so + // we don't need the database (db). + // + os << "if (pd != 0)" + << "{" + << "discriminator_type& d (*pd);"; + init_named_discriminator_value_member_->traverse (*discriminator); + os << "}"; + + if (opt != 0) + { + os << "if (pv != 0)" + << "{" + << "version_type& v (*pv);"; + init_named_version_value_member_->traverse (*opt); + os << "}"; + } + + os << "}"; + } + + if (options.generate_query ()) + { + char const* result_type; + if (poly) + result_type = "polymorphic_object_result_impl<object_type>"; + else if (id != 0) + result_type = "object_result_impl<object_type>"; + else + result_type = "no_id_object_result_impl<object_type>"; + + string sep (versioned || query_optimize ? "\n" : " "); + + // Unprepared. + // + if (!options.omit_unprepared ()) + { + // query () + // + os << "result< " << traits << "::object_type >" << endl + << traits << "::" << endl + << "query (database& db, const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << endl + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding (" << + (poly_derived ? "depth" : "") << "));" + << endl; + + if (poly_derived) + { + os << "if (imb.version == 0 ||" << endl + << "check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "}"; + } + + os << "std::string text (query_statement);" + << "if (!q.empty ())" + << "{" + << "text += " << strlit (sep) << ";" + << "text += q.clause ();" + << "}"; + + os << "q.init_parameters ();" + << "shared_ptr<select_statement> st (" << endl + << "new (shared) select_statement (" << endl; + object_query_statement_ctor_args ( + c, "q", versioned || query_optimize, false); + os << "));" << endl + << "st->execute ();"; + + post_query_ (c, true); + + os << endl + << "shared_ptr< odb::" << result_type << " > r (" << endl + << "new (shared) " << db << "::" << result_type << " (" << endl + << "q, st, sts, " << (versioned ? "&svm" : "0") << "));" + << endl + << "return result<object_type> (r);" + << "}"; + + // query(odb::query_base) + // + if (multi_dynamic) + os << "result< " << traits << "::object_type >" << endl + << traits << "::" << endl + << "query (database& db, const odb::query_base& q)" + << "{" + << "return query (db, query_base_type (q));" + << "}"; + } + + // erase_query + // + os << "unsigned long long " << traits << "::" << endl + << "erase_query (database& db, const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << endl + << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << endl + << "std::string text (erase_query_statement);" + << "if (!q.empty ())" + << "{" + << "text += ' ';" + << "text += q.clause ();" + << "}" + << "q.init_parameters ();" + << "delete_statement st (" << endl; + object_erase_query_statement_ctor_args (c); + os << ");" + << endl + << "return st.execute ();" + << "}"; + + // erase_query(odb::query_base) + // + if (multi_dynamic) + os << "unsigned long long " << traits << "::" << endl + << "erase_query (database& db, const odb::query_base& q)" + << "{" + << "return erase_query (db, query_base_type (q));" + << "}"; + + // Prepared. Very similar to unprepared but has some annoying variations + // that make it difficult to factor out something common. + // + if (options.generate_prepared ()) + { + // prepare_query + // + os << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl; + + os << db << "::connection& conn (" << endl + << "static_cast<" << db << "::connection&> (c));" + << endl + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding (" << + (poly_derived ? "depth" : "") << "));" + << endl; + + if (poly_derived) + { + os << "if (imb.version == 0 ||" << endl + << "check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "}"; + } + + os << "std::string text (query_statement);" + << "if (!q.empty ())" + << "{" + << "text += " << strlit (sep) << ";" + << "text += q.clause ();" + << "}"; + + os << "shared_ptr<" << db << "::prepared_query_impl> r (" << endl + << "new (shared) " << db << "::prepared_query_impl (conn));" + << "r->name = n;" + << "r->execute = &execute_query;" + << "r->query = q;" + << "r->stmt.reset (" << endl + << "new (shared) select_statement (" << endl; + object_query_statement_ctor_args ( + c, "r->query", versioned || query_optimize, true); + os << "));" + << endl + << "return r;" + << "}"; + + // prepare_query(odb::query_base) + // + if (multi_dynamic) + os << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const odb::query_base& q)" + << "{" + << "return prepare_query (c, n, query_base_type (q));" + << "}"; + + // execute_query + // + os << "odb::details::shared_ptr<result_impl>" << endl + << traits << "::" << endl + << "execute_query (prepared_query_impl& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl + << db << "::prepared_query_impl& pq (" << endl + << "static_cast<" << db << "::prepared_query_impl&> (q));" + << "shared_ptr<select_statement> st (" << endl + << "odb::details::inc_ref (" << endl + << "static_cast<select_statement*> (pq.stmt.get ())));" + << endl; + + os << db << "::transaction& tr (" << db << "::transaction::current ());" + << "ODB_POTENTIALLY_UNUSED (tr);" + << endl + << "// The connection used by the current transaction and the" << endl + << "// one used to prepare this statement must be the same." << endl + << "//" << endl + << "assert (q.verify_connection (tr));" + << endl + << "statements_type& sts (" << endl + << "st->connection ().statement_cache ().find_object<object_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding (" << + (poly_derived ? "depth" : "") << "));" + << endl; + + if (poly_derived) + { + os << "if (imb.version == 0 ||" << endl + << "check_version (sts.select_image_versions (), im))" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "update_version (sts.select_image_versions ()," << endl + << "im," << endl + << "sts.select_image_bindings ());" + << "}"; + } + else + { + os << "if (im.version != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, im, statement_select" << + (versioned ? ", svm" : "") << ");" + << "sts.select_image_version (im.version);" + << "imb.version++;" + << "}"; + } + + os << "pq.query.init_parameters ();" + << "st->execute ();"; + post_query_ (c, false); + + os << endl + << "return shared_ptr<result_impl> (" << endl + << "new (shared) " << db << "::" << result_type << " (" << endl + << "pq.query, st, sts, " << (versioned ? "&svm" : "0") << "));" + << "}"; + } + } + + // Generate function table registration for dynamic multi-database + // support. + // + if (multi_dynamic) + { + string fn (flat_name (type)); + string dt ("access::object_traits_impl< " + type + ", id_common >"); + + os << "static const" << endl + << dt << "::" << endl + << "function_table_type function_table_" << fn << "_ =" + << "{"; + + // persist () + // + os << "&" << traits << "::persist"; + + if (id != 0) + { + // find (id) + // + if (c.default_ctor ()) + os << "," << endl + << "&" << traits << "::find"; + + // find (id, obj) + // + os << "," << endl + << "&" << traits << "::find"; + + // reload () + // + os << "," << endl + << "&" << traits << "::reload"; + + // update () + // + if (!readonly || poly) + os << "," << endl + << "&" << traits << "::update"; + + // erase () + // + os << "," << endl + << "&" << traits << "::erase"; + + os << "," << endl + << "&" << traits << "::erase"; + + // Sections. + // + if (uss.count (user_sections::count_total | + user_sections::count_load | + (poly ? user_sections::count_load_empty : 0)) != 0) + os << "," << endl + << "&" << traits << "::load"; + + if (uss.count (user_sections::count_total | + user_sections::count_update | + (poly ? user_sections::count_update_empty : 0)) != 0) + os << "," << endl + << "&" << traits << "::update"; + } + + if (options.generate_query ()) + { + if (!options.omit_unprepared ()) + os << "," << endl + << "&" << traits << "::query"; + + os << "," << endl + << "&" << traits << "::erase_query"; + + if (options.generate_prepared ()) + { + os << "," << endl + << "&" << traits << "::prepare_query"; + + os << "," << endl + << "&" << traits << "::execute_query"; + } + } + + os << "};"; + + os << "static const object_function_table_entry< " << type << ", " << + "id_" << db << " >" << endl + << "function_table_entry_" << fn << "_ (" << endl + << "&function_table_" << fn << "_);" + << endl; + } +} + +void relational::source::class_:: +traverse_view (type& c) +{ + view_query& vq (c.get<view_query> ("query")); + + // Only process the view query if it is versioned since the query text + // (e.g., in the native view) must be structured. We also shouldn't try + // to optimize JOINs (since the objects are JOINed explicitly by the + // user), unless we are adding poly-base/derived JOINs. + // + bool versioned (context::versioned (c)); + bool query_optimize (false); + + string const& type (class_fq_name (c)); + string traits ("access::view_traits_impl< " + type + ", id_" + + db.string () + " >"); + + size_t columns (column_count (c).total); + + // Schema name as a string literal or empty. + // + string schema_name (options.schema_name ()[db]); + if (!schema_name.empty ()) + schema_name = strlit (schema_name); + + // Generate the from-list. Also collect relationships via which + // the objects are joined. + // + strings from; + view_relationship_map rel_map; + + if (vq.kind == view_query::condition) + { + view_objects& objs (c.get<view_objects> ("objects")); + + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + bool first (i == objs.begin ()); + string l; + + // + // Tables. + // + + if (i->kind == view_object::table) + { + if (first) + { + l = "FROM "; + l += quote_id (i->tbl_name); + + if (!i->alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (i->alias); + + l += from_trailer (c); + + from.push_back (l); + continue; + } + + l = join_syntax (*i); + l += ' '; + l += quote_id (i->tbl_name); + + if (!i->alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (i->alias); + + if (i->join == view_object::cross) // No ON condition for CROSS JOIN. + { + from.push_back (l); + continue; + } + + semantics::scope& scope ( + dynamic_cast<semantics::scope&> (*unit.find (i->scope))); + + expression e ( + translate_expression ( + c, i->cond, scope, i->loc, "table")); + + if (e.kind != expression::literal) + { + error (i->loc) << "invalid join condition in db pragma " << + "table" << endl; + throw operation_failed (); + } + + l += " ON"; + + // Add the pragma location for easier error tracking. + // + from.push_back (l); + from.push_back ("// From " + location_string (i->loc, true)); + from.push_back (e.value); + continue; + } + + // + // Objects. + // + semantics::class_& o (*i->obj); + + bool poly (polymorphic (o)); + size_t poly_depth (poly ? polymorphic_depth (o) : 1); + + string alias (i->alias); + + // For polymorphic objects, alias is just a prefix. + // + if (poly && !alias.empty ()) + alias += "_" + table_name (o).uname (); + + // First object. + // + if (first) + { + l = "FROM "; + l += table_qname (o); + + if (!alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (alias); + + l += from_trailer (c); + + from.push_back (l); + + if (poly_depth != 1) + { + bool t (true); //@@ (im)perfect forwarding + size_t d (poly_depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (o, t, d, i->alias); + j->traverse (polymorphic_base (o)); + + from.insert (from.end (), j->begin (), j->end ()); + query_optimize = query_optimize || !j->joins.empty (); + } + continue; + } + + semantics::scope& scope ( + dynamic_cast<semantics::scope&> (*unit.find (i->scope))); + + expression e (i->join == view_object::cross + ? expression ("") // Dummy literal expression. + : translate_expression ( + c, i->cond, scope, i->loc, "object")); + + // Literal expression. + // + if (e.kind == expression::literal) + { + l = join_syntax (*i); + l += ' '; + l += table_qname (o); + + if (!alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (alias); + + if (i->join == view_object::cross) // No ON condition for CROSS JOIN. + { + from.push_back (l); + continue; + } + + l += " ON"; + + // Add the pragma location for easier error tracking. + // + from.push_back (l); + from.push_back ("// From " + location_string (i->loc, true)); + from.push_back (e.value); + + if (poly_depth != 1) + { + bool t (true); //@@ (im)perfect forwarding + size_t d (poly_depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (o, t, d, i->alias); + j->traverse (polymorphic_base (o)); + + from.insert (from.end (), j->begin (), j->end ()); + query_optimize = query_optimize || !j->joins.empty (); + } + continue; + } + + // We have an object relationship (pointer) for which we need + // to come up with the corresponding JOIN condition. If this + // is a to-many relationship, then we first need to JOIN the + // container table. This code is similar to object_joins. + // + // Note that this cannot be CROSS JOIN; we've handled that case + // above. + // + using semantics::data_member; + + data_member& m (*e.member_path.back ()); + data_member_path* imp (inverse (m)); + + // Resolve the pointed-to object to view_object and do + // some sanity checks while at it. + // + semantics::class_* c (0); + + if (container (m)) + c = object_pointer (container_vt (m)); + else + c = object_pointer (utype (m)); + + view_object* vo (0); + + // Check if the pointed-to object has been previously associated + // with this view and is unambiguous. A pointer to ourselves is + // always assumed to point to this association. + // + if (&o == c) + vo = &*i; + else + { + bool ambig (false); + + for (view_objects::iterator j (objs.begin ()); j != i; ++j) + { + if (j->obj != c) + continue; + + if (vo == 0) + { + vo = &*j; + continue; + } + + // If it is the first ambiguous object, issue the + // error. + // + if (!ambig) + { + error (i->loc) << "pointed-to object '" << class_name (*c) << + "' is ambiguous" << endl; + info (i->loc) << "candidates are:" << endl; + info (vo->loc) << " '" << vo->name () << "'" << endl; + ambig = true; + } + + info (j->loc) << " '" << j->name () << "'" << endl; + } + + if (ambig) + { + info (i->loc) << "use the other side of the relationship " << + "or full join condition clause in db pragma object to " << + "resolve this ambiguity" << endl; + throw operation_failed (); + } + + if (vo == 0) + { + error (i->loc) << "pointed-to object '" << class_name (*c) << + "' specified in the join condition has not been " << + "previously associated with this view" << endl; + throw operation_failed (); + } + } + + // JOIN relationship points to us: + // vo - us + // e.vo - other side + // e.member_path - in other side + // + // JOIN relationship points to other side: + // vo - other side + // e.vo - us + // e.member_path - in us + // + if (imp == 0) + rel_map.insert (make_pair (e.member_path, make_pair (e.vo, vo))); + else + rel_map.insert (make_pair (*imp, make_pair (vo, e.vo))); + + // Left and right-hand side table names. + // + qname lt; + { + using semantics::class_; + + class_& o (*e.vo->obj); + string const& a (e.vo->alias); + + if (class_* root = polymorphic (o)) + { + // If the object is polymorphic, then figure out which of the + // bases this member comes from and use the corresponding + // table. + // + class_* c ( + &static_cast<class_&> ( + e.member_path.front ()->scope ())); + + // If this member's class is not polymorphic (root uses reuse + // inheritance), then use the root table. + // + if (!polymorphic (*c)) + c = root; + + qname const& t (table_name (*c)); + + if (a.empty ()) + lt = t; + else + lt = qname (a + "_" + t.uname ()); + } + else + lt = a.empty () ? table_name (o) : qname (a); + } + + qname rt; + { + qname t (table_name (*vo->obj)); + string const& a (vo->alias); + rt = a.empty () + ? t + : qname (polymorphic (*vo->obj) ? a + "_" + t.uname () : a); + } + + // First join the container table if necessary. + // + semantics::type* cont (container (imp != 0 ? *imp->back () : m)); + + string ct; // Container table. + if (cont != 0) + { + l = join_syntax (*i); + l += ' '; + + // The same relationship can be used by multiple associated + // objects. So if the object that contains this container has + // an alias, then also construct one for the table that we + // are joining. + // + { + using semantics::class_; + + qname t; + + // In a polymorphic hierarchy the member can be in a base (see + // above). + // + if (class_* root = polymorphic (imp != 0 ? *vo->obj : *e.vo->obj)) + { + class_* c; + if (imp == 0) + { + c = &static_cast<class_&> (e.member_path.front ()->scope ()); + + if (!polymorphic (*c)) + c = root; + + t = table_name (*c, e.member_path); + } + else + { + c = &static_cast<class_&> (imp->front ()->scope ()); + + if (!polymorphic (*c)) + c = root; + + t = table_name (*c, *imp); + } + } + else + t = imp != 0 + ? table_name (*vo->obj, *imp) + : table_name (*e.vo->obj, e.member_path); + + // The tricky part is to figure out which view_object, vo + // or e.vo we should use. We want to use the alias from the + // object that actually contains this container. The following + // might not make intuitive sense, but it has been verified + // with the truth table. + // + string const& a (imp != 0 ? vo->alias : e.vo->alias); + + if (a.empty ()) + { + ct = quote_id (t); + l += ct; + } + else + { + ct = quote_id (a + '_' + t.uname ()); + l += quote_id (t); + l += (need_alias_as ? " AS " : " ") + ct; + } + } + + l += " ON"; + from.push_back (l); + + // If we are the pointed-to object, then we have to turn + // things around. This is necessary to have the proper + // JOIN order. There seems to be a pattern there but it + // is not yet intuitively clear what it means. + // + instance<object_columns_list> c_cols; // Container columns. + instance<object_columns_list> o_cols; // Object columns. + + qname* ot; // Object table (either lt or rt). + + if (imp != 0) + { + semantics::data_member& imb (*imp->back ()); + + if (&o == c) + { + // container.value = pointer.id + // + data_member_path& id (*id_member (*e.vo->obj)); + + c_cols->traverse (imb, utype (id), "value", "value"); + o_cols->traverse (id); + ot = < + } + else + { + // container.id = pointed-to.id + // + data_member_path& id (*id_member (*vo->obj)); + + c_cols->traverse (imb, utype (id), "id", "object_id", vo->obj); + o_cols->traverse (id); + ot = &rt; + } + } + else + { + if (&o == c) + { + // container.id = pointer.id + // + data_member_path& id (*id_member (*e.vo->obj)); + + c_cols->traverse ( + m, utype (id), "id", "object_id", e.vo->obj); + o_cols->traverse (id); + ot = < + } + else + { + // container.value = pointed-to.id + // + data_member_path& id (*id_member (*vo->obj)); + + c_cols->traverse (m, utype (id), "value", "value"); + o_cols->traverse (id); + ot = &rt; + } + } + + for (object_columns_list::iterator b (c_cols->begin ()), i (b), + j (o_cols->begin ()); i != c_cols->end (); ++i, ++j) + { + l.clear (); + + if (i != b) + l += "AND "; + + l += ct; + l += '.'; + l += quote_id (i->name); + l += '='; + l += quote_id (*ot); + l += '.'; + l += quote_id (j->name); + + from.push_back (strlit (l)); + } + } + + // If we have already joined the container with the desired + // join type, then use LEFT JOIN to join the object to the + // container. This is the right thing to do since an entry + // in the container can only point (either via id or value) + // to a single object. + // + l = (cont == 0 ? join_syntax (*i) : "LEFT JOIN"); + l += ' '; + + l += table_qname (o); + + if (!alias.empty ()) + l += (need_alias_as ? " AS " : " ") + quote_id (alias); + + l += " ON"; + from.push_back (l); + + if (cont != 0) + { + instance<object_columns_list> c_cols; // Container columns. + instance<object_columns_list> o_cols; // Object columns. + + qname* ot; // Object table (either lt or rt). + + if (imp != 0) + { + semantics::data_member& imb (*imp->back ()); + + if (&o == c) + { + // container.id = pointed-to.id + // + data_member_path& id (*id_member (*vo->obj)); + + c_cols->traverse (imb, utype (id), "id", "object_id", vo->obj); + o_cols->traverse (id); + ot = &rt; + } + else + { + // container.value = pointer.id + // + data_member_path& id (*id_member (*e.vo->obj)); + + c_cols->traverse (imb, utype (id), "value", "value"); + o_cols->traverse (id); + ot = < + } + } + else + { + if (&o == c) + { + // container.value = pointed-to.id + // + data_member_path& id (*id_member (*vo->obj)); + + c_cols->traverse (m, utype (id), "value", "value"); + o_cols->traverse (id); + ot = &rt; + } + else + { + // container.id = pointer.id + // + data_member_path& id (*id_member (*e.vo->obj)); + + c_cols->traverse (m, utype (id), "id", "object_id", e.vo->obj); + o_cols->traverse (id); + ot = < + } + } + + for (object_columns_list::iterator b (c_cols->begin ()), i (b), + j (o_cols->begin ()); i != c_cols->end (); ++i, ++j) + { + l.clear (); + + if (i != b) + l += "AND "; + + l += ct; + l += '.'; + l += quote_id (i->name); + l += '='; + l += quote_id (*ot); + l += '.'; + l += quote_id (j->name); + + from.push_back (strlit (l)); + } + } + else + { + instance<object_columns_list> l_cols; + instance<object_columns_list> r_cols; + + if (imp != 0) + { + // our.id = pointed-to.pointer + // + l_cols->traverse (*id_member (*e.vo->obj)); + r_cols->traverse (*imp->back (), column_prefix (*imp)); + } + else + { + // our.pointer = pointed-to.id + // + l_cols->traverse (*e.member_path.back (), + column_prefix (e.member_path)); + r_cols->traverse (*id_member (*vo->obj)); + } + + for (object_columns_list::iterator b (l_cols->begin ()), i (b), + j (r_cols->begin ()); i != l_cols->end (); ++i, ++j) + { + l.clear (); + + if (i != b) + l += "AND "; + + l += quote_id (lt); + l += '.'; + l += quote_id (i->name); + l += '='; + l += quote_id (rt); + l += '.'; + l += quote_id (j->name); + + from.push_back (strlit (l)); + } + } + + if (poly_depth != 1) + { + bool t (true); //@@ (im)perfect forwarding + size_t d (poly_depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (o, t, d, i->alias); + j->traverse (polymorphic_base (o)); + + from.insert (from.end (), j->begin (), j->end ()); + query_optimize = query_optimize || !j->joins.empty (); + } + } // End JOIN-generating for-loop. + } + + // Check that pointed-to objects inside objects that we are loading + // have session support enabled (required to have a shared copy). + // Also see if we need to throw if there is no session. + // + bool need_session (false); + if (vq.kind == view_query::condition) + { + view_objects& objs (c.get<view_objects> ("objects")); + for (view_objects::iterator i (objs.begin ()); i != objs.end (); ++i) + { + if (i->kind != view_object::object || i->ptr == 0) + continue; // Not an object or not loaded. + + instance<view_object_check> t (*i, rel_map); + t->traverse (*i->obj); + need_session = need_session || t->session_; + } + } + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + view_extra (c); + + // query_columns + // + if (c.get<size_t> ("object-count") != 0) + view_query_columns_type_->traverse (c); + + // + // Functions. + // + + // grow () + // + if (generate_grow && columns != 0) + { + os << "bool " << traits << "::" << endl + << "grow (image_type& i," << endl + << truncated_vector << " t"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (t);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "bool grew (false);" + << endl; + + index_ = 0; + names (c, grow_member_names_); + + os << "return grew;" + << "}"; + } + + // bind (image_type) + // + if (columns != 0) + { + os << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "image_type& i"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);" + << endl; + + os << "using namespace " << db << ";" + << endl + << db << "::statement_kind sk (statement_select);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl + << "std::size_t n (0);" + << endl; + + names (c, bind_member_names_); + + os << "}"; + } + + // init (view, image) + // + if (columns != 0) + { + os << "void " << traits << "::" << endl + << "init (view_type& o," << endl + << "const image_type& i," << endl + << "database* db"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + if (need_session) + os << "if (!" << options.session_type () << "::_has_cache ())" << endl + << "throw session_required ();" + << endl; + + // Note: db must be not NULL in order to load pointers. + // + if (has_a (c, test_pointer)) + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (*db));" + << endl; + + names (c, init_view_pointer_member_pre_names_); + names (c, init_value_member_names_); + names (c, init_view_pointer_member_post_names_); + + os << "}"; + } + + // query_statement() + // + if (vq.kind != view_query::runtime) + { + os << traits << "::query_base_type" << endl + << traits << "::" << endl + << "query_statement (const query_base_type& q)" + << "{"; + + if (vq.kind == view_query::complete_select || + vq.kind == view_query::complete_execute) + { + os << "query_base_type r (" << endl; + + bool ph (false); + bool pred (vq.kind == view_query::complete_select); + + if (!vq.literal.empty ()) + { + // See if we have the '(?)' placeholder. + // + // @@ Ideally we would need to make sure we don't match + // this inside strings and quoted identifier. So the + // proper way to handle this would be to tokenize the + // statement using sql_lexer, once it is complete enough. + // + string::size_type p (vq.literal.find ("(?)")); + + if (p != string::npos) + { + ph = true; + // For the SELECT query we keep the parenthesis in (?) and + // also handle the case where the query expression is empty. + // + if (pred) + os << strlit (string (vq.literal, 0, p + 1)) << " +" << endl + << "(q.empty () ? query_base_type::true_expr : q) +" << endl + << strlit (string (vq.literal, p + 2)); + else + { + os << strlit (string (vq.literal, 0, p)) << " + q"; + + p += 3; + if (p != vq.literal.size ()) + os << " + " << strlit (string (vq.literal, p)); + } + } + else + os << strlit (vq.literal); + } + else + { + semantics::scope& scope ( + dynamic_cast<semantics::scope&> (*unit.find (vq.scope))); + + // Output the pragma location for easier error tracking. + // + os << "// From " << location_string (vq.loc, true) << endl + << translate_expression ( + c, vq.expr, scope, vq.loc, "query", &ph, pred).value; + } + + os << ");"; + + // If there was no placeholder, add the query condition + // at the end. + // + if (!ph) + os << endl + << "if (!q.empty ())" + << "{" + << "r += " << strlit (versioned ? "\n" : " ") << ";" + << "r += q.clause_prefix ();" + << "r += q;" + << "}"; + } + else // vq.kind == view_query::condition + { + // Use the from-list generated above. + // + statement_columns sc; + { + instance<view_columns> t (sc, from, rel_map); + t->traverse (c); + process_statement_columns ( + sc, statement_select, versioned || query_optimize); + } + + string sep (versioned || query_optimize ? "\n" : " "); + + os << "query_base_type r (" << endl + << strlit ((vq.distinct ? "SELECT DISTINCT" : "SELECT") + sep); + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << endl + << strlit (c + (++i != e ? "," : "") + sep); + } + + os << ");" + << endl; + + // It is much easier to add the separator at the beginning of the + // next line since the JOIN condition may not be a string literal. + // + for (strings::const_iterator i (from.begin ()); i != from.end (); ++i) + { + if (i->compare (0, 5, "FROM ") == 0) + os << "r += " << strlit (*i) << ";"; + else if (i->compare (0, 3, "// ") == 0) + os << *i << endl; + else + { + // See if this is a JOIN. The exact spelling is database-dependent, + // but we know there is the JOIN word in there somewhere and before + // it we should only have keywords and spaces. + // + size_t p (i->find ("JOIN ")); + if (p != string::npos) + { + // Make sure before it we only have A-Z and spaces. + // + for (char c; p != 0; --p) + { + c = (*i)[p - 1]; + if ((c < 'A' || c > 'Z') && c != ' ') + break; + } + + if (p == 0) + os << endl + << "r += " << strlit (sep + *i) << ";"; + } + + // Something else, assume already a string literal. + // + if (p != 0) + os << "r += " << *i << ";"; + } + } + + // Generate the query condition. + // + if (!vq.literal.empty () || !vq.expr.empty ()) + { + os << endl + << "query_base_type c (" << endl; + + bool ph (false); + + if (!vq.literal.empty ()) + { + // See if we have the '(?)' placeholder. + // + // @@ Ideally we would need to make sure we don't match + // this inside strings and quoted identifier. So the + // proper way to handle this would be to tokenize the + // statement using sql_lexer, once it is complete enough. + // + string::size_type p (vq.literal.find ("(?)")); + + if (p != string::npos) + { + ph = true; + os << strlit (string (vq.literal, 0, p + 1))<< " +" << endl + << "(q.empty () ? query_base_type::true_expr : q) +" << endl + << strlit (string (vq.literal, p + 2)); + } + else + os << strlit (vq.literal); + + os << ");"; + } + else + { + semantics::scope& scope ( + dynamic_cast<semantics::scope&> (*unit.find (vq.scope))); + + // Output the pragma location for easier error tracking. + // + os << "// From " << location_string (vq.loc, true) << endl + << translate_expression ( + c, vq.expr, scope, vq.loc, "query", &ph).value; + + os << ");"; + + // Optimize the query if it had a placeholder. This gets + // rid of useless clauses like WHERE TRUE. + // + if (ph) + os << endl + << "c.optimize ();"; + } + + if (!ph) + os << endl + << "c += q;"; + + os << endl + << "if (!c.empty ())" + << "{" + << "r += " << strlit (sep) << ";" + << "r += c.clause_prefix ();" + << "r += c;" + << "}"; + + string st (select_trailer (c)); + if (!st.empty ()) + { + os << "r += " << strlit (sep) << ";" + << "r += " << strlit (st) << ";"; + } + } + else + { + os << endl + << "if (!q.empty ())" + << "{" + << "r += " << strlit (sep) << ";" + << "r += q.clause_prefix ();" + << "r += q;" + << "}"; + } + } + + os << "return r;" + << "}"; + } + + // Unprepared. + // + if (!options.omit_unprepared ()) + { + os << "result< " << traits << "::view_type >" << endl + << traits << "::" << endl + << "query (database& db, const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl; + + os << db << "::connection& conn (" << endl + << db << "::transaction::current ().connection (db));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_view<view_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl + << "image_type& im (sts.image ());" + << "binding& imb (sts.image_binding ());" + << endl + << "if (im.version != sts.image_version () || imb.version == 0)" + << "{" + << "bind (imb.bind, im" << (versioned ? ", svm" : "") << ");" + << "sts.image_version (im.version);" + << "imb.version++;" + << "}"; + + if (vq.kind == view_query::runtime) + os << "const query_base_type& qs (q);"; + else + os << "const query_base_type& qs (query_statement (q));"; + + os << "qs.init_parameters ();" + << "shared_ptr<select_statement> st (" << endl + << "new (shared) select_statement (" << endl; + view_query_statement_ctor_args ( + c, "qs", versioned || query_optimize, false); + os << "));" << endl + << "st->execute ();"; + + post_query_ (c, true); + + os << endl + << "shared_ptr< odb::view_result_impl<view_type> > r (" << endl + << "new (shared) " << db << "::view_result_impl<view_type> (" << endl + << "qs, st, sts, " << (versioned ? "&svm" : "0") << "));" + << endl + << "return result<view_type> (r);" + << "}"; + + // query(odb::query_base) + // + if (multi_dynamic) + os << "result< " << traits << "::view_type >" << endl + << traits << "::" << endl + << "query (database& db, const odb::query_base& q)" + << "{" + << "return query (db, query_base_type (q));" + << "}"; + } + + // Prepared. Very similar to unprepared but has some annoying variations + // that make it difficult to factor out something common. + // + if (options.generate_prepared ()) + { + // prepare_query + // + os << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const query_base_type& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl; + + os << db << "::connection& conn (" << endl + << "static_cast<" << db << "::connection&> (c));" + << "statements_type& sts (" << endl + << "conn.statement_cache ().find_view<view_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.image_binding ());" + << endl + << "if (im.version != sts.image_version () || imb.version == 0)" + << "{" + << "bind (imb.bind, im" << (versioned ? ", svm" : "") << ");" + << "sts.image_version (im.version);" + << "imb.version++;" + << "}"; + + os << "shared_ptr<" << db << "::prepared_query_impl> r (" << endl + << "new (shared) " << db << "::prepared_query_impl (conn));" + << "r->name = n;" + << "r->execute = &execute_query;"; + + if (vq.kind == view_query::runtime) + os << "r->query = q;"; + else + os << "r->query = query_statement (q);"; + + os << "r->stmt.reset (" << endl + << "new (shared) select_statement (" << endl; + view_query_statement_ctor_args ( + c, "r->query", versioned || query_optimize, true); + os << "));" + << endl + << "return r;" + << "}"; + + // prepare_query(odb::query_base) + // + if (multi_dynamic) + os << "odb::details::shared_ptr<prepared_query_impl>" << endl + << traits << "::" << endl + << "prepare_query (connection& c, const char* n, " << + "const odb::query_base& q)" + << "{" + << "return prepare_query (c, n, query_base_type (q));" + << "}"; + + // execute_query + // + os << "odb::details::shared_ptr<result_impl>" << endl + << traits << "::" << endl + << "execute_query (prepared_query_impl& q)" + << "{" + << "using namespace " << db << ";" + << "using odb::details::shared;" + << "using odb::details::shared_ptr;" + << endl + << db << "::prepared_query_impl& pq (" << endl + << "static_cast<" << db << "::prepared_query_impl&> (q));" + << "shared_ptr<select_statement> st (" << endl + << "odb::details::inc_ref (" << endl + << "static_cast<select_statement*> (pq.stmt.get ())));" + << endl; + + os << db << "::transaction& tr (" << db << "::transaction::current ());" + << "ODB_POTENTIALLY_UNUSED (tr);" + << endl + << "// The connection used by the current transaction and the" << endl + << "// one used to prepare this statement must be the same." << endl + << "//" << endl + << "assert (q.verify_connection (tr));" + << endl + << "statements_type& sts (" << endl + << "st->connection ().statement_cache ().find_view<view_type> ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration (" << schema_name << "));"; + + os << endl; + + // Rebind the image if necessary. + // + os << "image_type& im (sts.image ());" + << "binding& imb (sts.image_binding ());" + << endl + << "if (im.version != sts.image_version () || imb.version == 0)" + << "{" + << "bind (imb.bind, im" << (versioned ? ", svm" : "") << ");" + << "sts.image_version (im.version);" + << "imb.version++;" + << "}"; + + os << "pq.query.init_parameters ();" + << "st->execute ();"; + + post_query_ (c, false); + + os << endl + << "return shared_ptr<result_impl> (" << endl + << "new (shared) " << db << "::view_result_impl<view_type> (" << endl + << "pq.query, st, sts, " << (versioned ? "&svm" : "0") << "));" + << "}"; + } + + // Generate function table registration for dynamic multi-database + // support. + // + if (multi_dynamic) + { + string fn (flat_name (type)); + string dt ("access::view_traits_impl< " + type + ", id_common >"); + + os << "static const" << endl + << dt << "::" << endl + << "function_table_type function_table_" << fn << "_ =" + << "{"; + + if (!options.omit_unprepared ()) + os << "&" << traits << "::query"; + + if (options.generate_prepared ()) + { + if (!options.omit_unprepared ()) + os << "," << endl; + + os << "&" << traits << "::prepare_query" << "," << endl + << "&" << traits << "::execute_query"; + } + + os << "};"; + + os << "static const view_function_table_entry< " << type << ", " << + "id_" << db << " >" << endl + << "function_table_entry_" << fn << "_ (" << endl + << "&function_table_" << fn << "_);" + << endl; + } +} + +namespace relational +{ + namespace source + { + static inline void + add_space (string& s) + { + string::size_type n (s.size ()); + if (n != 0 && s[n - 1] != ' ') + s += ' '; + } + + static string + translate_name_trailer (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + cpp_ttype& ptt) + { + string r; + + for (; tt != CPP_EOF; ptt = tt, tt = l.next (tl, &tn)) + { + bool done (false); + + switch (tt) + { + case CPP_SCOPE: + case CPP_DOT: + { + r += cxx_lexer::token_spelling[tt]; + break; + } + default: + { + // Handle CPP_KEYWORD here to avoid a warning (it is not + // part of the cpp_ttype enumeration). + // + if (tt == CPP_NAME || tt == CPP_KEYWORD) + { + // For names like 'foo::template bar'. + // + if (ptt == CPP_NAME || ptt == CPP_KEYWORD) + r += ' '; + + r += tl; + } + else + done = true; + + break; + } + } + + if (done) + break; + } + + return r; + } + + static class_::expression + translate_name (cxx_lexer& l, + cpp_ttype& tt, + string& tl, + tree& tn, + cpp_ttype& ptt, + semantics::scope& start_scope, + location_t loc, + string const& prag, + bool check_ptr, + view_alias_map const& amap, + view_object_map const& omap) + { + using semantics::scope; + using semantics::data_member; + typedef class_::expression expression; + + bool multi_obj ((amap.size () + omap.size ()) > 1); + + bool fail (false); + string name; + string r ("query_columns"); + context& ctx (context::current ()); + + // This code is quite similar to view_data_members in the type + // processor. + // + try + { + data_member* m (0); + view_object* vo (0); + + // Check if this is an alias. + // + if (tt == CPP_NAME) + { + view_alias_map::const_iterator i (amap.find (tl)); + + if (i != amap.end ()) + { + if (multi_obj) + { + r += "::"; + r += i->first; + } + + vo = i->second; + fail = true; // This must be a data member. + + // Skip '::'. + // + ptt = tt; + tt = l.next (tl, &tn); + + if (tt != CPP_SCOPE) + { + error (loc) << "member name expected after an alias in db " << + "pragma " << prag << endl; + throw operation_failed (); + } + + ptt = tt; + if (l.next (tl, &tn) != CPP_NAME) + throw lookup::invalid_name (); + + m = &vo->obj->lookup<data_member> (tl, scope::include_hidden); + + tt = l.next (tl, &tn); + } + } + + // If it is not an alias, do the normal lookup. + // + if (vo == 0) + { + // Also get the object type. We need to do it so that + // we can get the correct (derived) object name (the + // member itself can come from a base class). + // + scope* s; + cpp_ttype ptt; // Not used. + m = &lookup::resolve_scoped_name<data_member> ( + l, tt, tl, tn, ptt, + start_scope, + name, + false, + &s); + + view_object_map::const_iterator i ( + omap.find (dynamic_cast<semantics::class_*> (s))); + + if (i == omap.end ()) + { + // Not an object associated with this view. Assume it + // is some other valid name. + // + return expression ( + name + translate_name_trailer (l, tt, tl, tn, ptt)); + } + + vo = i->second; + + if (multi_obj) + { + r += "::"; + r += context::class_name (*vo->obj); + } + } + + expression e (vo); + r += "::"; + r += ctx.public_name (*m); + + // Assemble the member path if we may need to return a pointer + // expression. + // + if (check_ptr) + e.member_path.push_back (m); + + fail = true; // Now we definitely fail if anything goes wrong. + + // Finally, resolve nested members if any. + // + for (; tt == CPP_DOT; ptt = tt, tt = l.next (tl, &tn)) + { + // Check if this member is actually of a composite value type. + // This is to handle expressions like "object::member.is_null ()" + // correctly. The remaining issue here is that in the future + // is_null()/is_not_null() will be valid for composite values + // as well. + // + semantics::class_* comp ( + context::composite_wrapper (context::utype (*m))); + if (comp == 0) + break; + + ptt = tt; + tt = l.next (tl, &tn); + + if (tt != CPP_NAME) + { + error (loc) << "name expected after '.' in db pragma " << + prag << endl; + throw operation_failed (); + } + + m = &comp->lookup<data_member> (tl, scope::include_hidden); + + r += '.'; + r += ctx.public_name (*m); + + if (check_ptr) + e.member_path.push_back (m); + } + + // If requested, check if this member is a pointer. We only do this + // if there is nothing after this name. + // + if (check_ptr && tt == CPP_EOF) + { + using semantics::type; + + type* t; + + if (context::container (*m)) + t = &context::container_vt (*m); + else + t = &context::utype (*m); + + if (context::object_pointer (*t)) + return e; + } + + // Read the remainder of the expression (e.g., '.is_null ()') if + // the member is not composite and we bailed out from the above + // loop. + // + if (tt == CPP_DOT) + r += translate_name_trailer (l, tt, tl, tn, ptt); + + return expression (r); + } + catch (lookup::invalid_name const&) + { + if (!fail) + return expression ( + name + translate_name_trailer (l, tt, tl, tn, ptt)); + + error (loc) << "invalid name in db pragma " << prag << endl; + throw operation_failed (); + } + catch (semantics::unresolved const& e) + { + if (!fail) + return expression ( + name + translate_name_trailer (l, tt, tl, tn, ptt)); + + if (e.type_mismatch) + error (loc) << "name '" << e.name << "' in db pragma " << prag << + " does not refer to a data member" << endl; + else + error (loc) << "unable to resolve data member '" << e.name << + "' specified with db pragma " << prag << endl; + + throw operation_failed (); + } + catch (semantics::ambiguous const& e) + { + error (loc) << "data member name '" << e.first.name () << "' " << + "specified with db pragma " << prag << " is ambiguous" << endl; + + info (e.first.named ().location ()) << "could resolve to this " << + "data member" << endl; + + info (e.second.named ().location ()) << "or could resolve to this " << + "data member" << endl; + + throw operation_failed (); + } + } + + class_::expression class_:: + translate_expression (type& c, + cxx_tokens const& ts, + semantics::scope& scope, + location_t loc, + string const& prag, + bool* placeholder, + bool predicate) + { + // This code is similar to translate() from context.cxx. + // + + // The overall idea is as folows: read in tokens and add them + // to the string. If a token starts a name, try to resolve it + // to an object member (taking into account aliases). If this + // was successful, translate it to the query column reference. + // Otherwise, output it as is. + // + // If the placeholder argument is not NULL, then we need to + // detect the special '(?)' token sequence and replace it + // with the query variable ('q'). + // + expression e (""); + string& r (e.value); + + view_alias_map const& amap (c.get<view_alias_map> ("alias-map")); + view_object_map const& omap (c.get<view_object_map> ("object-map")); + + cxx_tokens_lexer l; + l.start (ts); + + tree tn; + string tl; + for (cpp_ttype tt (l.next (tl, &tn)), ptt (CPP_EOF); tt != CPP_EOF;) + { + // Try to format the expression to resemble the style of the + // generated code. + // + switch (tt) + { + case CPP_NOT: + { + add_space (r); + r += '!'; + break; + } + case CPP_COMMA: + { + r += ", "; + break; + } + case CPP_OPEN_PAREN: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD) + add_space (r); + + r += '('; + break; + } + case CPP_CLOSE_PAREN: + { + r += ')'; + break; + } + case CPP_OPEN_SQUARE: + { + r += '['; + break; + } + case CPP_CLOSE_SQUARE: + { + r += ']'; + break; + } + case CPP_OPEN_BRACE: + { + add_space (r); + r += "{ "; + break; + } + case CPP_CLOSE_BRACE: + { + add_space (r); + r += '}'; + break; + } + case CPP_SEMICOLON: + { + r += ';'; + break; + } + case CPP_ELLIPSIS: + { + add_space (r); + r += "..."; + break; + } + case CPP_PLUS: + case CPP_MINUS: + { + bool unary (ptt != CPP_NAME && + ptt != CPP_SCOPE && + ptt != CPP_NUMBER && + ptt != CPP_STRING && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_PLUS_PLUS && + ptt != CPP_MINUS_MINUS); + + if (!unary) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + + if (!unary) + r += ' '; + break; + } + case CPP_PLUS_PLUS: + case CPP_MINUS_MINUS: + { + if (ptt != CPP_NAME && + ptt != CPP_CLOSE_PAREN && + ptt != CPP_CLOSE_SQUARE) + add_space (r); + + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_DEREF: + case CPP_DEREF_STAR: + case CPP_DOT: + case CPP_DOT_STAR: + { + r += cxx_lexer::token_spelling[tt]; + break; + } + case CPP_STRING: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += strlit (tl); + break; + } + case CPP_NUMBER: + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += tl; + break; + } + case CPP_SCOPE: + case CPP_NAME: + { + // Start of a name. + // + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + // Check if this is a pointer expression. + // + // If r is not empty, then it means this is not just the + // name. If placeholder is not 0, then we are translating + // a query expression, not a join condition. + // + expression e ( + translate_name ( + l, tt, tl, tn, ptt, + scope, loc, prag, + r.empty () && placeholder == 0, amap, omap)); + + if (e.kind == expression::literal) + r += e.value; + else + return e; + + continue; // We have already extracted the next token. + } + case CPP_QUERY: + { + if (placeholder != 0 && !*placeholder) + { + if (ptt == CPP_OPEN_PAREN) + { + // Get the next token and see if it is ')'. + // + ptt = tt; + tt = l.next (tl, &tn); + + if (tt == CPP_CLOSE_PAREN) + { + *placeholder = true; + + // Predicate is true if this is a SELECT statement clause. + // Otherwise it is a stored procedure parameters. + // + if (predicate) + r += "q.empty () ? query_base_type::true_expr : q"; + else + { + r.resize (r.size () - 1); // Remove opening paren. + r += "q"; + break; // Skip the closing paren as well. + } + } + else + { + // The same as in the default case. + // + add_space (r); + r += "? "; + } + continue; // We have already gotten the next token. + } + } + } + // Fall through. + default: + { + // Handle CPP_KEYWORD here to avoid a warning (it is not + // part of the cpp_ttype enumeration). + // + if (tt == CPP_KEYWORD) + { + if (ptt == CPP_NAME || + ptt == CPP_KEYWORD || + ptt == CPP_STRING || + ptt == CPP_NUMBER) + add_space (r); + + r += tl; + } + else + { + // All the other operators. + // + add_space (r); + r += cxx_lexer::token_spelling[tt]; + r += ' '; + } + break; + } + } + + // + // Watch out for the continue statements above if you add any + // logic here. + // + + ptt = tt; + tt = l.next (tl, &tn); + } + + return e; + } + + void + generate () + { + context ctx; + ostream& os (ctx.os); + + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (false); + traversal::namespace_ ns; + instance<class_> c; + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (false); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + instance<include> i; + i->generate (); + + os << "namespace odb" + << "{"; + + unit.dispatch (ctx.unit); + + os << "}"; + } + } +} diff --git a/odb/odb/relational/source.hxx b/odb/odb/relational/source.hxx new file mode 100644 index 0000000..ba6b2be --- /dev/null +++ b/odb/odb/relational/source.hxx @@ -0,0 +1,7158 @@ +// file : odb/relational/source.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SOURCE_HXX +#define ODB_RELATIONAL_SOURCE_HXX + +#include <map> +#include <set> +#include <list> +#include <vector> +#include <sstream> + +#include <odb/diagnostics.hxx> + +#include <odb/relational/context.hxx> +#include <odb/relational/common.hxx> +#include <odb/relational/schema.hxx> + +namespace relational +{ + namespace source + { + // Column literal in a statement (e.g., in select-list, etc). + // + struct statement_column + { + statement_column (): member (0) {} + statement_column (std::string const& tbl, + std::string const& col, + std::string const& t, + semantics::data_member& m, + std::string const& kp = "") + : table (tbl), column (col), type (t), member (&m), key_prefix (kp) + { + } + + std::string table; // Schema-qualifed and quoted table name. + std::string column; // Table-qualifed and quoted column expr. + std::string type; // Column SQL type. + semantics::data_member* member; + std::string key_prefix; + }; + + typedef std::list<statement_column> statement_columns; + + // Query parameter generator. A new instance is created for each + // query, so the customized version can have a counter to implement, + // for example, numbered parameters (e.g., $1, $2, etc). The auto_id() + // function is called instead of next() for the automatically-assigned + // object id member when generating the persist statement. If empty + // string is returned, then parameter is ignored. + // + struct query_parameters: virtual context + { + typedef query_parameters base; + + query_parameters (statement_kind sk, qname const& table) + : sk_ (sk), table_ (table) {} + + virtual string + next (semantics::data_member&, + const std::string& /*column*/, // Table qualified and quoted. + const std::string& /*sqlt*/) + { + return "?"; + } + + virtual string + auto_id (semantics::data_member& m, + const std::string& column, + const std::string& sqlt) + { + return next (m, column, sqlt); + } + + string + next (const object_columns_list::column& c) + { + return next (*c.member, quote_id (c.name), c.type); + } + + string + next (const statement_column& c) + { + return next (*c.member, c.column, c.type); + } + + protected: + statement_kind sk_; + qname table_; + }; + + struct object_columns: object_columns_base, virtual context + { + typedef object_columns base; + + // If provided, used to resolve table/alias names for inverse + // pointed-to and base objects. Returns qualified name. + // + struct table_name_resolver + { + virtual string + resolve_pointer (semantics::data_member&) const = 0; + + virtual string + resolve_base (semantics::class_&) const = 0; + }; + + object_columns (statement_kind sk, + statement_columns& sc, + query_parameters* param = 0, + object_section* section = 0) + : object_columns_base (true, true, section), + sk_ (sk), + ro_ (true), + sc_ (sc), + param_ (param), + table_name_resolver_ (0), + depth_ (1) + { + } + + object_columns (statement_kind sk, + bool ignore_ro, + statement_columns& sc, + query_parameters* param) + : object_columns_base (true, true, 0), + sk_ (sk), + ro_ (ignore_ro), + sc_ (sc), + param_ (param), + table_name_resolver_ (0), + depth_ (1) + { + } + + object_columns (std::string const& table_qname, + statement_kind sk, + statement_columns& sc, + size_t depth = 1, + object_section* section = 0, + table_name_resolver* tnr = 0) + : object_columns_base (true, true, section), + sk_ (sk), + ro_ (true), + sc_ (sc), + param_ (0), + table_name_ (table_qname), + table_name_resolver_ (tnr), + depth_ (depth) + { + } + + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section for + // SELECT statements. Also include optimistic version into + // section's SELECT and UPDATE statements. + // + return section_ == 0 || + *section_ == s || + (sk_ == statement_select && + *section_ == main_section && + !s.separate_load ()) || + (version (mp) && + (sk_ == statement_update || sk_ == statement_select)); + } + + virtual void + traverse_object (semantics::class_& c) + { + // If we are generating a select statement and this is a derived + // type in a polymorphic hierarchy, then we need to include base + // columns, but do it in reverse order as well as switch the table + // name (base columns come from different tables). + // + semantics::class_* poly_root (polymorphic (c)); + if (poly_root != 0 && poly_root != &c) + { + names (c); + + if (sk_ == statement_select && --depth_ != 0) + { + semantics::class_& b (polymorphic_base (c)); + + table_name_ = table_name_resolver_ != 0 + ? table_name_resolver_->resolve_base (b) + : table_qname (b); + + inherits (c); + } + } + else + object_columns_base::traverse_object (c); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore polymorphic id references for select statements. + // + if (sk_ == statement_select && m.count ("polymorphic-ref")) + return; + + data_member_path* imp (inverse (m, key_prefix_)); + + // Ignore certain columns depending on what kind of statement we are + // generating. Columns corresponding to the inverse members are + // only present in the select statements. + // + if (imp != 0 && sk_ != statement_select) + return; + + // Inverse object pointers come from a joined table. + // + if (imp != 0) + { + bool poly (polymorphic (c) != 0); + semantics::data_member& imf (*imp->front ()); + semantics::data_member& imb (*imp->back ()); + + // In a polymorphic hierarchy the inverse member can be in + // the base class, in which case we should use that table. + // + semantics::class_& imc ( + poly ? dynamic_cast<semantics::class_&> (imf.scope ()) : c); + + data_member_path& id (*id_member (imc)); + semantics::type& idt (utype (id)); + + if (container (imb)) + { + // This container is a direct member of the class so the table + // prefix is just the class table name. We don't assign join + // aliases for container tables so use the actual table name. + // Note that the if(!table_name_.empty ()) test may look wrong + // at first but it is not; if table_name_ is empty then we are + // generating a container table where we don't qualify columns + // with tables. + // + string table; + + if (!table_name_.empty ()) + { + if (table_name_resolver_ != 0) + table = table_name_resolver_->resolve_pointer (m); + else + table = table_qname (imc, *imp); + } + + instance<object_columns> oc (table, sk_, sc_); + oc->traverse (imb, idt, "id", "object_id", &imc); + } + else + { + // Use the join alias instead of the actual table name unless we + // are handling a container. Generally, we want the join alias + // 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. Note that the + // if(!table_name_.empty ()) test may look wrong at first but + // it is not; if table_name_ is empty then we are generating a + // container table where we don't qualify columns with tables. + // + string alias; + + if (!table_name_.empty ()) + { + if (table_name_resolver_ != 0) + alias = table_name_resolver_->resolve_pointer (m); + else + { + string n; + + if (composite_wrapper (idt)) + { + n = column_prefix (m, key_prefix_, default_name_).prefix; + + if (n.empty ()) + n = public_name_db (m); + else if (n[n.size () - 1] == '_') + n.resize (n.size () - 1); // Remove trailing underscore. + } + else + { + bool dummy; + n = column_name (m, key_prefix_, default_name_, dummy); + } + + alias = column_prefix_.prefix + n; + + if (poly) + { + qname const& table (table_name (imc)); + alias = quote_id (alias + "_" + table.uname ()); + } + else + alias = quote_id (alias); + } + } + + instance<object_columns> oc (alias, sk_, sc_); + oc->traverse (id); + } + } + else + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, string const& name, bool) + { + // Ignore certain columns depending on what kind statement we are + // generating. Id and readonly columns are not present in the update + // statements. + // + if ((id () || readonly (member_path_, member_scope_)) && + sk_ == statement_update && ro_) + return false; + + return column (m, table_name_, quote_id (name)); + } + + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + string r; + + if (!table.empty ()) + { + r += table; // Already quoted. + r += '.'; + } + + r += column; // Already quoted. + + string const& sqlt (column_type ()); + + // Version column (optimistic concurrency) requires special + // handling in the UPDATE statement. + // + // + if (sk_ == statement_update && version (m)) + { + r += "=" + r + "+1"; + } + else if (param_ != 0) + { + r += '='; + r += convert_to (param_->next (m, column, sqlt), sqlt, m); + } + else if (sk_ == statement_select) + r = convert_from (r, sqlt, m); + + sc_.push_back (statement_column (table, r, sqlt, m, key_prefix_)); + return true; + } + + protected: + statement_kind sk_; + bool ro_; + statement_columns& sc_; + query_parameters* param_; + string table_name_; + table_name_resolver* table_name_resolver_; + size_t depth_; + }; + + struct view_columns: object_columns_base, + object_columns::table_name_resolver, + virtual context + { + typedef view_columns base; + + view_columns (statement_columns& sc, + strings& from, + const view_relationship_map& rm) + : sc_ (sc), from_ (from), rel_map_ (rm), in_composite_ (false) {} + + // Implementation of table_name_resolver for object_columns. + // + virtual string + resolve_pointer (semantics::data_member& m) const + { + view_object& us (*ptr_->get<view_object*> ("view-object")); + + data_member_path& imp (*inverse (m)); + semantics::data_member& imf (*imp.front ()); + semantics::data_member& imb (*imp.back ()); + + using semantics::class_; + typedef view_relationship_map::const_iterator iterator; + + std::pair<iterator, iterator> r (rel_map_.equal_range (imp)); + + for (; r.first != r.second; ++r.first) + { + if (r.first->second.second != &us) // Not our associated. + continue; + + view_object& vo (*r.first->second.first); // First because inverse. + + // Derive the table name the same way as the JOIN code. + // + class_* c (vo.obj); + if (class_* root = polymorphic (*c)) + { + // Can be in base. + // + c = &static_cast<class_&> (imf.scope ()); + + if (!polymorphic (*c)) + c = root; + } + + string const& a (vo.alias); + + if (container (imb)) + { + // If this is a container, then object_columns will use the + // column from the container table, not from the object table + // (which, strictly speaking, might not have been JOIN'ed). + // + qname t (table_name (*c, imp)); + return a.empty () + ? quote_id (t) + : quote_id (a + '_' + t.uname ()); + } + else + { + qname t; + if (a.empty ()) + t = table_name (*c); + else + { + if (polymorphic (*c)) + t = qname (a + "_" + table_name (*c).uname ()); + else + t = qname (a); + } + return quote_id (t); + } + } + + // So there is no associated object for this column. The initial + // plan was to complain and ask the user to explicitly associate + // the object. This is not a bad plan except for one thing: if + // the direct side of the relationship is a container, then + // associating that object explicitly will result in both the + // container table and the object table being JOIN'ed. But we + // only need the container table (for the object id) So we will + // be joining a table for nothing, which is not very clean. So + // the alternative, and more difficult, plan is to go ahead and + // add the necessary JOIN's automatically. + // + // This code follows the normal JOIN generation code. + // + class_* o (object_pointer (utype (m))); + if (class_* root = polymorphic (*o)) + { + o = &static_cast<class_&> (imf.scope ()); + + if (!polymorphic (*o)) + o = root; + } + + string const& a (us.alias); + string lt (a.empty () ? table_qname (*us.obj) : quote_id (a)); + string rt; + qname ct (container (imb) ? table_name (*o, imp) : table_name (*o)); + + string l ("LEFT JOIN "); + + if (a.empty ()) + { + rt = quote_id (ct); + l += rt; + } + else + { + // The same relationship can be used by multiple associated + // objects. So if we have an alias, then also construct one + // for the table that we are joining. + // + rt = quote_id (a + '_' + ct.uname ()); + l += quote_id (ct); + l += (need_alias_as ? " AS " : " ") + rt; + } + + l += " ON"; + from_.push_back (l); + + instance<object_columns_list> l_cols; // Our id columns. + instance<object_columns_list> r_cols; // Other side id columns. + + data_member_path& id (*id_member (*us.obj)); + + l_cols->traverse (id); + + if (container (imb)) + r_cols->traverse (imb, utype (id), "value", "value"); + else + r_cols->traverse (imb, column_prefix (imp)); + + for (object_columns_list::iterator b (l_cols->begin ()), i (b), + j (r_cols->begin ()); i != l_cols->end (); ++i, ++j) + { + l.clear (); + + if (i != b) + l += "AND "; + + l += lt; + l += '.'; + l += quote_id (i->name); + l += '='; + l += rt; + l += '.'; + l += quote_id (j->name); + + from_.push_back (strlit (l)); + } + + return rt; + + /* + // The alternative implementation: + // + location const& l1 (m.location ()); + location const& l2 (ptr_->location ()); + + string n1 (class_name (*object_pointer (utype (m)))); + string n2 (class_name (*object_pointer (utype (*ptr_)))); + + error (l1) << "object '" << n1 << "' pointed-to by the inverse " + << "data member in object '" << n2 << "' must be " + << "explicitly associated with the view" << endl; + + info (l2) << "view data member that loads '" << n2 << "' is " + << "defined here" << endl; + + throw operation_failed (); + */ + } + + virtual string + resolve_base (semantics::class_& b) const + { + view_object& vo (*ptr_->get<view_object*> ("view-object")); + + qname t (vo.alias.empty () + ? table_name (b) + : qname (vo.alias + "_" + table_name (b).uname ())); + + return quote_id (t); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + type* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + size_t poly_depth (poly_derived ? polymorphic_depth (c) : 1); + + view_object& vo (*m.get<view_object*> ("view-object")); + string const& a (vo.alias); + + qname t; + if (a.empty ()) + t = table_name (c); + else + { + if (poly) + t = qname (a + "_" + table_name (c).uname ()); + else + t = qname (a); + } + string qt (quote_id (t)); + + ptr_ = &m; + + statement_kind sk (statement_select); // Imperfect forwarding. + object_section* s (&main_section); // Imperfect forwarding. + instance<object_columns> oc (qt, sk, sc_, poly_depth, s, this); + oc->traverse (c); + } + + virtual void + traverse_composite (semantics::data_member* pm, semantics::class_& c) + { + if (in_composite_) + { + object_columns_base::traverse_composite (pm, c); + return; + } + + // Override the column prerix. + // + semantics::data_member& m (*pm); + + // If we have literal column specified, use that. + // + if (m.count ("column")) + { + table_column const& tc (m.get<table_column> ("column")); + + if (!tc.table.empty ()) + table_prefix_ = tc.table; + + column_prefix_ = object_columns_base::column_prefix (m); + } + // Otherwise, see if there is a column expression. For composite + // members in a view, this should be a single reference. + // + else if (m.count ("column-expr")) + { + column_expr const& e (m.get<column_expr> ("column-expr")); + + if (e.size () > 1) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column expression specified for a data member " + << "of a composite value type" << endl; + + throw operation_failed (); + } + + data_member_path const& mp (e.back ().member_path); + + if (mp.size () > 1) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: invalid data member in db pragma column" + << endl; + + throw operation_failed (); + } + + table_prefix_ = e.back ().table; + column_prefix_ = object_columns_base::column_prefix (*mp.back ()); + } + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: no column prefix provided for a view data member" + << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": info: use db pragma column to specify the column prefix" + << endl; + + throw operation_failed (); + } + + in_composite_ = true; + object_columns_base::traverse_composite (pm, c); + in_composite_ = false; + } + + virtual bool + traverse_column (semantics::data_member& m, string const& name, bool) + { + string tbl; + string col; + + // If we are inside a composite value, use the standard + // column name machinery. + // + if (in_composite_) + { + if (!table_prefix_.empty ()) + { + tbl = quote_id (table_prefix_); + col += tbl; + col += '.'; + } + + col += quote_id (name); + } + // If we have literal column specified, use that. + // + else if (m.count ("column")) + { + table_column const& tc (m.get<table_column> ("column")); + + if (!tc.expr) + { + if (!tc.table.empty ()) + { + tbl = quote_id (tc.table); + col += tbl; + col += '.'; + } + + col += quote_id (tc.column); + } + else + col += tc.column; + } + // Otherwise, see if there is a column expression. + // + else if (m.count ("column-expr")) + { + column_expr const& e (m.get<column_expr> ("column-expr")); + + for (column_expr::const_iterator i (e.begin ()); i != e.end (); ++i) + { + switch (i->kind) + { + case column_expr_part::literal: + { + col += i->value; + break; + } + case column_expr_part::reference: + { + tbl = quote_id (i->table); + col += tbl; + col += '.'; + col += column_qname (i->member_path); + break; + } + } + } + } + else + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: no column name provided for a view data member" + << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": info: use db pragma column to specify the column name" + << endl; + + throw operation_failed (); + } + + return column (m, tbl, col); + } + + // The column argument is a qualified and quoted column or + // expression. + // + virtual bool + column (semantics::data_member& m, + string const& table, + string const& column) + { + string const& sqlt (column_type ()); + sc_.push_back ( + statement_column ( + table, convert_from (column, sqlt, m), sqlt, m)); + return true; + } + + protected: + statement_columns& sc_; + strings& from_; + const view_relationship_map& rel_map_; + + bool in_composite_; + qname table_prefix_; // Table corresponding to column_prefix_; + + // Set to the current pointer data member that we are traversing. + // + semantics::data_member* ptr_; + }; + + struct polymorphic_object_joins: object_columns_base, virtual context + { + typedef polymorphic_object_joins base; + + polymorphic_object_joins (semantics::class_& obj, + bool query, + size_t depth, + string const& alias = "", + user_section* section = 0) + : object_columns_base (true, true), + obj_ (obj), + query_ (query), + depth_ (depth), + section_ (section), + alias_ (alias) + { + // Get the table and id columns. + // + table_ = alias_.empty () + ? table_qname (obj_) + : quote_id (alias_ + "_" + table_name (obj_).uname ()); + + cols_->traverse (*id_member (obj_)); + } + + virtual void + traverse_object (semantics::class_& c) + { + // If section is specified, skip bases that don't add anything + // to load. + // + bool skip (false), stop (false); + if (section_ != 0) + { + skip = true; + + if (section_->object == &c) + { + user_section& s (*section_); + + if (s.total != 0 || s.optimistic ()) + skip = false; + + section_ = s.base; // Move to the next base. + + if (section_ == 0) + stop = true; // Stop at this base if there are no more overrides. + } + } + // Skip intermediates that don't add any data members. + // + else if (!query_) + { + column_count_type const& cc (column_count (c)); + if (cc.total == cc.id + cc.separate_load) + skip = true; + } + + if (!skip) + { + std::ostringstream cond; + + qname table (table_name (c)); + string alias (alias_.empty () + ? quote_id (table) + : quote_id (alias_ + "_" + table.uname ())); + + for (object_columns_list::iterator b (cols_->begin ()), i (b); + i != cols_->end (); + ++i) + { + if (i != b) + cond << " AND "; + + string qn (quote_id (i->name)); + cond << alias << '.' << qn << '=' << table_ << '.' << qn; + } + + string line ("LEFT JOIN " + quote_id (table)); + + if (!alias_.empty ()) + line += (need_alias_as ? " AS " : " ") + alias; + + line += " ON " + cond.str (); + + joins.push_back (line); + } + + if (!stop && --depth_ != 0) + inherits (c); + } + + public: + strings joins; + + strings::const_iterator + begin () const {return joins.begin ();} + + strings::const_iterator + end () const {return joins.end ();} + + private: + semantics::class_& obj_; + bool query_; + size_t depth_; + user_section* section_; + string alias_; + string table_; + instance<object_columns_list> cols_; + }; + + struct object_joins: object_columns_base, virtual context + { + typedef object_joins base; + + //@@ context::{cur,top}_object; might have to be created every time. + // + object_joins (semantics::class_& scope, + bool query, + size_t depth, + object_section* section = 0) + : object_columns_base (true, true, section), + query_ (query), + depth_ (depth), + table_ (table_qname (scope)), + id_ (*id_member (scope)) + { + id_cols_->traverse (id_); + } + + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section. + // + return section_ == 0 || + *section_ == s || + (*section_ == main_section && !s.separate_load ()); + } + + virtual void + traverse_object (semantics::class_& c) + { + // If this is a derived type in a polymorphic hierarchy, then we + // need to include base joins, but do it in reverse order as well + // as switch the table name (base columns come from different + // tables). + // + semantics::class_* poly_root (polymorphic (c)); + if (poly_root != 0 && poly_root != &c) + { + names (c); + + if (query_ || --depth_ != 0) + { + table_ = table_qname (polymorphic_base (c)); + inherits (c); + } + } + else + object_columns_base::traverse_object (c); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore polymorphic id references; they are joined by + // polymorphic_object_joins in a special way. + // + if (m.count ("polymorphic-ref")) + return; + + string t, a, dt, da; + std::ostringstream cond, dcond; // @@ diversion? + + // Derive table alias for this member. Generally, we want the + // alias 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 alias; + { + string n; + + if (composite_wrapper (utype (*id_member (c)))) + { + n = column_prefix (m, key_prefix_, default_name_).prefix; + + if (n.empty ()) + n = public_name_db (m); + else if (n[n.size () - 1] == '_') + n.resize (n.size () - 1); // Remove trailing underscore. + } + else + { + bool dummy; + n = column_name (m, key_prefix_, default_name_, dummy); + } + + alias = column_prefix_.prefix + n; + } + + semantics::class_* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + + semantics::class_* joined_obj (0); + + if (data_member_path* imp = inverse (m, key_prefix_)) + { + semantics::data_member& imf (*imp->front ()); + semantics::data_member& imb (*imp->back ()); + + // In a polymorphic hierarchy the inverse member can be in + // the base class, in which case we should use that table. + // + semantics::class_& imc ( + poly ? dynamic_cast<semantics::class_&> (imf.scope ()) : c); + + if (container (imb)) + { + // This container is a direct member of the class so the table + // prefix is just the class table name. + // + t = table_qname (imc, *imp); + + // Container's value is our id. + // + instance<object_columns_list> id_cols; + id_cols->traverse (imb, utype (id_), "value", "value"); + + for (object_columns_list::iterator b (id_cols->begin ()), i (b), + j (id_cols_->begin ()); i != id_cols->end (); ++i, ++j) + { + + if (i != b) + cond << " AND "; + + cond << t << '.' << quote_id (i->name) << '=' << + table_ << '.' << quote_id (j->name); + } + + // Add the join for the object itself so that we are able to + // use it in the WHERE clause. + // + if (query_) + { + // Here we can use the most derived class instead of the + // one containing the inverse member. + // + qname const& table (table_name (c)); + + dt = quote_id (table); + da = quote_id (poly ? alias + "_" + table.uname () : alias); + + data_member_path& id (*id_member (c)); + + instance<object_columns_list> oid_cols, cid_cols; + oid_cols->traverse (id); + cid_cols->traverse (imb, utype (id), "id", "object_id", &c); + + for (object_columns_list::iterator b (cid_cols->begin ()), i (b), + j (oid_cols->begin ()); i != cid_cols->end (); ++i, ++j) + { + + if (i != b) + dcond << " AND "; + + dcond << da << '.' << quote_id (j->name) << '=' << + t << '.' << quote_id (i->name); + } + + joined_obj = &c; + } + } + else + { + qname const& table (table_name (imc)); + + t = quote_id (table); + a = quote_id (poly ? alias + "_" + table.uname () : alias); + + instance<object_columns_list> id_cols; + id_cols->traverse (imb, column_prefix (*imp)); + + for (object_columns_list::iterator b (id_cols->begin ()), i (b), + j (id_cols_->begin ()); i != id_cols->end (); ++i, ++j) + { + if (i != b) + cond << " AND "; + + cond << a << '.' << quote_id (i->name) << '=' << + table_ << '.' << quote_id (j->name); + } + + // If we are generating query, JOIN base/derived classes so + // that we can use their data in the WHERE clause. + // + if (query_) + joined_obj = &imc; + } + } + else if (query_) + { + // We need the join to be able to use the referenced object + // in the WHERE clause. + // + qname const& table (table_name (c)); + + t = quote_id (table); + a = quote_id (poly ? alias + "_" + table.uname () : alias); + + instance<object_columns_list> oid_cols (column_prefix_); + oid_cols->traverse (m); + + instance<object_columns_list> pid_cols; + pid_cols->traverse (*id_member (c)); + + for (object_columns_list::iterator b (pid_cols->begin ()), i (b), + j (oid_cols->begin ()); i != pid_cols->end (); ++i, ++j) + { + + if (i != b) + cond << " AND "; + + cond << a << '.' << quote_id (i->name) << '=' << + table_ << '.' << quote_id (j->name); + } + + joined_obj = &c; + } + + if (!t.empty ()) + { + string line ("LEFT JOIN "); + line += t; + + if (!a.empty ()) + line += (need_alias_as ? " AS " : " ") + a; + + line += " ON "; + line += cond.str (); + + joins.push_back (line); + } + + // Add dependent join (i.e., an object table join via the + // container table). + // + if (!dt.empty ()) + { + string line ("LEFT JOIN "); + line += dt; + + if (!da.empty ()) + line += (need_alias_as ? " AS " : " ") + da; + + line += " ON "; + line += dcond.str (); + + joins.push_back (line); + } + + // If we joined the object that is part of a polymorphic type + // hierarchy, then we may need join its bases as well as its + // derived types. This is only done for queries. + // + if (joined_obj != 0 && poly) + { + size_t depth (polymorphic_depth (*joined_obj)); + + // Join "up" (derived). + // + if (joined_obj != &c) + { + bool t (true); //@@ (im)perfect forward. + size_t d (polymorphic_depth (c) - depth); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (*joined_obj, t, d, alias); + j->traverse (c); + joins.insert (joins.end (), j->joins.begin (), j->joins.end ()); + } + + // Join "down" (base). + // + if (joined_obj != poly_root) + { + bool t (true); //@@ (im)perfect forward. + size_t d (depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (*joined_obj, t, d, alias); + j->traverse (polymorphic_base (*joined_obj)); + joins.insert (joins.end (), j->joins.begin (), j->joins.end ()); + } + } + } + + public: + strings joins; + + strings::const_iterator + begin () const {return joins.begin ();} + + strings::const_iterator + end () const {return joins.end ();} + + private: + bool query_; + size_t depth_; + string table_; + data_member_path& id_; + instance<object_columns_list> id_cols_; + }; + + // Check that eager object pointers in the objects that a view loads + // can be loaded from the cache (i.e., they have session support + // enabled). + // + struct view_object_check: object_members_base + { + typedef view_object_check base; + + view_object_check (view_object& vo, view_relationship_map& rm) + : object_members_base (false, &main_section), + session_ (false), vo_ (vo), rel_map_ (rm) {} + + virtual bool + section_test (data_member_path const& mp) + { + // Include eager loaded members into the main section. + // + object_section& s (section (mp)); + return *section_ == s || !s.separate_load (); + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + // Ignore polymorphic id references. + // + if (m.count ("polymorphic-ref")) + return; + + check (m, inverse (m), utype (m), c); + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + semantics::type& vt (container_vt (m)); + + if (semantics::class_* cvt = composite_wrapper (vt)) + { + // Check this composite value for any pointers. + // + instance<view_object_check> t (vo_, rel_map_); + t->traverse (*cvt); + + session_ = session_ || t->session_; + } + else if (semantics::class_* c = object_pointer (vt)) + check (m, inverse (m, "value"), vt, *c); + } + + void + check (semantics::data_member& m, + data_member_path* imp, + semantics::type& pt, // Pointer type. + semantics::class_& c) + { + // We don't care about lazy pointers. + // + if (lazy_pointer (pt)) + return; + + // First check the pointed-to object recursively. + // + if (!c.count ("view-object-check-seen")) + { + c.set ("view-object-check-seen", true); + instance<view_object_check> t (vo_, rel_map_); + t->traverse (c); + + // We may come again for another view. + // + c.remove ("view-object-check-seen"); + + session_ = session_ || t->session_; + } + + // See if the pointed-to object in this relationship is loaded + // by this view. + // + typedef view_relationship_map::const_iterator iterator; + + std::pair<iterator, iterator> r ( + rel_map_.equal_range (imp != 0 ? *imp : member_path_)); + + if (r.first == r.second) + return; // This relationship does not figure in the view. + + view_object& vo (*(imp != 0 + ? r.first->second.first + : r.first->second.second)); + + assert (vo.obj == &c); // Got the above right? + + if (vo.ptr == 0) + return; // This object is not loaded by the view. + + // The pointed-to object in this relationship is loaded + // by the view. The hard question, of course, is whether + // it has anything to do with us. We assume it does. + // + if (!session (c)) + { + // Always direct data member. + // + semantics::class_& v ( + dynamic_cast<semantics::class_&> (vo.ptr->scope ())); + + location const& l1 (c.location ()); + location const& l2 (m.location ()); + location const& l3 (vo_.ptr->location ()); + location const& l4 (vo.ptr->location ()); + + string on (class_name (c)); + string vn (class_name (v)); + + error (l1) << "object '" << on << "' has session support disabled " + << "but may be loaded by view '" << vn << "' via " + << "several data members" << endl; + + info (l2) << "indirectly via this data member..." << endl; + info (l3) << "...as a result of this object load" << endl; + info (l4) << "and directly as a result of this load" << endl; + info (l1) << "session support is required to only load one copy " + << "of the object" << endl; + info (l1) << "and don't forget to create a session instance when " + << "using this view" << endl; + + throw operation_failed (); + } + + session_ = true; + } + + bool session_; + + private: + view_object& vo_; + view_relationship_map& rel_map_; + }; + + // + // bind + // + + struct bind_member: virtual member_base + { + typedef bind_member base; + + // NULL section means we are generating object bind(). + // + bind_member (string const& var = string (), + string const& arg = string (), + object_section* section = 0) + : member_base (var, 0, 0, string (), string (), section), + arg_override_ (arg) {} + + bind_member (string const& var, + string const& arg, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix), + arg_override_ (arg) {} + + protected: + string arg_override_; + }; + + template <typename T> + struct bind_member_impl: bind_member, virtual member_base_impl<T> + { + typedef bind_member_impl base_impl; + + bind_member_impl (base const& x): base (x) {} + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual bool + pre (member_info& mi) + { + if (container (mi)) + return false; + + // Treat version as present in every section. + // + if (section_ != 0 && !version (mi.m) && *section_ != section (mi.m)) + return false; + + // Ignore polymorphic id references; they are bound in a special + // way. + // + if (mi.ptr != 0 && mi.m.count ("polymorphic-ref")) + return false; + + std::ostringstream ostr; + ostr << "b[n]"; + b = ostr.str (); + + arg = arg_override_.empty () ? string ("i") : arg_override_; + + if (var_override_.empty ()) + { + // Ignore inverse, separately-loaded members in the main + // section (nothing to persist). + // + if (section_ == 0 && separate_load (mi.m) && inverse (mi.m)) + return false; + + semantics::class_* comp (composite (mi.t)); + + os << "// " << mi.m.name () << endl + << "//" << endl; + + // Order of these tests is important. + // + if (!insert_send_auto_id && auto_ (mi.m)) + os << "if (sk != statement_insert && sk != statement_update)" + << "{"; + else if (section_ == 0 && separate_load (mi.m)) + os << "if (sk == statement_insert)" + << "{"; + else if (inverse (mi.m, key_prefix_) || version (mi.m)) + os << "if (sk == statement_select)" + << "{"; + // If the whole class is readonly, then we will never be + // called with sk == statement_update. + // + else if (!readonly (*context::top_object)) + { + if (id (mi.m) || + readonly (mi.m) || + (comp != 0 && readonly (*comp)) || + (section_ == 0 && separate_update (mi.m))) + os << "if (sk != statement_update)" + << "{"; + } + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + // If this is a composite member, see if it is summarily + // added/deleted. + // + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + } + + return true; + } + + virtual void + post (member_info& mi) + { + if (var_override_.empty ()) + { + semantics::class_* comp (composite (mi.t)); + + // We need to increment the index even if we skipped this + // member due to the schema version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + os << "}"; + + if (mi.ptr != 0 && view_member (mi.m)) + { + // See column_count_impl for details on what's going on here. + // + column_count_type cc; + if (semantics::class_* root = polymorphic (*mi.ptr)) + { + for (semantics::class_* b (mi.ptr);; b = &polymorphic_base (*b)) + { + column_count_type const& ccb (column_count (*b)); + + cc.total += ccb.total - (b != root ? ccb.id : 0); + cc.separate_load += ccb.separate_load; + + if (b == root) + break; + } + } + else + cc = column_count (*mi.ptr); + + os << "n += " << cc.total - cc.separate_load << "UL;"; + } + else if (comp != 0) + { + bool ro (readonly (*comp)); + column_count_type const& cc (column_count (*comp)); + + os << "n += " << cc.total << "UL"; + + // select = total + // insert = total - inverse + // update = total - inverse - readonly + // + if (cc.inverse != 0 || (!ro && cc.readonly != 0)) + { + os << " - (" << endl + << "sk == statement_select ? 0 : "; + + if (cc.inverse != 0) + os << cc.inverse << "UL"; + + if (!ro && cc.readonly != 0) + { + if (cc.inverse != 0) + os << " + "; + + os << "(" << endl + << "sk == statement_insert ? 0 : " << + cc.readonly << "UL)"; + } + + os << ")"; + } + + os << ";"; + } + else + os << "n++;"; + + bool block (false); + + // The same logic as in pre(). + // + if (!insert_send_auto_id && auto_ (mi.m)) + block = true; + else if (section_ == 0 && separate_load (mi.m)) + block = true; + else if (inverse (mi.m, key_prefix_) || version (mi.m)) + block = true; + else if (!readonly (*context::top_object)) + { + semantics::class_* c; + + if (id (mi.m) || + readonly (mi.m) || + ((c = composite (mi.t)) && readonly (*c)) || + (section_ == 0 && separate_update (mi.m))) + block = true; + } + + if (block) + os << "}"; + else + os << endl; + } + } + + virtual void + traverse_pointer (member_info& mi) + { + // Object pointers in views require special treatment. + // + if (view_member (mi.m)) + { + semantics::class_& c (*mi.ptr); + semantics::class_* poly_root (polymorphic (c)); + bool poly_derived (poly_root != 0 && poly_root != &c); + + os << "object_traits_impl< " << class_fq_name (c) << ", id_" << + db << " >::bind (" << endl + << "b + n, " << (poly_derived ? "0, 0, " : "") << arg << "." << + mi.var << "value, sk" << (versioned (c) ? ", svm" : "") << ");"; + } + else + member_base_impl<T>::traverse_pointer (mi); + } + + virtual void + traverse_composite (member_info& mi) + { + os << "composite_value_traits< " << mi.fq_type () << ", id_" << + db << " >::bind (" << endl + << "b + n, " << arg << "." << mi.var << "value, sk" << + (versioned (*composite (mi.t)) ? ", svm" : "") << ");"; + } + + protected: + string b; + string arg; + }; + + struct bind_base: traversal::class_, virtual context + { + typedef bind_base base; + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + os << "// " << class_name (c) << " base" << endl + << "//" << endl; + + // If the derived class is readonly, then we will never be + // called with sk == statement_update. + // + bool ro (readonly (c)); + bool check (ro && !readonly (*context::top_object)); + + if (check) + os << "if (sk != statement_update)" + << "{"; + + if (obj) + os << "object_traits_impl< "; + else + os << "composite_value_traits< "; + + os << class_fq_name (c) << ", id_" << db << " >::bind (b + n, i, sk" << + (versioned (c) ? ", svm" : "") << ");"; + + column_count_type const& cc (column_count (c)); + + os << "n += "; + + // select = total - separate_load + // insert = total - inverse - optimistic_managed - id(auto & !sending) + // update = total - inverse - optimistic_managed - id - readonly - + // separate_update + // + size_t select (cc.total - cc.separate_load); + size_t insert (cc.total - cc.inverse - cc.optimistic_managed); + size_t update (insert - cc.id - cc.readonly - cc.separate_update); + + data_member_path* id; + if (!insert_send_auto_id && (id = id_member (c)) != 0 && auto_ (*id)) + insert -= cc.id; + + if (select == insert && insert == update) + os << select << "UL;"; + else if (select != insert && insert == update) + os << "sk == statement_select ? " << select << "UL : " << + insert << "UL;"; + else if (select == insert && insert != update) + os << "sk == statement_update ? " << update << "UL : " << + select << "UL;"; + else + os << "sk == statement_select ? " << select << "UL : " << + "sk == statement_insert ? " << insert << "UL : " << + update << "UL;"; + + if (check) + os << "}"; + else + os << endl; + } + }; + + // + // grow + // + + struct grow_member: virtual member_base + { + typedef grow_member base; + + grow_member (size_t& index, + string const& var = string (), + user_section* section = 0) + : member_base (var, 0, 0, string (), string (), section), + index_ (index) {} + + grow_member (size_t& index, + string const& var, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix), index_ (index) {} + + protected: + size_t& index_; + }; + + template <typename T> + struct grow_member_impl: grow_member, virtual member_base_impl<T> + { + typedef grow_member_impl base_impl; + + grow_member_impl (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual bool + pre (member_info& mi) + { + if (container (mi)) + return false; + + // If we have a key prefix (container), then it can't be in a section + // (while mi.m can). The same for top-level -- if we got called, then + // we shouldn't ignore it. (Same logic as in has_grow_member). + // + if (!(!key_prefix_.empty () || top_level_ || + (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)))) + return false; + + // Ignore polymorphic id references; they are not returned by + // the select statement. + // + if (mi.ptr != 0 && mi.m.count ("polymorphic-ref")) + return false; + + std::ostringstream ostr; + ostr << "t[" << index_ << "UL]"; + e = ostr.str (); + + if (var_override_.empty ()) + { + os << "// " << mi.m.name () << endl + << "//" << endl; + + semantics::class_* comp (composite (mi.t)); + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + // If this is a composite member, see if it is summarily + // added/deleted. + // + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + } + + return true; + } + + virtual void + post (member_info& mi) + { + semantics::class_* comp (composite (mi.t)); + + if (var_override_.empty ()) + { + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + os << "}"; + } + + if (mi.ptr != 0 && view_member (mi.m)) + { + // See column_count_impl for details on what's going on here. + // + column_count_type cc; + if (semantics::class_* root = polymorphic (*mi.ptr)) + { + for (semantics::class_* b (mi.ptr);; b = &polymorphic_base (*b)) + { + column_count_type const& ccb (column_count (*b)); + + cc.total += ccb.total - (b != root ? ccb.id : 0); + cc.separate_load += ccb.separate_load; + + if (b == root) + break; + } + } + else + cc = column_count (*mi.ptr); + + index_ += cc.total - cc.separate_load; + } + else if (comp != 0) + index_ += column_count (*comp).total; + else + index_++; + } + + virtual void + traverse_pointer (member_info& mi) + { + // Object pointers in views require special treatment. They + // can only be immediate members of the view class. + // + if (view_member (mi.m)) + { + semantics::class_& c (*mi.ptr); + + os << "if (object_traits_impl< " << class_fq_name (c) << + ", id_" << db << " >::grow (" << endl + << "i." << mi.var << "value, t + " << index_ << "UL" << + (versioned (c) ? ", svm" : "") << "))" << endl + << "grew = true;" + << endl; + } + else + member_base_impl<T>::traverse_pointer (mi); + } + + virtual void + traverse_composite (member_info& mi) + { + semantics::class_& c (*composite (mi.t)); + + os << "if (composite_value_traits< " << mi.fq_type () << + ", id_" << db << " >::grow (" << endl + << "i." << mi.var << "value, t + " << index_ << "UL" << + (versioned (c) ? ", svm" : "") << "))" << endl + << "grew = true;" + << endl; + } + + protected: + string e; + }; + + struct grow_base: traversal::class_, virtual context + { + typedef grow_base base; + + grow_base (size_t& index): index_ (index) {} + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + os << "// " << class_name (c) << " base" << endl + << "//" << endl; + + os << "if ("; + + if (obj) + os << "object_traits_impl< "; + else + os << "composite_value_traits< "; + + os << class_fq_name (c) << ", id_" << db << " >::grow (" << endl + << "i, t + " << index_ << "UL" << + (versioned (c) ? ", svm" : "") << "))" << endl + << "grew = true;" + << endl; + + index_ += column_count (c).total; + } + + protected: + size_t& index_; + }; + + // + // init image + // + + struct init_image_member: virtual member_base + { + typedef init_image_member base; + + init_image_member (string const& var = string (), + string const& member = string (), + user_section* section = 0) + : member_base (var, 0, 0, string (), string (), section), + member_override_ (member) + { + } + + init_image_member (string const& var, + string const& member, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix), + member_override_ (member) + { + } + + protected: + string member_override_; + }; + + template <typename T> + struct init_image_member_impl: init_image_member, + virtual member_base_impl<T> + { + typedef init_image_member_impl base_impl; + + init_image_member_impl (base const& x) + : base (x), + member_database_type_id_ (base::type_override_, + base::custom_override_, + base::fq_type_override_, + base::key_prefix_) + { + } + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual void + set_null (member_info&) = 0; + + virtual void + check_accessor (member_info&, member_access&) {} + + virtual bool + pre (member_info& mi) + { + // Ignore containers (they get their own table) and inverse + // object pointers (they are not present in this binding). + // + if (container (mi) || inverse (mi.m, key_prefix_)) + return false; + + if (section_ != 0 && *section_ != section (mi.m)) + return false; + + // Ignore polymorphic id references; they are initialized in a + // special way. + // + if (mi.ptr != 0 && mi.m.count ("polymorphic-ref")) + return false; + + semantics::class_* comp (composite (mi.t)); + + if (!member_override_.empty ()) + { + member = member_override_; + os << "{"; + } + else + { + // If we are generating standard init() and this member + // contains version, ignore it. + // + if (version (mi.m)) + return false; + + // If we don't send auto id in INSERT statement, ignore this + // member altogether (we never send auto id in UPDATE). + // + if (!insert_send_auto_id && auto_ (mi.m)) + return false; + + os << "// " << mi.m.name () << endl + << "//" << endl; + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + // If this is a composite member, see if it is summarily + // added/deleted. + // + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" + << "{"; + } + + // If the whole class is readonly, then we will never be + // called with sk == statement_update. + // + if (!readonly (*context::top_object)) + { + if (id (mi.m) || + readonly (mi.m) || + (section_ == 0 && separate_update (mi.m)) || + (comp != 0 && readonly (*comp))) // Can't be id. + { + // If we are generating section init(), then sk can only be + // statement_update. + // + if (section_ == 0) + os << "if (sk == statement_insert)"; + } + } + + os << "{"; + + if (discriminator (mi.m)) + member = "di.discriminator"; + else + { + // Get the member using the accessor expression. + // + member_access& ma (mi.m.template get<member_access> ("get")); + + // Make sure this kind of member can be accessed with this + // kind of accessor (database-specific, e.g., streaming). + // + if (comp == 0) + check_accessor (mi, ma); + + // If this is not a synthesized expression, then output + // its location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // Use the original type to form the const reference. VC++ + // cannot grok the constructor syntax. + // + os << member_ref_type (mi.m, true, "v") << " =" << endl + << " " << ma.translate ("o") << ";" + << endl; + + member = "v"; + } + } + + // Translate. + // + if (mi.ct != 0) + { + os << "// From " << location_string (mi.ct->loc, true) << endl + << type_ref_type (*mi.ct->as, mi.ct->as_hint, true, "vt") << + " =" << endl + << " " << mi.ct->translate_to (member) << ";" + << endl; + + member = "vt"; + } + + // If this is a wrapped composite value, then we need to "unwrap" + // it. If this is a NULL wrapper, then we also need to handle that. + // For simple values this is taken care of by the value_traits + // specializations. + // + if (mi.wrapper != 0 && comp != 0) + { + // The wrapper type, not the wrapped type. + // + string const& wt (mi.fq_type (false)); + + // If this is a NULL wrapper and the member can be NULL, then + // we need to handle the NULL value. + // + if (null (mi.m, key_prefix_) && + mi.wrapper->template get<bool> ("wrapper-null-handler")) + { + os << "if (wrapper_traits< " << wt << " >::get_null (" << + member << "))" << endl + << "composite_value_traits< " << mi.fq_type () << ", id_" << + db << " >::set_null (" << endl + << "i." << mi.var << "value, sk" << + (versioned (*comp) ? ", svm" : "") << ");" + << "else" + << "{"; + } + + os << "const" << mi.fq_type () << "& vw = " << endl + << " wrapper_traits< " + wt + " >::get_ref (" + member + ");" + << endl; + + member = "vw"; + } + + if (discriminator (mi.m)) + os << "const info_type& di (map->find (typeid (o)));" + << endl; + + if (mi.ptr != 0) + { + // When handling a pointer, mi.t is the id type of the referenced + // object. + // + semantics::type& pt (utype (mi.m, key_prefix_)); + + type = "obj_traits::id_type"; + + // Handle NULL pointers and extract the id. + // + os << "typedef object_traits< " << class_fq_name (*mi.ptr) << + " > obj_traits;"; + + if (weak_pointer (pt)) + { + os << "typedef odb::pointer_traits< " << mi.ptr_fq_type () << + " > wptr_traits;" + << "typedef odb::pointer_traits< wptr_traits::" << + "strong_pointer_type > ptr_traits;" + << endl + << "wptr_traits::strong_pointer_type sp (" << + "wptr_traits::lock (" << member << "));"; + + member = "sp"; + } + else + os << "typedef odb::pointer_traits< " << mi.ptr_fq_type () << + " > ptr_traits;" + << endl; + + os << "bool is_null (ptr_traits::null_ptr (" << member << "));" + << "if (!is_null)" + << "{" + << "const " << type << "& ptr_id (" << endl; + + if (lazy_pointer (pt)) + os << "ptr_traits::object_id< ptr_traits::element_type > (" << + member << ")"; + else + os << "obj_traits::id (ptr_traits::get_ref (" << member << "))"; + + os << ");" + << endl; + + member = "ptr_id"; + } + else if (comp != 0) + type = mi.fq_type (); + else + { + type = mi.fq_type (); + + // Indicate to the value_traits whether this column can be NULL. + // + os << "bool is_null (" << null (mi.m, key_prefix_) << ");"; + } + + if (comp != 0) + traits = "composite_value_traits< " + type + ", id_" + + db.string () + " >"; + else + { + db_type_id = member_database_type_id_->database_type_id (mi.m); + traits = db.string () + "::value_traits<\n " + + type + ",\n " + + db_type_id + " >"; + } + + return true; + } + + virtual void + post (member_info& mi) + { + semantics::class_* comp (composite (mi.t)); + + if (mi.ptr != 0) + { + os << "}" + << "else" << endl; + + if (!null (mi.m, key_prefix_)) + os << "throw null_pointer ();"; + else if (comp != 0) + os << traits << "::set_null (i." << mi.var << "value, sk" << + (versioned (*comp) ? ", svm" : "") << ");"; + else + set_null (mi); + } + + if (mi.wrapper != 0 && comp != 0) + { + if (null (mi.m, key_prefix_) && + mi.wrapper->template get<bool> ("wrapper-null-handler")) + os << "}"; + } + + os << "}"; + + if (member_override_.empty ()) + { + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + os << "}"; + } + } + + virtual void + traverse_composite (member_info& mi) + { + bool grow (generate_grow && + context::grow (mi.m, mi.t, mi.ct, key_prefix_)); + + if (grow) + os << "if ("; + + os << traits << "::init (" << endl + << "i." << mi.var << "value," << endl + << member << "," << endl + << "sk"; + + if (versioned (*composite (mi.t))) + os << "," << endl + << "svm"; + + os << ")"; + + if (grow) + os << ")" << endl + << "grew = true"; + + os << ";"; + } + + protected: + string type; + string db_type_id; + string member; + string traits; + + instance<member_database_type_id> member_database_type_id_; + }; + + struct init_image_base: traversal::class_, virtual context + { + typedef init_image_base base; + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + os << "// " << class_name (c) << " base" << endl + << "//" << endl; + + // If the derived class is readonly, then we will never be + // called with sk == statement_update. + // + bool check (readonly (c) && !readonly (*context::top_object)); + + if (check) + os << "if (sk != statement_update)" + << "{"; + + if (generate_grow) + os << "if ("; + + if (obj) + os << "object_traits_impl< "; + else + os << "composite_value_traits< "; + + os << class_fq_name (c) << ", id_" << db << " >::init (i, o, sk" << + (versioned (c) ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "grew = true"; + + os << ";"; + + if (check) + os << "}"; + else + os << endl; + } + }; + + // + // init value + // + + struct init_value_member: virtual member_base + { + typedef init_value_member base; + + init_value_member (string const& member = string (), + string const& var = string (), + bool ignore_implicit_discriminator = true, + user_section* section = 0) + : member_base (var, 0, 0, string (), string (), section), + member_override_ (member), + ignore_implicit_discriminator_ (ignore_implicit_discriminator) + { + } + + init_value_member (string const& var, + string const& member, + semantics::type& t, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base (var, &t, ct, fq_type, key_prefix), + member_override_ (member), + ignore_implicit_discriminator_ (true) + { + } + + virtual void + get_null (string const& /*var*/) const {}; + + protected: + string member_override_; + bool ignore_implicit_discriminator_; + }; + + template <typename T> + struct init_value_member_impl: init_value_member, + virtual member_base_impl<T> + { + typedef init_value_member_impl base_impl; + + init_value_member_impl (base const& x) + : base (x), + member_database_type_id_ (base::type_override_, + base::custom_override_, + base::fq_type_override_, + base::key_prefix_) + { + } + + typedef typename member_base_impl<T>::member_info member_info; + + using member_base_impl<T>::container; + + virtual void + get_null (string const& var) const = 0; + + virtual void + check_modifier (member_info&, member_access&) {} + + virtual bool + pre (member_info& mi) + { + if (container (mi)) + return false; + + if (section_ != 0 && *section_ != section (mi.m)) + return false; + + // Ignore polymorphic id references; they are initialized in a + // special way. + // + if (mi.ptr != 0 && mi.m.count ("polymorphic-ref")) + return false; + + // Ignore implicit discriminators. + // + if (ignore_implicit_discriminator_ && discriminator (mi.m)) + return false; + + semantics::class_* comp (composite (mi.t)); + + if (!member_override_.empty ()) + { + os << "{"; + member = member_override_; + } + else + { + // Ignore separately loaded members. + // + if (section_ == 0 && separate_load (mi.m)) + return false; + + os << "// " << mi.m.name () << endl + << "//" << endl; + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + // If this is a composite member, see if it is summarily + // added/deleted. + // + if (comp != 0) + { + unsigned long long cav (added (*comp)); + unsigned long long cdv (deleted (*comp)); + + if (cav != 0 && (av == 0 || av < cav)) + av = cav; + + if (cdv != 0 && (dv == 0 || dv > cdv)) + dv = cdv; + } + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")"; + } + + os << "{"; + + if (mi.ptr != 0 && view_member (mi.m)) + return true; // That's enough for the object pointer in view. + + // Set the member using the modifier expression. + // + member_access& ma (mi.m.template get<member_access> ("set")); + + // Make sure this kind of member can be modified with this + // kind of accessor (database-specific, e.g., streaming). + // + if (comp == 0) + check_modifier (mi, ma); + + // If this is not a synthesized expression, then output + // its location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // See if we are modifying via a reference or proper modifier. + // + if (ma.placeholder ()) + os << member_val_type (mi.m, false, "v") << ";" + << endl; + else + { + // Use the original type to form the reference. VC++ cannot + // grok the constructor syntax. + // + os << member_ref_type (mi.m, false, "v") << " =" << endl + << " "; + + // If this member is const and we have a synthesized direct + // access, then cast away constness. Otherwise, we assume + // that the user-provided expression handles this. + // + bool cast (mi.cq && ma.direct ()); + if (cast) + os << "const_cast< " << member_ref_type (mi.m, false) << + " > (" << endl; + + os << ma.translate ("o"); + + if (cast) + os << ")"; + + os << ";" + << endl; + } + + member = "v"; + } + + // Translate. + // + if (mi.ct != 0) + { + os << type_val_type (*mi.ct->as, mi.ct->as_hint, false, "vt") << ";" + << endl; + + translate_member = member; + member = "vt"; + } + + // If this is a wrapped composite value, then we need to "unwrap" it. + // If this is a NULL wrapper, then we also need to handle that. For + // simple values this is taken care of by the value_traits + // specializations. + // + if (mi.wrapper != 0 && comp != 0) + { + // The wrapper type, not the wrapped type. + // + string const& wt (mi.fq_type (false)); + + // If this is a NULL wrapper and the member can be NULL, then + // we need to handle the NULL value. + // + if (null (mi.m, key_prefix_) && + mi.wrapper->template get<bool> ("wrapper-null-handler")) + { + os << "if (composite_value_traits< " << mi.fq_type () << + ", id_" << db << " >::get_null (" << endl + << "i." << mi.var << "value" << + (versioned (*comp) ? ", svm" : "") << "))" << endl + << "wrapper_traits< " << wt << " >::set_null (" << member + ");" + << "else" + << "{"; + } + + os << mi.fq_type () << "& vw =" << endl + << " wrapper_traits< " + wt + " >::set_ref (" + member + ");" + << endl; + + wrap_member = member; + member = "vw"; + } + + if (mi.ptr != 0) + { + type = "obj_traits::id_type"; + + // Handle NULL pointers and extract the id. + // + os << "typedef object_traits< " << class_fq_name (*mi.ptr) << + " > obj_traits;" + << "typedef odb::pointer_traits< " << mi.ptr_fq_type () << + " > ptr_traits;" + << endl; + + os << "if ("; + + if (comp != 0) + os << "composite_value_traits< " << type << ", id_" << db << + " >::get_null (" << endl + << "i." << mi.var << "value" << + (versioned (*comp) ? ", svm" : "") << ")"; + else + get_null (mi.var); + + os << ")" << endl; + + // Don't throw null_pointer if we can't have NULLs and the pointer + // is NULL since this can be useful during migration. Instead, we + // rely on the database enforcing this. + // + os << member << " = ptr_traits::pointer_type ();"; + + os << "else" + << "{"; + + os << type << " ptr_id;"; + + member = "ptr_id"; + } + else + type = mi.fq_type (); + + if (comp != 0) + traits = "composite_value_traits< " + type + ", id_" + + db.string () + " >"; + else + { + db_type_id = member_database_type_id_->database_type_id (mi.m); + traits = db.string () + "::value_traits<\n " + + type + ",\n " + + db_type_id + " >"; + } + + return true; + } + + virtual void + post (member_info& mi) + { + if (mi.ptr != 0) + { + if (view_member (mi.m)) + { + // The object pointer in view doesn't need any of this. + os << "}"; + return; + } + + // Restore the member variable name. + // + member = member_override_.empty () ? "v" : member_override_; + + // When handling a pointer, mi.t is the id type of the referenced + // object. + // + semantics::type& pt (utype (mi.m, key_prefix_)); + + if (lazy_pointer (pt)) + os << member << " = ptr_traits::pointer_type (" << endl + << "*static_cast<" << db << "::database*> (db), ptr_id);"; + else + { + os << "// If a compiler error points to the line below, then" << endl + << "// it most likely means that a pointer used in a member" << endl + << "// cannot be initialized from an object pointer." << endl + << "//" << endl + << member << " = ptr_traits::pointer_type (" << endl + << "static_cast<" << db << "::database*> (db)->load<" << endl + << " obj_traits::object_type > (ptr_id));"; + + // If we are loading into an eager weak pointer, make sure there + // is someone else holding a strong pointer to it (normally a + // session). Otherwise, the object will be loaded and immediately + // deleted. Besides not making much sense, this also breaks the + // delayed loading machinery which expects the object to be around + // at least until the top-level load() returns. + // + if (weak_pointer (pt)) + { + os << endl + << "if (odb::pointer_traits<" << + "ptr_traits::strong_pointer_type>::null_ptr (" << endl + << "ptr_traits::lock (" << member << ")))" << endl + << "throw session_required ();"; + } + } + + os << "}"; + } + + // Wrap back (so to speak). + // + if (mi.wrapper != 0 && composite (mi.t) != 0) + { + if (null (mi.m, key_prefix_) && + mi.wrapper->template get<bool> ("wrapper-null-handler")) + os << "}"; + + member = wrap_member; + } + + // Untranslate. + // + if (mi.ct != 0) + { + //@@ Use move() in C++11? Or not. + // + os << "// From " << location_string (mi.ct->loc, true) << endl + << translate_member << " = " << + mi.ct->translate_from (member) << ";"; + + member = translate_member; + } + + // Call the modifier if we are using a proper one. + // + if (member_override_.empty ()) + { + member_access& ma (mi.m.template get<member_access> ("set")); + + if (ma.placeholder ()) + { + // If this is not a synthesized expression, then output its + // location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ( + "o", "v", "*static_cast<" + db.string () + "::database*> (db)") + << ";"; + } + } + + os << "}"; + } + + virtual void + traverse_pointer (member_info& mi) + { + // Object pointers in views require special treatment. + // + if (view_member (mi.m)) + { + // This is the middle part. The pre and post parts are generated + // by init_view_pointer_member below. + // + using semantics::class_; + + class_& c (*mi.ptr); + class_* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + + string o_tp (mi.var + "object_type"); + string o_tr (mi.var + "object_traits"); + string r_tr (poly_derived ? mi.var + "root_traits" : o_tr); + string i_tp (mi.var + "info_type"); + + string id (mi.var + "id"); + string o (mi.var + "o"); + string pi (mi.var + "pi"); // Polymorphic type info. + + // If load_() will be loading containers or the rest of the + // polymorphic object, then we need to perform several extra + // things. We need to initialize the id image in the object + // statements. We also have to lock the statements so that + // nobody messes up this id image. + // + bool init_id ( + poly || + has_a (c, test_container | include_eager_load, &main_section)); + + bool versioned (context::versioned (c)); + + os << "if (" << o << " != 0)" + << "{"; + + if (poly) + os << "callback_event ce (callback_event::pre_load);" + << pi << "->dispatch (" << i_tp << "::call_callback, " << + "*db, " << o << ", &ce);"; + else + os << o_tr << "::callback (*db, *" << o << + ", callback_event::pre_load);"; + + os << o_tr << "::init (*" << o << ", i." << mi.var << "value, db" << + (versioned ? ", svm" : "") << ");"; + + // Call load_() to load the rest of the object (containers, etc). + // + if (id_member (poly ? *poly_root : c) != 0) + { + const char* s (poly_derived ? "osts" : "sts"); + + os << o_tr << "::statements_type& " << s << " (" << endl + << "conn.statement_cache ().find_object<" << o_tp << "> ());"; + + if (poly_derived) + os << r_tr << "::statements_type& sts (osts.root_statements ());"; + + if (init_id) + { + // This can only be top-level call so lock must succeed. + // + os << r_tr << "::statements_type::auto_lock l (sts);" + << "assert (l.locked ()) /* Must be a top-level call. */;" + << endl + << r_tr << "::id_image_type& i (sts.id_image ());" + << r_tr << "::init (i, " << id << ");" + << db << "::binding& idb (sts.id_image_binding ());" + << "if (i.version != sts.id_image_version () || " << + "idb.version == 0)" + << "{" + << r_tr << "::bind (idb.bind, i);" + << "sts.id_image_version (i.version);" + << "idb.version++;"; + if (optimistic (poly ? *poly_root : c) != 0) + os << "sts.optimistic_id_image_binding ().version++;"; + os << "}"; + } + + os << o_tr << "::load_ (" << s << ", *" << o << ", false" << + (versioned ? ", svm" : "") << ");"; + + // Load the dynamic part of the object unless static and dynamic + // types are the same. + // + if (poly) + os << endl + << "if (" << pi << " != &" << o_tr << "::info)" + << "{" + << "std::size_t d (" << o_tr << "::depth);" + << pi << "->dispatch (" << i_tp << "::call_load, *db, " << + o << ", &d);" + << "}"; + + if (init_id) + os << "sts.load_delayed (" << (versioned ? "&svm" : "0") << ");" + << "l.unlock ();"; + } + + os << "}"; + } + else + member_base_impl<T>::traverse_pointer (mi); + } + + virtual void + traverse_composite (member_info& mi) + { + os << traits << "::init (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "db"; + + if (versioned (*composite (mi.t))) + os << "," << endl + << "svm"; + + os << ");" + << endl; + } + + protected: + string type; + string db_type_id; + string traits; + string member; + string translate_member; // Untranslated member. + string wrap_member; // Wrapped member. + + instance<member_database_type_id> member_database_type_id_; + }; + + // This class generates the pre and post parts. The middle part is + // generated by init_value_member above. + // + struct init_view_pointer_member: virtual member_base, + member_base_impl<bool> // Dummy SQL type. + { + typedef init_view_pointer_member base; + + init_view_pointer_member (bool pre, init_value_member const& ivm) + : member_base (0, 0, string (), string (), 0), + pre_ (pre), init_value_member_ (ivm) {} + + virtual bool + pre (member_info& mi) + { + // Only interested in object pointers inside views. + // + return mi.ptr != 0 && view_member (mi.m); + } + + virtual void + traverse_pointer (member_info& mi) + { + using semantics::class_; + + class_& c (*mi.ptr); + bool abst (abstract (c)); + class_* poly_root (polymorphic (c)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c); + size_t poly_depth (poly_derived ? polymorphic_depth (c) : 1); + + data_member_path* idm (id_member (poly ? *poly_root : c)); + + os << "// " << mi.m.name () << (pre_ ? " pre" : " post") << endl + << "//" << endl; + + string o_tp (mi.var + "object_type"); + string o_tr (mi.var + "object_traits"); + string r_tr (poly_derived ? mi.var + "root_traits" : o_tr); + string i_tp (mi.var + "info_type"); + string p_tp (mi.var + "pointer_type"); + string p_tr (mi.var + "pointer_traits"); + string c_tr (mi.var + "cache_traits"); + + string id (mi.var + "id"); // Object id. + string p (mi.var + "p"); // Object pointer. + string pg (mi.var + "pg"); // Pointer guard. + string ig (mi.var + "ig"); // Cache insert guard. + string o (mi.var + "o"); // Object. + string pi (mi.var + "pi"); // Polymorphic type info. + + bool op_raw (c.get<bool> ("object-pointer-raw")); + bool mp_raw (utype (mi.m).is_a<semantics::pointer> ()); + + // Output aliases and variables before any schema version if- + // blocks since we need to be able to access them across all + // three parts. + // + if (pre_) + { + os << "typedef " << class_fq_name (c) << " " << o_tp << ";" + << "typedef object_traits_impl<" << o_tp << ", id_" << db << + "> " << o_tr << ";"; + + if (poly_derived) + os << "typedef " << o_tr << "::root_traits " << r_tr << ";"; + + if (poly) + os << "typedef " << r_tr << "::info_type " << i_tp << ";"; + + os << "typedef " << r_tr << "::pointer_type " << p_tp << ";" + << "typedef " << r_tr << "::pointer_traits " << p_tr << ";"; + if (idm != 0) + os << "typedef " << r_tr << "::pointer_cache_traits " << + c_tr << ";"; + os << endl; + + if (idm != 0) + os << r_tr << "::id_type " << id << ";"; + os << p_tp << " " << p << (op_raw ? " = 0" : "") << ";" // VC++ + << p_tr << "::guard " << pg << ";"; + if (idm != 0) + os << c_tr << "::insert_guard " << ig << ";"; + os << o_tp << "* " << o << " (0);"; + + if (poly) + os << "const " << i_tp << "* " << pi << " = 0;"; // VC++ + + os << endl; + } + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (mi.m)); + unsigned long long dv (deleted (mi.m)); + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")"; + } + + os << "{"; + + if (pre_) + { + string id_im; + if (idm != 0) + { + // Check for NULL. + // + string id_var; + { + id_im = mi.var + "value"; + + // In a polymorphic class, the id is in the root image. + // + for (size_t i (0); i < poly_depth - 1; ++i) + id_im += (i == 0 ? ".base" : "->base"); + + string n; + for (data_member_path::const_iterator i (idm->begin ()); + i != idm->end (); + ++i) + { + // The same logic as in member_base. + // + if (!n.empty ()) + n += "value."; // Composite. + + string const& name ((*i)->name ()); + n += name; + + if (n[n.size () - 1] != '_') + n += '_'; + } + + id_var = id_im + (poly_derived ? "->" : ".") + n; + id_im = (poly_derived ? "*i." : "i.") + id_im; + } + + os << "if ("; + + if (semantics::class_* comp = composite (mi.t)) + os << "!composite_value_traits< " << o_tr << "::id_type, id_" << + db << " >::get_null (" << endl + << "i." << id_var << "value" << + (versioned (*comp) ? ", svm" : "") << ")"; + else + { + os << "!("; + init_value_member_.get_null (id_var); + os << ")"; + } + + os << ")" + << "{"; + + // Check cache. + // + os << id << " = " << r_tr << "::id (" << id_im << ");" + << p << " = " << c_tr << "::find (*db, " << id << ");" + << endl; + + os << "if (" << p_tr << "::null_ptr (" << p << "))" + << "{"; + } + + // To support by-value object loading, we are going to load + // into an existing instance if the pointer is already not + // NULL. To limit the potential misuse (especially when it + // comes to sessions), we are going to limit this support + // only to raw pointers. Furthermore, we will only insert + // such an object into the cache if its object pointer is + // also raw. + // + if (mp_raw && !poly) + { + // Get the member using the accessor expression. + // + member_access& ma (mi.m.get<member_access> ("get")); + + // If this is not a synthesized expression, then output + // its location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // Use the original type to form the const reference. VC++ + // cannot grok the constructor syntax. + // + os << member_ref_type (mi.m, true, "m") << " =" << endl + << " " << ma.translate ("o") << ";" + << endl; + + os << "if (m != 0)" + << "{"; + + if (op_raw) + os << ig << ".reset (" << c_tr << "::insert (*db, " << id << + ", m));"; + + os << o << " = m;" + << "}" + << "else" + << "{"; + } + + if (poly) + { + os << r_tr << "::discriminator_type d (" << endl + << r_tr << "::discriminator (" << id_im << "));"; + + if (abst) + os << pi << " = &" << r_tr << "::map->find (d);"; + else + os << pi << " = (d == " << o_tr << "::info.discriminator" << endl + << "? &" << o_tr << "::info" << endl + << ": &" << r_tr << "::map->find (d));"; + + os << p << " = " << pi << "->create ();"; + } + else + os << p << " = object_factory<" << o_tp << ", " << p_tp << + ">::create ();"; + + os << pg << ".reset (" << p << ");"; + if (idm != 0) + os << ig << ".reset (" << c_tr << "::insert (*db, " << id << + ", " << p << "));"; + + if (poly_derived) + os << o << " = static_cast<" << o_tp << "*> (" << p_tr << + "::get_ptr (" << p << "));"; + else + os << o << " = " << p_tr << "::get_ptr (" << p << ");"; + + if (mp_raw && !poly) + os << "}"; + + if (idm != 0) + os << "}" // Cache. + << "}"; // NULL. + } + else + { + os << "if (" << o << " != 0)" + << "{"; + + if (poly) + os << "callback_event ce (callback_event::post_load);" + << pi << "->dispatch (" << i_tp << "::call_callback, " << + "*db, " << o << ", &ce);"; + else + os << o_tr << "::callback (*db, *" << o << + ", callback_event::post_load);"; + + if (idm != 0) + { + if (mp_raw && !op_raw && !poly) + os << "if (!" << p_tr << "::null_ptr (" << p << "))" + << "{"; + + os << c_tr << "::load (" << ig << ".position ());" + << ig << ".release ();"; + + if (mp_raw && !op_raw && !poly) + os << "}"; + } + + os << pg << ".release ();"; + + os << "}"; + + // If member pointer is not raw, then result is in p. + // If both member and object are raw, then result is in o. + // If member is raw but object is not, then result is in + // p if it is not NULL, and in o (either NULL or the same + // as the member value) otherwise. + // + member_access& ma (mi.m.get<member_access> ("set")); + + if (ma.empty () && !poly) + { + // It is ok to have empty modifier expression as long as + // the member pointer is raw. This is the by-value load + // and the user is not interested in learning whether the + // object is NULL. + // + if (!mp_raw) + { + error (ma.loc) << "non-empty modifier expression required " << + "for loading an object via a smart pointer" << endl; + throw operation_failed (); + } + + os << "// Empty modifier expression was specified for this\n" + << "// object so make sure we have actually loaded the\n" + << "// data into the existing instance rather than, say,\n" + << "// finding the object in the cache or creating a new one.\n" + << "//\n" + << "assert (" << p_tr << "::null_ptr (" << p << "));"; + } + else + { + if (!(mp_raw && op_raw) || poly) + { + string r (options.std () >= cxx_version::cxx11 + ? "std::move (" + p + ")" + : p); + + if (poly_derived) + // This pointer could have come from cache, so use dynamic + // cast. + // + r = p_tr + "::dynamic_pointer_cast<" + o_tp + "> (\n" + + r + ")"; + + // Unless the pointer is raw, explicitly construct the + // smart pointer from the object pointer so that we get + // the behavior similar to calling database::load() (in + // both cases we are the "ownership end-points"; unless + // the object was already in the session before loading + // this view (in which case using raw pointers as object + // pointers is a really stupid idea), this logic will do + // the right thing and what the user most likely expects. + // + if (!mp_raw) + r = member_val_type (mi.m, false) + " (\n" + r + ")"; + + if (mp_raw && !op_raw) + os << "if (!" << p_tr << "::null_ptr (" << p << "))" << endl; + + os << "// If a compiler error points to the line below, then\n" + << "// it most likely means that a pointer used in view\n" + << "// member cannot be initialized from an object pointer.\n" + << "//" << endl; + + set_member (mi.m, "o", r, "db"); + } + + if (mp_raw && !poly) + { + if (!op_raw) + os << "else" << endl; // NULL p + + set_member (mi.m, "o", o, "db"); + } + } + } + + os << "}"; + } + + virtual bool const& + member_sql_type (semantics::data_member&) {return pre_;}; + + protected: + bool pre_; + init_value_member const& init_value_member_; + }; + + struct init_value_base: traversal::class_, virtual context + { + typedef init_value_base base; + + virtual void + traverse (type& c) + { + bool obj (object (c)); + + // Ignore transient bases. Not used for views. + // + if (!(obj || composite (c))) + return; + + os << "// " << class_name (c) << " base" << endl + << "//" << endl; + + if (obj) + os << "object_traits_impl< "; + else + os << "composite_value_traits< "; + + os << class_fq_name (c) << ", id_" << db << " >::init (o, i, db" << + (versioned (c) ? ", svm" : "") << ");" + << endl; + } + }; + + // Member-specific traits types for container members. + // + struct container_traits: object_members_base, virtual context + { + typedef container_traits base; + + container_traits (semantics::class_& c) + : object_members_base ( + true, + object (c), // Only build table prefix for objects. + false), + c_ (c) + { + scope_ = object (c) + ? "access::object_traits_impl< " + : "access::composite_value_traits< "; + + scope_ += class_fq_name (c) + ", id_" + db.string () + " >"; + } + + // Unless the database system can execute several interleaving + // statements, cache the result set. + // + virtual void + cache_result (string const& statement) + { + os << statement << ".cache ();"; + } + + // Additional code that need to be executed following the call to + // init_value. + // + virtual void + init_value_extra () + { + } + + virtual void + process_statement_columns (statement_columns&, + statement_kind, + bool /*dynamic*/) + { + } + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&) + { + // We don't want to traverse composite id. + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + if (object (c_)) + object_members_base::traverse_composite (m, c); + else + { + // If we are generating traits for a composite value type, then + // we don't want to go into its bases or it composite members. + // + if (m == 0 && &c == &c_) + names (c); + } + } + + virtual void + container_extra (semantics::data_member&, semantics::type&) + { + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& t) + { + using semantics::type; + + // Figure out if this member is from a base object or composite + // value and if it's from an object, whether it is reuse-abstract. + // + bool base, reuse_abst; + + if (object (c_)) + { + base = cur_object != &c_ || + !object (dynamic_cast<type&> (m.scope ())); + reuse_abst = abstract (c_) && !polymorphic (c_); + } + else + { + base = false; // We don't go into bases. + reuse_abst = true; // Always abstract. + } + + container_kind_type ck (container_kind (t)); + + const custom_cxx_type* vct (0); + const custom_cxx_type* ict (0); + const custom_cxx_type* kct (0); + + type& vt (container_vt (m, &vct)); + type* it (0); + type* kt (0); + + data_member_path* imp (context::inverse (m, "value")); + + bool ordered (false); + bool inverse (imp != 0); + bool grow (false); + + switch (ck) + { + case ck_ordered: + { + if (!unordered (m)) + { + it = &container_it (m, &ict); + ordered = true; + + if (generate_grow) + grow = grow || context::grow (m, *it, ict, "index"); + } + + break; + } + case ck_map: + case ck_multimap: + { + kt = &container_kt (m, &kct); + + if (generate_grow) + grow = grow || context::grow (m, *kt, kct, "key"); + + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + bool smart (!inverse && + (ck != ck_ordered || ordered) && + container_smart (t)); + + if (generate_grow) + grow = grow || context::grow (m, vt, vct, "value"); + + bool eager_ptr (is_a (member_path_, + member_scope_, + test_eager_pointer, + vt, + "value")); + if (!eager_ptr) + { + if (semantics::class_* cvt = composite_wrapper (vt)) + eager_ptr = has_a (*cvt, test_eager_pointer); + } + + bool versioned (context::versioned (m)); + + string name (flat_prefix_ + public_name (m) + "_traits"); + string scope (scope_ + "::" + name); + + os << "// " << m.name () << endl + << "//" << endl + << endl; + + container_extra (m, t); + + // + // Statements. + // + if (!reuse_abst) + { + string sep (versioned ? "\n" : " "); + + semantics::type& idt (container_idt (m)); + + qname table (table_name (m, table_prefix_)); + string qtable (quote_id (table)); + instance<object_columns_list> id_cols; + instance<object_columns_list> ik_cols; // index/key columns + + if (smart) + { + switch (ck) + { + case ck_ordered: + { + ik_cols->traverse (m, *it, "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + } + + // select_statement + // + os << "const char " << scope << "::" << endl + << "select_statement[] =" << endl; + + if (inverse) + { + semantics::class_* c (object_pointer (vt)); + semantics::data_member& imf (*imp->front ()); + semantics::data_member& imb (*imp->back ()); + + // In a polymorphic hierarchy the inverse member can be in + // the base class, in which case we should use that class + // for the table name, etc. + // + if (polymorphic (*c)) + c = &dynamic_cast<semantics::class_&> (imf.scope ()); + + data_member_path& inv_id (*id_member (*c)); + + qname inv_table; // Other table name. + string inv_qtable; + instance<object_columns_list> inv_id_cols; // Other id column. + instance<object_columns_list> inv_fid_cols; // Other foreign id + // column (ref to us). + statement_columns sc; + + if (container (imb)) + { + // many(i)-to-many + // + + // This other container is a direct member of the class so the + // table prefix is just the class table name. + // + inv_table = table_name (*c, *imp); + inv_qtable = quote_id (inv_table); + + inv_id_cols->traverse (imb, utype (inv_id), "id", "object_id", c); + inv_fid_cols->traverse (imb, idt, "value", "value"); + + for (object_columns_list::iterator i (inv_id_cols->begin ()); + i != inv_id_cols->end (); ++i) + { + // If this is a simple id, then pass the "id" key prefix. If + // it is a composite id, then the members have no prefix. + // + sc.push_back ( + statement_column ( + inv_qtable, + convert_from (inv_qtable + "." + quote_id (i->name), + i->type, + *i->member), + i->type, + *i->member, + inv_id_cols->size () == 1 ? "id" : "")); + } + } + else + { + // many(i)-to-one + // + inv_table = table_name (*c); + inv_qtable = quote_id (inv_table); + + inv_id_cols->traverse (inv_id); + inv_fid_cols->traverse (imb, column_prefix (*imp)); + + for (object_columns_list::iterator i (inv_id_cols->begin ()); + i != inv_id_cols->end (); ++i) + { + sc.push_back ( + statement_column ( + inv_qtable, + convert_from (inv_qtable + "." + quote_id (i->name), + i->type, + *i->member), + i->type, + *i->member)); + } + } + + process_statement_columns (sc, statement_select, versioned); + + os << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + instance<query_parameters> qp (statement_select, inv_table); + os << strlit ("FROM " + inv_qtable + sep) << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (inv_fid_cols->begin ()), + i (b); i != inv_fid_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += inv_qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + os << strlit (where); + } + else + { + id_cols->traverse (m, idt, "id", "object_id"); + + statement_columns sc; + statement_kind sk (statement_select); // Imperfect forwarding. + instance<object_columns> t (qtable, sk, sc); + + switch (ck) + { + case ck_ordered: + { + if (ordered) + t->traverse (m, *it, "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + t->traverse (m, *kt, "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + t->traverse (m, vt, "value", "value"); + + process_statement_columns (sc, statement_select, versioned); + + os << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + instance<query_parameters> qp (statement_select, table); + os << strlit ("FROM " + qtable + sep) << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + if (ordered) + { + // Top-level column. + // + string const& col ( + column_qname (m, "index", "index", column_prefix ())); + + where += " ORDER BY " + qtable + "." + col; + } + + os << strlit (where); + } + + os << ";" + << endl; + + // insert_statement + // + os << "const char " << scope << "::" << endl + << "insert_statement[] =" << endl; + + if (inverse) + os << strlit ("") << ";" + << endl; + else + { + statement_columns sc; + statement_kind sk (statement_insert); // Imperfect forwarding. + instance<object_columns> t (sk, sc); + + t->traverse (m, idt, "id", "object_id"); + + switch (ck) + { + case ck_ordered: + { + if (ordered) + t->traverse (m, *it, "index", "index"); + break; + } + case ck_map: + case ck_multimap: + { + t->traverse (m, *kt, "key", "key"); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + t->traverse (m, vt, "value", "value"); + + process_statement_columns (sc, statement_insert, versioned); + + os << strlit ("INSERT INTO " + qtable + sep) << endl; + + for (statement_columns::const_iterator b (sc.begin ()), i (b), + e (sc.end ()); i != e;) + { + string s; + + if (i == b) + s += '('; + s += i->column; + s += (++i != e ? ',' : ')'); + s += sep; + + os << strlit (s) << endl; + } + + os << strlit ("VALUES" + sep) << endl; + + string values ("("); + instance<query_parameters> qp (statement_insert, table); + for (statement_columns::const_iterator b (sc.begin ()), i (b), + e (sc.end ()); i != e; ++i) + { + if (i != b) + { + values += ','; + values += sep; + } + + values += convert_to (qp->next (*i), i->type, *i->member); + } + values += ')'; + + os << strlit (values) << ";" + << endl; + } + + // update_statement + // + if (smart) + { + os << "const char " << scope << "::" << endl + << "update_statement[] =" << endl + << strlit ("UPDATE " + qtable + sep) << endl + << strlit ("SET" + sep) << endl; + + instance<query_parameters> qp (statement_update, table); + statement_columns sc; + { + bool f (false); // Imperfect forwarding. + query_parameters* p (qp.get ()); // Imperfect forwarding. + statement_kind sk (statement_update); // Imperfect forwarding. + instance<object_columns> t (sk, f, sc, p); + t->traverse (m, vt, "value", "value"); + process_statement_columns (sc, statement_update, versioned); + } + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + for (object_columns_list::iterator b (ik_cols->begin ()), i (b); + i != ik_cols->end (); ++i) + { + where += " AND " + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where) << ";" + << endl; + } + + // delete_statement + // + os << "const char " << scope << "::" << endl + << "delete_statement[] =" << endl; + + if (inverse) + os << strlit ("") << ";" + << endl; + else + { + instance<query_parameters> qp (statement_delete, table); + + os << strlit ("DELETE FROM " + qtable + " ") << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + if (smart) + { + for (object_columns_list::iterator b (ik_cols->begin ()), i (b); + i != ik_cols->end (); ++i) + { + where += " AND " + quote_id (i->name) + + (ck == ck_ordered ? ">=" : "=") + + convert_to (qp->next (*i), i->type, *i->member); + } + } + + os << strlit (where) << ";" + << endl; + } + } + + if (base) + return; + + // + // Functions. + // + + // bind (cond_image_type) + // + if (smart) + { + os << "void " << scope << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "cond_image_type& c)" + << "{" + << "using namespace " << db << ";" + << endl + << "statement_kind sk (statement_select);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl + << "std::size_t n (0);" + << endl; + + os << "// object_id" << endl + << "//" << endl + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << endl; + + // We don't need to update the bind index since this is the + // last element. + // + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<bind_member> bm ( + "index_", "c", *it, ict, "index_type", "index"); + bm->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<bind_member> bm ( + "key_", "c", *kt, kct, "key_type", "key"); + bm->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + os << "// value" << endl + << "//" << endl; + instance<bind_member> bm ( + "value_", "c", vt, vct, "value_type", "value"); + bm->traverse (m); + break; + } + } + os << "}"; + } + + // bind (data_image_type) + // + { + os << "void " << scope << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "data_image_type& d"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + // In the case of containers, insert and select column sets are + // the same since we can't have inverse members as container + // elements. + // + << "statement_kind sk (statement_select);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl + << "size_t n (0);" + << endl; + + os << "// object_id" << endl + << "//" << endl + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<bind_member> bm ( + "index_", "d", *it, ict, "index_type", "index"); + bm->traverse (m); + os << "n++;" // Simple value. + << endl; + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<bind_member> bm ( + "key_", "d", *kt, kct, "key_type", "key"); + bm->traverse (m); + + if (semantics::class_* c = composite_wrapper (*kt)) + os << "n += " << column_count (*c).total << "UL;" + << endl; + else + os << "n++;" + << endl; + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + // We don't need to update the bind index since this is the + // last element. + // + os << "// value" << endl + << "//" << endl; + instance<bind_member> bm ( + "value_", "d", vt, vct, "value_type", "value"); + bm->traverse (m); + + os << "}"; + } + + // bind (cond_image, data_image) (update) + // + if (smart) + { + os << "void " << scope << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "const " << bind_vector << " id," << endl + << "std::size_t id_size," << endl + << "cond_image_type& c," << endl + << "data_image_type& d"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + // Use insert instead of update to include read-only members. + // + << "statement_kind sk (statement_insert);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl + << "std::size_t n (0);" + << endl; + + os << "// value" << endl + << "//" << endl; + instance<bind_member> bm ( + "value_", "d", vt, vct, "value_type", "value"); + bm->traverse (m); + + if (semantics::class_* c = composite_wrapper (vt)) + os << "n += " << column_count (*c).total << "UL;" + << endl; + else + os << "n++;" + << endl; + + os << "// object_id" << endl + << "//" << endl + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << endl; + + // We don't need to update the bind index since this is the + // last element. + // + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<bind_member> bm ( + "index_", "c", *it, ict, "index_type", "index"); + bm->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<bind_member> bm ( + "key_", "c", *kt, kct, "key_type", "key"); + bm->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + os << "// value" << endl + << "//" << endl; + instance<bind_member> bm ( + "value_", "c", vt, vct, "value_type", "value"); + bm->traverse (m); + break; + } + } + os << "}"; + } + + // grow () + // + if (generate_grow) + { + size_t index (0); + + os << "void " << scope << "::" << endl + << "grow (data_image_type& i," << endl + << truncated_vector << " t"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "bool grew (false);" + << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + instance<grow_member> gm ( + index, "index_", *it, ict, "index_type", "index"); + gm->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + instance<grow_member> gm ( + index, "key_", *kt, kct, "key_type", "key"); + gm->traverse (m); + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "// value" << endl + << "//" << endl; + instance<grow_member> gm ( + index, "value_", vt, vct, "value_type", "value"); + gm->traverse (m); + + os << "if (grew)" << endl + << "i.version++;" + << "}"; + } + + // init (data_image) + // + if (!inverse) + { + os << "void " << scope << "::" << endl + << "init (data_image_type& i," << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "index_type* j," << endl; + break; + } + case ck_map: + case ck_multimap: + { + os << "const key_type* k," << endl; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "const value_type& v"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + << "statement_kind sk (statement_insert);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl; + + if (generate_grow) + os << "bool grew (false);" + << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl + << "if (j != 0)"; + + instance<init_image_member> im ( + "index_", "*j", *it, ict, "index_type", "index"); + im->traverse (m); + } + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl + << "if (k != 0)"; + + instance<init_image_member> im ( + "key_", "*k", *kt, kct, "key_type", "key"); + im->traverse (m); + + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "// value" << endl + << "//" << endl; + { + instance<init_image_member> im ( + "value_", "v", vt, vct, "value_type", "value"); + im->traverse (m); + } + + if (generate_grow) + os << "if (grew)" << endl + << "i.version++;"; + + os << "}"; + } + + // init (cond_image) + // + if (smart) + { + os << "void " << scope << "::" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "init (cond_image_type& i, index_type j)" + << "{" + << "using namespace " << db << ";" + << endl + << "statement_kind sk (statement_select);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl; + + instance<init_image_member> im ( + "index_", "j", *it, ict, "index_type", "index"); + im->traverse (m); + + os << "}"; + break; + } + case ck_map: + case ck_multimap: + { + // Need to handle growth. + // + // os << "init (data_image_type&, const key_type&);"; + break; + } + case ck_set: + case ck_multiset: + { + // Need to handle growth. + // + // os << "init (data_image_type&, const value_type&);"; + break; + } + } + + os << endl; + } + + // init (data) + // + os << "void " << scope << "::" << endl + << "init ("; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "index_type& j," << endl; + break; + } + case ck_map: + case ck_multimap: + { + os << "key_type& k," << endl; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "value_type& v," << endl; + os << "const data_image_type& i," << endl + << "database* db"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);" + << endl; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + { + os << "// index" << endl + << "//" << endl; + + instance<init_value_member> im ( + "index_", "j", *it, ict, "index_type", "index"); + im->traverse (m); + } + + break; + } + case ck_map: + case ck_multimap: + { + os << "// key" << endl + << "//" << endl; + + instance<init_value_member> im ( + "key_", "k", *kt, kct, "key_type", "key"); + im->traverse (m); + + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "// value" << endl + << "//" << endl; + { + // If the value is an object pointer, pass the id type as a + // type override. + // + instance<init_value_member> im ( + "value_", "v", vt, vct, "value_type", "value"); + im->traverse (m); + } + os << "}"; + + // insert + // + { + string ia, ka, va, da; + + if (!inverse) + { + ia = ordered ? " i" : ""; + ka = " k"; + va = " v"; + da = " d"; + } + + os << "void " << scope << "::" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "insert (index_type" << ia << ", " << + "const value_type&" << va << ", " << + "void*" << da << ")"; + break; + } + case ck_map: + case ck_multimap: + { + os << "insert (const key_type&" << ka << ", " << + "const value_type&" << va << ", " << + "void*" << da << ")"; + break; + } + case ck_set: + case ck_multiset: + { + os << "insert (const value_type&" << va << ", " << + "void*" << da << ")"; + break; + } + } + + os << "{"; + + if (!inverse) + { + os << "using namespace " << db << ";" + << endl + << "statements_type& sts (*static_cast< statements_type* > (d));" + << "data_image_type& di (sts.data_image ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration ());"; + + os << endl + << "init (di, "; + + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "&i, "; + break; + } + case ck_map: + case ck_multimap: + { + os << "&k, "; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "v" << (versioned ? ", svm" : "") << ");"; + + os << endl + << "if (sts.data_binding_test_version ())" + << "{" + << "const binding& id (sts.id_binding ());" + << "bind (sts.data_bind (), id.bind, id.count, di" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "}" + << "if (!sts.insert_statement ().execute ())" << endl + << "throw object_already_persistent ();"; + } + + os << "}"; + } + + // update + // + if (smart) + { + os << "void " << scope << "::" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "update (index_type i, const value_type& v, void* d)"; + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << "{"; + + os << "using namespace " << db << ";" + << endl + << "statements_type& sts (*static_cast< statements_type* > (d));" + << "cond_image_type& ci (sts.cond_image ());" + << "data_image_type& di (sts.data_image ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration ());"; + + os << endl; + + switch (ck) + { + case ck_ordered: + { + os << "init (ci, i);"; + os << "init (di, 0, v" << (versioned ? ", svm" : "") << ");"; + break; + } + case ck_map: + case ck_multimap: + { + //os << "init (di, 0, v);"; + break; + } + case ck_set: + case ck_multiset: + { + //os << "init (di, v);"; + break; + } + } + + os << endl + << "if (sts.update_binding_test_version ())" + << "{" + << "const binding& id (sts.id_binding ());" + << "bind (sts.update_bind (), id.bind, id.count, ci, di" << + (versioned ? ", svm" : "") << ");" + << "sts.update_binding_update_version ();" + << "}"; + + os << "if (sts.update_statement ().execute () == 0)" << endl + << "throw object_not_persistent ();" + << "}"; + } + + // select + // + os << "bool " << scope << "::" << endl; + + switch (ck) + { + case ck_ordered: + { + os << "select (index_type&" << (ordered ? " i" : "") << + ", value_type& v, void* d)"; + break; + } + case ck_map: + case ck_multimap: + { + os << "select (key_type& k, value_type& v, void* d)"; + break; + } + case ck_set: + case ck_multiset: + { + os << "select (value_type& v, void* d)"; + break; + } + } + + os << "{" + << "using namespace " << db << ";" + << "using " << db << "::select_statement;" // Conflicts. + << endl + << "statements_type& sts (*static_cast< statements_type* > (d));" + << "data_image_type& di (sts.data_image ());"; + + if (versioned) + os << "const schema_version_migration& svm (" << + "sts.version_migration ());"; + + os << endl + << "init ("; + + // Extract current element. + // + switch (ck) + { + case ck_ordered: + { + if (ordered) + os << "i, "; + break; + } + case ck_map: + case ck_multimap: + { + os << "k, "; + break; + } + case ck_set: + case ck_multiset: + break; + } + + os << "v, di, &sts.connection ().database ()" << + (versioned ? ", svm" : "") << ");" + << endl; + + init_value_extra (); + + // If we are loading an eager pointer, then the call to init + // above executes other statements which potentially could + // change the image, including the id. + // + if (eager_ptr) + { + os << "if (sts.data_binding_test_version ())" + << "{" + << "const binding& id (sts.id_binding ());" + << "bind (sts.data_bind (), id.bind, id.count, di" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "}"; + } + + // Fetch next. + // + os << "select_statement& st (sts.select_statement ());" + << "select_statement::result r (st.fetch ());"; + + if (grow) + os << endl + << "if (r == select_statement::truncated)" + << "{" + << "grow (di, sts.select_image_truncated ()" << + (versioned ? ", svm" : "") << ");" + << endl + << "if (sts.data_binding_test_version ())" + << "{" + // Id cannot change. + // + << "bind (sts.data_bind (), 0, sts.id_binding ().count, di" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "st.refetch ();" + << "}" + << "}"; + + os << "return r != select_statement::no_data;" + << "}"; + + // delete_ + // + os << "void " << scope << "::" << endl + << "delete_ ("; + + if (smart) + { + switch (ck) + { + case ck_ordered: + { + os << "index_type i, "; + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + } + + os << "void*" << (inverse ? "" : " d") << ")" + << "{"; + + if (!inverse) + { + os << "using namespace " << db << ";" + << endl + << "statements_type& sts (*static_cast< statements_type* > (d));"; + + if (smart) + { + os << "cond_image_type& ci (sts.cond_image ());" + << endl; + + switch (ck) + { + case ck_ordered: + { + os << "init (ci, i);"; + break; + } + case ck_map: + case ck_multimap: + { + break; + } + case ck_set: + case ck_multiset: + { + break; + } + } + + os << endl + << "if (sts.cond_binding_test_version ())" + << "{" + << "const binding& id (sts.id_binding ());" + << "bind (sts.cond_bind (), id.bind, id.count, ci);" + << "sts.cond_binding_update_version ();" + << "}"; + } + + os << "sts.delete_statement ().execute ();"; + } + + os << "}"; + + // persist + // + if (!inverse) + { + os << "void " << scope << "::" << endl + << "persist (const container_type& c," << endl + << "statements_type& sts"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + << "functions_type& fs (sts.functions ());"; + + if (versioned) + os << "sts.version_migration (svm);"; + + if (!smart && ck == ck_ordered) + os << "fs.ordered_ = " << ordered << ";"; + + os << "container_traits_type::persist (c, fs);" + << "}"; + } + + // load + // + os << "void " << scope << "::" << endl + << "load (container_type& c," << endl + << "statements_type& sts"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << "using " << db << "::select_statement;" // Conflicts. + << endl + << "const binding& id (sts.id_binding ());" + << endl + << "if (sts.data_binding_test_version ())" + << "{" + << "bind (sts.data_bind (), id.bind, id.count, sts.data_image ()" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "}" + // We use the id binding directly so no need to check cond binding. + // + << "select_statement& st (sts.select_statement ());" + << "st.execute ();" + << "auto_result ar (st);"; + + // If we are loading eager object pointers, we may need to cache + // the result since we will be loading other objects. + // + if (eager_ptr) + cache_result ("st"); + + os << "select_statement::result r (st.fetch ());"; + + if (grow) + os << endl + << "if (r == select_statement::truncated)" + << "{" + << "data_image_type& di (sts.data_image ());" + << "grow (di, sts.select_image_truncated ()" << + (versioned ? ", svm" : "") << ");" + << endl + << "if (sts.data_binding_test_version ())" + << "{" + // Id cannot change. + // + << "bind (sts.data_bind (), 0, id.count, di" << + (versioned ? ", svm" : "") << ");" + << "sts.data_binding_update_version ();" + << "st.refetch ();" + << "}" + << "}"; + + os << "bool more (r != select_statement::no_data);" + << endl + << "functions_type& fs (sts.functions ());"; + + if (versioned) + os << "sts.version_migration (svm);"; + + if (!smart && ck == ck_ordered) + os << "fs.ordered_ = " << ordered << ";"; + + os << "container_traits_type::load (c, more, fs);" + << "}"; + + // update + // + if (!(inverse || readonly (member_path_, member_scope_))) + { + os << "void " << scope << "::" << endl + << "update (const container_type& c," << endl + << "statements_type& sts"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "using namespace " << db << ";" + << endl + << "functions_type& fs (sts.functions ());"; + + if (versioned) + os << "sts.version_migration (svm);"; + + if (!smart && ck == ck_ordered) + os << "fs.ordered_ = " << ordered << ";"; + + os << "container_traits_type::update (c, fs);" + << "}"; + } + + // erase + // + if (!inverse) + { + os << "void " << scope << "::" << endl + << "erase ("; + + if (smart) + os << "const container_type* c, "; + + os << "statements_type& sts)" + << "{" + << "using namespace " << db << ";" + << endl + << "functions_type& fs (sts.functions ());"; + + if (!smart && ck == ck_ordered) + os << "fs.ordered_ = " << ordered << ";"; + + os << "container_traits_type::erase (" << (smart ? "c, " : "") << "fs);" + << "}"; + } + } + + protected: + string scope_; + semantics::class_& c_; + }; + + // Extra statement cache members for containers. + // + struct container_cache_members: object_members_base, virtual context + { + typedef container_cache_members base; + + container_cache_members () + : object_members_base (true, false, false) + { + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& c) + { + bool smart (!context::inverse (m, "value") && + !unordered (m) && + container_smart (c)); + + string traits (flat_prefix_ + public_name (m) + "_traits"); + + os << db << "::" << (smart ? "smart_" : "") << + "container_statements_impl< " << traits << " > " << + flat_prefix_ << m.name () << ";"; + } + }; + + struct container_cache_init_members: object_members_base, virtual context + { + typedef container_cache_init_members base; + + container_cache_init_members () + : object_members_base (true, false, false), first_ (true) + { + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + if (first_) + { + os << endl + << ": "; + first_ = false; + } + else + os << "," << endl + << " "; + + os << flat_prefix_ << m.name () << " (c, id"; + extra_members (); + os << ")"; + } + + virtual void + extra_members () {} + + protected: + bool first_; + }; + + // Extra statement cache members for sections. + // + struct section_cache_members: virtual context + { + typedef section_cache_members base; + + virtual void + traverse (user_section& s) + { + string traits (public_name (*s.member) + "_traits"); + + os << db << "::" << "section_statements< " << + class_fq_name (*s.object) << ", " << traits << " > " << + s.member->name () << ";"; + } + }; + + struct section_cache_init_members: virtual context + { + typedef section_cache_init_members base; + + section_cache_init_members (bool first): first_ (first) {} + + virtual void + traverse (user_section& s) + { + if (first_) + { + os << endl + << ": "; + first_ = false; + } + else + os << "," << endl + << " "; + + os << s.member->name () << " (c, im, idim, id, idv"; + extra_members (); + os << ")"; + } + + virtual void + extra_members () {} + + protected: + bool first_; + }; + + // Calls for container members. + // + struct container_calls: object_members_base, virtual context + { + typedef container_calls base; + + enum call_type + { + persist_call, + load_call, + update_call, + erase_obj_call, + erase_id_call, + section_call + }; + + container_calls (call_type call, object_section* section = 0) + : object_members_base (true, false, true, false, section), + call_ (call), + obj_prefix_ ("obj"), + by_value_ (0) + { + } + + virtual bool + section_test (data_member_path const& mp) + { + object_section& s (section (mp)); + + // Include eager loaded members into the main section for + // load calls. + // + return section_ == 0 || + *section_ == s || + (call_ == load_call && + *section_ == main_section && + !s.separate_load ()); + } + + virtual void + traverse_pointer (semantics::data_member&, semantics::class_&) + { + // We don't want to traverse composite id. + } + + virtual void + traverse_composite_wrapper (semantics::data_member* m, + semantics::class_& c, + semantics::type* w) + { + if (m == 0 || + call_ == erase_id_call || + (call_ == load_call && by_value_ != 0)) + { + object_members_base::traverse_composite (m, c); + return; + } + + // Get this member using the accessor expression. + // + member_access& ma ( + m->get<member_access> (call_ == load_call ? "set" : "get")); + + // We don't support by-value modifiers for composite values + // with containers. However, at this point we don't know + // whether this composite value has any containers. So we + // are just going to set a flag that can be checked in + // traverse_container() below. + // + if (call_ == load_call && ma.placeholder ()) + { + by_value_ = &ma; + object_members_base::traverse_composite (m, c); + by_value_ = 0; + return; + } + + // We also don't support by-value accessors is there is a + // smart container inside (which, again, we don't know at + // this point). So keep track of such first instance. + // + member_access* old_by_value (by_value_); + if (call_ != load_call && ma.by_value && by_value_ == 0) + by_value_ = &ma; + + string old_op (obj_prefix_); + string old_f (from_); + obj_prefix_.clear (); + + // If this member is const and we have a synthesized direct + // access, then cast away constness. Otherwise, we assume + // that the user-provided expression handles this. + // + bool cast (call_ == load_call && ma.direct () && const_member (*m)); + if (cast) + obj_prefix_ = "const_cast< " + member_ref_type (*m, false) + + " > (\n"; + + obj_prefix_ += ma.translate (old_op); + + if (cast) + obj_prefix_ += ")"; + + // If this is not a synthesized expression, then store its + // location which we will output later for easier error + // tracking. + // + if (!ma.synthesized) + from_ += "// From " + location_string (ma.loc, true) + "\n"; + + // If this is a wrapped composite value, then we need to "unwrap" it. + // + if (w != 0) + { + semantics::names* hint; + semantics::type& t (utype (*m, hint)); + + // Because we cannot have nested containers, member type should + // be the same as w. + // + assert (&t == w); + + obj_prefix_ = "wrapper_traits< " + t.fq_name (hint) + " >::" + + (call_ == load_call ? "set_ref" : "get_ref") + + " (\n" + obj_prefix_ + ")"; + } + + object_members_base::traverse_composite (m, c); + from_ = old_f; + obj_prefix_ = old_op; + by_value_ = old_by_value; + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& c) + { + using semantics::type; + + bool inverse (context::inverse (m, "value")); + bool smart (!inverse && !unordered (m) && container_smart (c)); + bool versioned (context::versioned (m)); + + // In certain cases we don't need to do anything. + // + if ((call_ != load_call && inverse) || + (call_ == section_call && !smart) || + (call_ == update_call && readonly (member_path_, member_scope_))) + return; + + string const& name (m.name ()); + string sts_name (flat_prefix_ + name); + string traits (flat_prefix_ + public_name (m) + "_traits"); + + os << "// " << member_prefix_ << m.name () << endl + << "//" << endl; + + // Get this member using the accessor expression. + // + string var; + member_access& ma ( + m.get<member_access> (call_ == load_call ? "set" : "get")); + + // We don't support by-value modifiers for composite values + // with containers. + // + if (call_ == load_call && by_value_ != 0) + { + error (by_value_->loc) << "by-value modification of a composite " + << "value with container is not supported" + << endl; + info (m.location ()) << "container member is defined here" << endl; + throw operation_failed (); + } + + // We don't support by-value accessors for smart containers. + // + if (call_ != load_call && smart) + { + if (by_value_ != 0) + { + error (by_value_->loc) << "by-value access to a composite value " + << "with smart container is not supported" + << endl; + info (m.location ()) << "container member is defined here" << endl; + throw operation_failed (); + } + + if (ma.by_value) + { + error (ma.loc) << "by-value access to a smart container is not " + << "supported" << endl; + info (m.location ()) << "container member is defined here" << endl; + throw operation_failed (); + } + } + + // If the member is soft- added or deleted, check the version. + // + unsigned long long av (added (member_path_)); + unsigned long long dv (deleted (member_path_)); + + // If the addition/deletion version is the same as the section's, + // then we don't need the test. + // + if (user_section* s = dynamic_cast<user_section*> (section_)) + { + if (av == added (*s->member)) + av = 0; + + if (dv == deleted (*s->member)) + dv = 0; + } + + if (av != 0 || dv != 0) + { + os << "if ("; + + if (av != 0) + os << "svm >= schema_version_migration (" << av << "ULL, true)"; + + if (av != 0 && dv != 0) + os << " &&" << endl; + + if (dv != 0) + os << "svm <= schema_version_migration (" << dv << "ULL, true)"; + + os << ")" << endl; + } + + os << "{"; + + if (call_ != erase_id_call && (call_ != erase_obj_call || smart)) + { + // See if we are modifying via a reference or proper modifier. + // + if (call_ == load_call && ma.placeholder ()) + os << member_val_type (m, false, "v") << ";" + << endl; + else + { + // Note: this case is for both access and modification. + // + + // Output stored locations, if any. + // + os << from_; + + // If this is not a synthesized expression, then output its + // location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + // Note that here we don't decay arrays. + // + const string& ref_type ( + member_ref_type (m, call_ != load_call, "v", false /* decay */)); + + // VC++ cannot grok the constructor syntax. + // + os << ref_type << " =" << endl + << " "; + + // If this member is const and we have a synthesized direct + // access, then cast away constness. Otherwise, we assume + // that the user-provided expression handles this. + // + bool cast (call_ == load_call && ma.direct () && const_member (m)); + if (cast) + os << "const_cast< " << member_ref_type (m, false, "", false) << + " > (" << endl; + + os << ma.translate (obj_prefix_); + + if (cast) + os << ")"; + + os << ";" + << endl; + } + + var = "v"; + + semantics::names* hint; + semantics::type& t (utype (m, hint)); + + // If this is a wrapped container, then we need to "unwrap" it. + // + if (wrapper (t)) + { + var = "wrapper_traits< " + t.fq_name (hint) + " >::" + + (call_ == load_call ? "set_ref" : "get_ref") + " (" + var + ")"; + } + } + + switch (call_) + { + case persist_call: + { + os << traits << "::persist (" << endl + << var << "," << endl + << "esc." << sts_name; + + if (versioned) + os << "," << endl + << "svm"; + + os << ");"; + break; + } + case load_call: + { + os << traits << "::load (" << endl + << var << "," << endl + << "esc." << sts_name; + + if (versioned) + os << "," << endl + << "svm"; + + os << ");"; + break; + } + case update_call: + { + os << traits << "::update (" << endl + << var << "," << endl + << "esc." << sts_name; + + if (versioned) + os << "," << endl + << "svm"; + + os << ");"; + break; + } + case erase_obj_call: + { + os << traits << "::erase (" << endl; + + if (smart) + os << "&" << var << "," << endl; + + os << "esc." << sts_name << ");" + << endl; + break; + } + case erase_id_call: + { + os << traits << "::erase (" << endl; + + if (smart) + os << "0," << endl; + + os << "esc." << sts_name << ");" + << endl; + break; + } + case section_call: + { + os << "if (" << traits << "::container_traits_type::changed (" << + var << "))" << endl + << "s.reset (true, true);"; // loaded, changed + break; + } + } + + if (call_ == load_call) + { + // Call the modifier if we are using a proper one. + // + if (ma.placeholder ()) + { + os << endl + << from_; + + // If this is not a synthesized expression, then output its + // location for easier error tracking. + // + if (!ma.synthesized) + os << "// From " << location_string (ma.loc, true) << endl; + + os << ma.translate ( + obj_prefix_, "v", "static_cast<" + db.string () + + "::database&> (db)") << ";"; + } + } + + os << "}"; + } + + protected: + call_type call_; + string obj_prefix_; + string from_; + member_access* by_value_; + }; + + // + // + struct section_traits: traversal::class_, virtual context + { + typedef section_traits base; + + section_traits (semantics::class_& c) + : c_ (c), + scope_ ("access::object_traits_impl< " + class_fq_name (c) + + ", id_" + db.string () + " >") + { + } + + // Additional code that need to be executed following the call to + // init_value(). + // + virtual void + init_value_extra () + { + } + + virtual void + process_statement_columns (statement_columns&, + statement_kind, + bool /*dynamic*/) + { + } + + virtual void + section_extra (user_section&) + { + } + + // Returning "1" means increment by one. + // + virtual string + optimistic_version_increment (semantics::data_member&) + { + return "1"; + } + + virtual string + update_statement_extra (user_section&) + { + return ""; + } + + virtual void + traverse (user_section& s) + { + using semantics::class_; + using semantics::data_member; + + data_member& m (*s.member); + + class_* poly_root (polymorphic (c_)); + bool poly (poly_root != 0); + bool poly_derived (poly && poly_root != &c_); + class_* poly_base (poly_derived ? &polymorphic_base (c_) : 0); + + data_member* opt (optimistic (c_)); + + // Treat the special version update sections as abstract in reuse + // inheritance. + // + bool reuse_abst (!poly && + (abstract (c_) || + s.special == user_section::special_version)); + + bool load (s.total != 0 && s.separate_load ()); + bool load_con (s.containers && s.separate_load ()); + bool load_opt (s.optimistic () && s.separate_load ()); + + bool update (s.total != s.inverse + s.readonly); // Always separate. + bool update_con (s.readwrite_containers); + bool update_opt (s.optimistic () && (s.readwrite_containers || poly)); + + // Don't generate anything for empty sections. + // + if (!(load || load_con || load_opt || + update || update_con || update_opt)) + return; + + // If we are adding a new section to a derived class in an optimistic + // polymorphic hierarchy, then pretend it inherits from the special + // version update section. + // + user_section* rs (0); + if (opt != 0) + { + // Skip overrides and get to the new section if polymorphic. + // + for (rs = &s; poly && rs->base != 0; rs = rs->base) ; + + if (rs != 0) + { + if (rs->object != &opt->scope ()) + rs->base = &(poly ? poly_root : &opt->scope ())-> + get<user_sections> ("user-sections").back (); + else + rs = 0; + } + } + + string name (public_name (m) + "_traits"); + string scope (scope_ + "::" + name); + + os << "// " << m.name () << endl + << "//" << endl + << endl; + + // bind (id, image_type) + // + if (load || load_opt || update || update_opt) + { + os << "std::size_t " << scope << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "const " << bind_vector << (reuse_abst ? "," : " id,") << endl + << "std::size_t" << (reuse_abst ? "," : " id_size,") << endl + << "image_type& i," << endl + << db << "::statement_kind sk"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (s.versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl + << "std::size_t n (0);" + << endl; + + // Bind reuse base. It is always first and we never ask it + // to bind id(+ver). + // + if (s.base != 0 && !poly_derived) + { + user_section& b (*s.base); + + bool load (b.total != 0 && b.separate_load ()); + bool load_opt (b.optimistic () && b.separate_load ()); + + bool update (b.total != b.inverse + b.readonly); + + if (load || load_opt || update) + os << "// " << class_name (*b.object) << endl + << "//" << endl + << "n += object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::bind (" << endl + << "b, 0, 0, i, sk" << (b.versioned ? ", svm" : "") << ");" + << endl; + } + + // Bind members. + // + { + instance<bind_member> bm ("", "", &s); + traversal::names n (*bm); + names (c_, n); + } + + // Bind polymorphic image chain for the select statement. + // + if (s.base != 0 && poly_derived && s.separate_load ()) + { + // Find the next base that has something to load, if any. + // + user_section* b (s.base); + string acc (".base"); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != 0 || b->optimistic ()) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + if (b != 0) + os << "// " << class_name (*b->object) << endl + << "//" << endl + << "if (sk == statement_select)" << endl + << "n += object_traits_impl< " << class_fq_name (*b->object) << + ", id_" << db << " >::" << public_name (*b->member) << + "_traits::bind (" << endl + << "b + n, 0, 0, *i" << acc << ", sk" << + (b->versioned ? ", svm" : "") << ");" + << endl; + } + + if (!reuse_abst) + os << "// object_id" << endl + << "//" << endl + << "if (id != 0)" << endl + << "std::memcpy (&b[n], id, id_size * sizeof (id[0]));" + << "n += id_size;" // Not in if for "id unchanged" optimization. + << endl; + + os << "return n;" + << "}"; + } + + // grow () + // + if (generate_grow && (load || load_opt)) + { + os << "bool " << scope << "::" << endl + << "grow (image_type& i," << endl + << truncated_vector << " t"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (t);"; + + if (s.versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "bool grew (false);" + << endl; + + size_t index (0); + + if (s.base != 0 && !poly_derived) + { + user_section& b (*s.base); + + bool load (b.total != 0); + bool load_opt (b.optimistic ()); + + if (load || load_opt) + { + os << "// " << class_name (*b.object) << endl + << "//" << endl + << "grew = object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::grow (i, t" << (b.versioned ? ", svm" : "") << ");" + << endl; + + index += b.total + (load_opt ? 1 : 0); + } + } + + { + user_section* ps (&s); + instance<grow_member> gm (index, "", ps); + traversal::names n (*gm); + names (c_, n); + } + + // Grow polymorphic image chain. + // + if (s.base != 0 && poly_derived) + { + // Find the next base that has something to load, if any. + // + user_section* b (s.base); + string acc (".base"); + size_t cols; + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + cols = b->total + (b->optimistic () ? 1 : 0); + if (cols != 0) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + if (b != 0) + os << "// " << class_name (*b->object) << endl + << "//" << endl + << "if (object_traits_impl< " << class_fq_name (*b->object) << + ", id_" << db << " >::" << public_name (*b->member) << + "_traits::grow (" << endl + << "*i" << acc << ", t + " << cols << "UL" << + (b->versioned ? ", svm" : "") << "))" << endl + << "i" << acc << "->version++;" + << endl; + } + + os << "return grew;" << endl + << "}"; + } + + // init (object, image) + // + if (load) + { + os << "void " << scope << "::" << endl + << "init (object_type& o," << endl + << "const image_type& i," << endl + << "database* db"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (s.versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + if (s.base != 0) + { + if (!poly_derived) + { + user_section& b (*s.base); + + bool load (b.total != 0); + + if (load) + os << "// " << class_name (*b.object) << endl + << "//" << endl + << "object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::init (o, i, db" << + (b.versioned ? ", svm" : "") << ");" + << endl; + } + else + { + // Find the next base that has something to load, if any. + // + user_section* b (s.base); + string acc (".base"); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != 0) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + if (b != 0) + os << "// " << class_name (*b->object) << endl + << "//" << endl + << "object_traits_impl< " << class_fq_name (*b->object) << + ", id_" << db << " >::" << public_name (*b->member) << + "_traits::init (" << endl + << "o, *i" << acc << ", db" << + (b->versioned ? ", svm" : "") << ");" + << endl; + } + } + + { + instance<init_value_member> iv ("", "", true, &s); + traversal::names n (*iv); + names (c_, n); + } + + os << "}"; + } + + // init (image, object) + // + if (update) + { + os << (generate_grow ? "bool " : "void ") << scope << "::" << endl + << "init (image_type& i," << endl + << "const object_type& o"; + + if (s.versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{"; + + if (s.versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);" + << endl; + + os << "using namespace " << db << ";" + << endl + << "statement_kind sk (statement_insert);" + << "ODB_POTENTIALLY_UNUSED (sk);" + << endl; + + // There is no call to init_image_pre() here (which calls the + // copy callback for some databases) since we are not going to + // touch any of the members that were loaded by query. + + if (generate_grow) + os << "bool grew (false);" + << endl; + + if (s.base != 0 && !poly_derived) + { + user_section& b (*s.base); + + bool update (b.total != b.inverse + b.readonly); + + if (update) + os << "// " << class_name (*b.object) << endl + << "//" << endl + << (generate_grow ? "grew = " : "") << + "object_traits_impl< " << class_fq_name (*b.object) << + ", id_" << db << " >::" << public_name (*b.member) << + "_traits::init (i, o" << (b.versioned ? ", svm" : "") << ");" + << endl; + } + + { + instance<init_image_member> ii ("", "", &s); + traversal::names n (*ii); + names (c_, n); + } + + if (generate_grow) + os << "return grew;"; + + os << "}"; + } + + // The rest does not apply to reuse-abstract sections. + // + if (reuse_abst) + { + section_extra (s); + return; + } + + string sep (s.versioned ? "\n" : " "); + + // Schema name as a string literal or empty. + // + string schema_name (options.schema_name ()[db]); + if (!schema_name.empty ()) + schema_name = strlit (schema_name); + + // Statements. + // + qname table (table_name (c_)); + string qtable (quote_id (table)); + + instance<object_columns_list> id_cols; + id_cols->traverse (*id_member (c_)); + + // select_statement + // + if (load || load_opt) + { + size_t depth (poly_derived ? polymorphic_depth (c_) : 1); + + statement_columns sc; + { + statement_kind sk (statement_select); // Imperfect forwarding. + object_section* ps (&s); // Imperfect forwarding. + instance<object_columns> t (qtable, sk, sc, depth, ps); + t->traverse (c_); + process_statement_columns (sc, statement_select, s.versioned); + } + + os << "const char " << scope << "::" << endl + << "select_statement[] =" << endl + << strlit ("SELECT" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + os << strlit ("FROM " + qtable + sep) << endl; + + // Join polymorphic bases. + // + if (depth != 1 && s.base != 0) + { + bool f (false); //@@ (im)perfect forwarding + size_t d (depth - 1); //@@ (im)perfect forward. + instance<polymorphic_object_joins> j (c_, f, d, "", s.base); + j->traverse (*poly_base); + + for (strings::const_iterator i (j->begin ()); i != j->end (); ++i) + os << strlit (*i + sep) << endl; + } + + // Join tables of inverse members belonging to this section. + // + { + bool f (false); // @@ (im)perfect forwarding + object_section* ps (&s); // @@ (im)perfect forwarding + instance<object_joins> j (c_, f, depth, ps); + j->traverse (c_); + + for (strings::const_iterator i (j->begin ()); i != j->end (); ++i) + os << strlit (*i + sep) << endl; + } + + string where ("WHERE "); + instance<query_parameters> qp (statement_select, table); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += qtable + "." + quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + os << strlit (where) << ";" + << endl; + } + + // update_statement + // + if (update || update_opt) + { + instance<query_parameters> qp (statement_update, table); + + statement_columns sc; + { + query_parameters* p (qp.get ()); // Imperfect forwarding. + statement_kind sk (statement_update); // Imperfect forwarding. + object_section* ps (&s); // Imperfect forwarding. + instance<object_columns> t (sk, sc, p, ps); + t->traverse (c_); + process_statement_columns (sc, statement_update, s.versioned); + } + + os << "const char " << scope << "::" << endl + << "update_statement[] =" << endl + << strlit ("UPDATE " + qtable + sep) << endl + << strlit ("SET" + sep) << endl; + + for (statement_columns::const_iterator i (sc.begin ()), + e (sc.end ()); i != e;) + { + string const& c (i->column); + os << strlit (c + (++i != e ? "," : "") + sep) << endl; + } + + // This didn't work out: cannot change the identity column. + // + //if (sc.empty ()) + //{ + // // We can end up with nothing to set if we need to "touch" a row + // // in order to increment its optimistic concurrency version. In + // // this case just do a dummy assignment based on the id column. + // // + // string const& c (quote_id (id_cols->begin ()->name)); + // os << strlit (c + "=" + c) << endl; + //} + + string extra (update_statement_extra (s)); + + if (!extra.empty ()) + os << strlit (extra + sep) << endl; + + string where ("WHERE "); + for (object_columns_list::iterator b (id_cols->begin ()), i (b); + i != id_cols->end (); ++i) + { + if (i != b) + where += " AND "; + + where += quote_id (i->name) + "=" + + convert_to (qp->next (*i), i->type, *i->member); + } + + if (s.optimistic ()) // Note: not update_opt. + { + string name (column_qname (*opt, column_prefix ())); + string type (column_type (*opt)); + + where += " AND " + name + "=" + + convert_to (qp->next (*opt, name, type), type, *opt); + } + + os << strlit (where) << ";" + << endl; + } + + // load () + // + if (load || load_opt || load_con) + { + os << "void " << scope << "::" << endl + << "load (extra_statement_cache_type& esc, object_type& obj" << + (poly ? ", bool top" : "") << ")" + << "{"; + + if (poly) + os << "ODB_POTENTIALLY_UNUSED (top);" + << endl; + + if (s.versioned || s.versioned_containers) + os << "const schema_version_migration& svm (" << endl + << "esc." << m.name () << ".version_migration (" << + schema_name << "));" + << endl; + + // Load values, if any. + // + if (load || load_opt) + { + // The SELECT statement for the top override loads all the + // values. + // + if (poly) + os << "if (top)" + << "{"; + + // Note that we don't use delayed load machinery here. While + // a section can definitely contain self-referencing pointers, + // loading such a pointer won't mess up the data members in the + // image that we care about. It also holds true for streaming + // result, since the bindings are different. + + os << "using namespace " << db << ";" + << "using " << db << "::select_statement;" // Conflicts. + << endl + << "statements_type& sts (esc." << m.name () << ");" + << endl + << "image_type& im (sts.image ());" + << "binding& imb (sts.select_image_binding ());" + << endl; + + // For the polymorphic case, instead of storing an array of + // versions as we do for objects, we will add all the versions + // up and use that as a cumulative image chain version. If you + // meditate a bit on that, you will realize that it will work + // (hint: versions can only increase). + // + string ver; + string ver_decl; + + if (s.base != 0 && poly_derived) + { + ver = "imv"; + ver_decl = "std::size_t imv (im.version"; + + user_section* b (s.base); + string acc ("im.base"); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != 0 || b->optimistic ()) + ver_decl += " +\n" + acc + "->version"; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + acc += "->base"; + } + + ver_decl += ")"; + + os << ver_decl << ";" + << endl; + } + else + ver = "im.version"; + + os << "if (" << ver << " != sts.select_image_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (s.versioned ? ", svm" : "") << ");" + << "sts.select_image_version (" << ver << ");" + << "imb.version++;" + << "}"; + + // Id binding is assumed initialized and bound. + // + os << "select_statement& st (sts.select_statement ());"; + + // The statement can be dynamically empty. + // + if (s.versioned) + os << "if (!st.empty ())" + << "{"; + + os << "st.execute ();" + << "auto_result ar (st);" + << "select_statement::result r (st.fetch ());" + << endl; + + os << "if (r == select_statement::no_data)" << endl + << "throw object_not_persistent ();" + << endl; + + if (grow (c_, &s)) + { + os << "if (r == select_statement::truncated)" + << "{" + << "if (grow (im, sts.select_image_truncated ()" << + (s.versioned ? ", svm" : "") << "))" << endl + << "im.version++;" + << endl; + + // The same logic as above. + // + if (s.base != 0 && poly_derived) + os << ver_decl << ";" + << endl; + + os << "if (" << ver << " != sts.select_image_version ())" + << "{" + << "bind (imb.bind, 0, 0, im, statement_select" << + (s.versioned ? ", svm" : "") << ");" + << "sts.select_image_version (" << ver << ");" + << "imb.version++;" + << "st.refetch ();" + << "}" + << "}"; + } + + if (opt != 0) // Not load_opt, we do it in poly-derived as well. + { + os << "if ("; + + if (poly_derived) + { + os << "root_traits::version (*im.base"; + for (class_* b (poly_base); + b != poly_root; + b = &polymorphic_base (*b)) + os << "->base"; + os << ")"; + } + else + os << "version (im)"; + + os << " != " << (poly_derived ? "root_traits::" : "") << + "version (obj))" << endl + << "throw object_changed ();" + << endl; + } + + if (load) + { + os << "init (obj, im, &sts.connection ().database ()" << + (s.versioned ? ", svm" : "") << ");"; + init_value_extra (); // Stream results, etc. + os << endl; + } + + if (s.versioned) + os << "}"; // if (!st.empty ()) + + if (poly) + os << "}"; // if (top) + } + + // Call base to load its containers, if this is an override. + // + if (poly_derived && s.base != 0) + { + user_section* b (s.base); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + // If we don't have any values of our own but out base + // does, then allow it to load them. + // + if (b->containers || + (!load && (b->total != 0 || b->optimistic ()))) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + } + + // This one is tricky: ideally we would do a direct call to + // the base's load() (which may not be our immediate base, + // BTW) but there is no easy way to resolve base's extra + // statements from ours. So, instead, we are going to go + // via the dispatch machinery which requires a connection + // rather than statements. Not the most efficient way but + // simple. + + // Find the "previous" override by starting the search from + // our base. + // + if (b != 0) + { + // Note that here we are using the base section index to + // handle the special version update base. + // + os << "info.base->find_section_load (" << b->index << "UL) (" << + "esc." << m.name () << ".connection (), obj, " << + // If we don't have any values of our own, then allow the + // base load its. + // + (load ? "false" : "top") << ");" + << endl; + } + } + + // Load our containers, if any. + // + if (s.containers) + { + instance<container_calls> t (container_calls::load_call, &s); + t->traverse (c_); + } + + os << "}"; + } + + // update () + // + if (update || update_opt || update_con) + { + os << "void " << scope << "::" << endl + << "update (extra_statement_cache_type& esc, " << + "const object_type& obj" << + (poly_derived && s.base != 0 ? ", bool base" : "") << ")" + << "{"; + + // Call base if this is an override. + // + if (poly_derived && s.base != 0) + { + user_section* b (s.base); + for (class_* bo (poly_base);; bo = &polymorphic_base (*bo)) + { + if (b->object == bo) + { + if (b->total != b->inverse + b->readonly || + b->readwrite_containers || + (poly && b->optimistic ())) + break; + + b = b->base; + if (b == 0 || !polymorphic (*b->object)) + { + b = 0; + break; + } + } + } + + // The same (tricky) logic as in load(). Note that here we are + // using the base section index to handle the special version + // update base. + // + if (b != 0) + os << "if (base)" << endl + << "info.base->find_section_update (" << b->index << + "UL) (esc." << m.name () << ".connection (), obj);" + << endl; + else + os << "ODB_POTENTIALLY_UNUSED (base);" + << endl; + } + + if (s.versioned || s.readwrite_versioned_containers) + os << "const schema_version_migration& svm (" << endl + << "esc." << m.name () << ".version_migration (" << + schema_name << "));" + << endl; + + // Update values, if any. + // + if (update || update_opt) + { + os << "using namespace " << db << ";" + << "using " << db << "::update_statement;" // Conflicts. + << endl + << "statements_type& sts (esc." << m.name () << ");" + << endl + << "image_type& im (sts.image ());" + << "const binding& id (sts.idv_binding ());" // id+version + << "binding& imb (sts.update_image_binding ());" + << endl; + + if (update) + { + if (generate_grow) + os << "if ("; + + os << "init (im, obj" << (s.versioned ? ", svm" : "") << ")"; + + if (generate_grow) + os << ")" << endl + << "im.version++"; + + os << ";" + << endl; + } + + os << "if (im.version != sts.update_image_version () ||" << endl + << "id.version != sts.update_id_binding_version () ||" << endl + << "imb.version == 0)" + << "{" + << "bind (imb.bind, id.bind, id.count, im, statement_update" << + (s.versioned ? ", svm" : "") << ");" + << "sts.update_image_version (im.version);" + << "sts.update_id_binding_version (id.version);" + << "imb.version++;" + << "}"; + + os << "update_statement& st (sts.update_statement ());" + << "if ("; + + if (s.versioned) + os << "!st.empty () && "; + + os << "st.execute () == 0)" << endl; + + if (opt == 0) + os << "throw object_not_persistent ();"; + else + os << "throw object_changed ();"; + + os << endl; + } + + // Update readwrite containers if any. + // + if (s.readwrite_containers) + { + instance<container_calls> t (container_calls::update_call, &s); + t->traverse (c_); + } + + // Update the optimistic concurrency version in the object member. + // Very similar code to object. + // + if (s.optimistic ()) // Note: not update_opt. + { + // Object is passed as const reference so we need to cast away + // constness. + // + const char* obj ("const_cast<object_type&> (obj)"); + string inc (optimistic_version_increment (*opt)); + + if (inc == "1") + inc_member (*opt, obj, "obj", "version_type"); + else + set_member (*opt, obj, inc, "", "version_type"); + } + + os << "}"; + } + + section_extra (s); + + if (rs != 0) + rs->base = 0; + } + + using class_::traverse; // Unhide. + + protected: + semantics::class_& c_; + string scope_; + }; + + // Output a list of parameters for the persist statement. + // + struct persist_statement_params: object_columns_base, virtual context + { + typedef persist_statement_params base; + + persist_statement_params (string& params, + query_parameters& qp, + const string& sep) + : params_ (params), qp_ (qp), sep_ (sep) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_& c) + { + if (!inverse (m, key_prefix_)) + object_columns_base::traverse_pointer (m, c); + } + + virtual bool + traverse_column (semantics::data_member& m, + string const& name, + bool first) + { + string p; + + if (version (m)) + p = version_value (m); + else + { + const string& qname (quote_id (name)); + const string& type (column_type ()); + + p = auto_ (m) // Only simple, direct id can be auto. + ? qp_.auto_id (m, qname, type) + : qp_.next (m, qname, type); + } + + if (!p.empty ()) + { + if (!first) + { + params_ += ','; + params_ += sep_; + } + + params_ += (p != "DEFAULT" ? convert_to (p, column_type (), m) : p); + } + + return !p.empty (); + } + + virtual string + version_value (semantics::data_member&) + { + return "1"; + } + + private: + string& params_; + query_parameters& qp_; + const string& sep_; + }; + + // + // + struct class_: traversal::class_, virtual context + { + typedef class_ base; + + class_ () + : typedefs_ (false), + query_columns_type_ (false, false, false), + view_query_columns_type_ (false), + index_ (0), + grow_base_ (index_), + grow_member_ (index_), + grow_version_member_ (index_, "version_"), + grow_discriminator_member_ (index_, "discriminator_"), + bind_id_member_ ("id_"), + bind_version_member_ ("version_"), + bind_discriminator_member_ ("discriminator_"), + init_id_image_member_ ("id_", "id"), + init_version_image_member_ ("version_", "(*v)"), + init_view_pointer_member_pre_ (true, *init_value_member_), + init_view_pointer_member_post_ (false, *init_value_member_), + init_id_value_member_ ("id"), + init_id_value_member_id_image_ ("id", "id_"), + init_version_value_member_ ("v"), + init_named_version_value_member_ ("v", "version_"), + init_discriminator_value_member_ ("d", "", false), + init_named_discriminator_value_member_ ( + "d", "discriminator_", false) + { + init (); + } + + class_ (class_ const&) + : root_context (), //@@ -Wextra + context (), + typedefs_ (false), + query_columns_type_ (false, false, false), + view_query_columns_type_ (false), + index_ (0), + grow_base_ (index_), + grow_member_ (index_), + grow_version_member_ (index_, "version_"), + grow_discriminator_member_ (index_, "discriminator_"), + bind_id_member_ ("id_"), + bind_version_member_ ("version_"), + bind_discriminator_member_ ("discriminator_"), + init_id_image_member_ ("id_", "id"), + init_version_image_member_ ("version_", "(*v)"), + init_view_pointer_member_pre_ (true, *init_value_member_), + init_view_pointer_member_post_ (false, *init_value_member_), + init_id_value_member_ ("id"), + init_id_value_member_id_image_ ("id", "id_"), + init_version_value_member_ ("v"), + init_named_version_value_member_ ("v", "version_"), + init_discriminator_value_member_ ("d", "", false), + init_named_discriminator_value_member_ ( + "d", "discriminator_", false) + { + init (); + } + + void + init () + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + if (generate_grow) + { + grow_base_inherits_ >> grow_base_; + grow_member_names_ >> grow_member_; + } + + bind_base_inherits_ >> bind_base_; + bind_member_names_ >> bind_member_; + + init_image_base_inherits_ >> init_image_base_; + init_image_member_names_ >> init_image_member_; + + init_value_base_inherits_ >> init_value_base_; + init_value_member_names_ >> init_value_member_; + + init_view_pointer_member_pre_names_ >> init_view_pointer_member_pre_; + init_view_pointer_member_post_names_ >> init_view_pointer_member_post_; + } + + virtual void + init_auto_id (semantics::data_member&, // id member + string const&) // image variable prefix + { + if (insert_send_auto_id) + assert (false); + } + + virtual void + init_image_pre (type&) + { + } + + virtual void + init_value_extra () + { + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + + if (ck == class_other || + (!options.at_once () && class_file (c) != unit.file ())) + return; + + names (c); + + context::top_object = context::cur_object = &c; + + switch (ck) + { + case class_object: traverse_object (c); break; + case class_view: traverse_view (c); break; + case class_composite: traverse_composite (c); break; + default: break; + } + + context::top_object = context::cur_object = 0; + } + + // + // statements + // + + enum persist_position + { + persist_after_columns, + persist_after_values + }; + + virtual string + persist_statement_extra (type&, query_parameters&, persist_position) + { + return ""; + } + + virtual string + update_statement_extra (type&) + { + return ""; + } + + // + // common + // + + virtual void + post_query_ (type&, bool /*once_off*/) + { + } + + virtual void + process_statement_columns (statement_columns&, + statement_kind, + bool /*dynamic*/) + { + } + + // + // object + // + + virtual void + object_extra (type&) {} + + virtual void + extra_statement_cache_extra_args (bool /*containers*/, + bool /*sections*/) {} + + virtual void + object_query_statement_ctor_args (type&, + std::string const& q, + bool process, + bool /*prepared*/) + { + os << "conn," << endl + << "text," << endl + << process << "," << endl // Process. + << "true," << endl // Optimize. + << q << ".parameters_binding ()," << endl + << "imb"; + } + + virtual void + object_erase_query_statement_ctor_args (type&) + { + os << "conn," << endl + << "text," << endl + << "q.parameters_binding ()"; + } + + virtual string + optimistic_version_init (semantics::data_member&, bool /*index*/ = false) + { + return "1"; + } + + // Returning "1" means increment by one. + // + virtual string + optimistic_version_increment (semantics::data_member&, + bool /*index*/ = false) + { + return "1"; + } + + virtual bool + optimistic_insert_bind_version (semantics::data_member&) + { + return false; + } + + virtual void + traverse_object (type& c); + + // + // view + // + + virtual void + view_extra (type&) + { + } + + virtual void + view_query_statement_ctor_args (type&, + string const& q, + bool process, + bool /*prepared*/) + { + os << "conn," << endl + << q << ".clause ()," << endl + << process << "," << endl // Process. + << "true," << endl // Optimize. + << q << ".parameters_binding ()," << endl + << "imb"; + } + + virtual string + from_trailer (type&) { return "";} + + virtual string + select_trailer (type& c) + { + return c.get<view_query> ("query").for_update ? "FOR UPDATE" : ""; + } + + virtual string + join_syntax (view_object const& vo) + { + const char* r (0); + + switch (vo.join) + { + case view_object::left: r = "LEFT JOIN"; break; + case view_object::right: r = "RIGHT JOIN"; break; + case view_object::full: r = "FULL JOIN"; break; + case view_object::inner: r = "INNER JOIN"; break; + case view_object::cross: r = "CROSS JOIN"; break; + } + + return r; + } + + virtual void + traverse_view (type& c); + + struct expression + { + explicit + expression (std::string const& v): kind (literal), value (v) {} + expression (view_object* vo): kind (pointer), vo (vo) {} + + enum kind_type {literal, pointer}; + + kind_type kind; + std::string value; + data_member_path member_path; + view_object* vo; + }; + + expression + translate_expression (type& c, + cxx_tokens const&, + semantics::scope& start_scope, + location_t loc, + string const& prag, + bool* placeholder = 0, + bool predicate = true); + // + // composite + // + + virtual void + traverse_composite (type& c) + { + bool versioned (context::versioned (c)); + + string const& type (class_fq_name (c)); + string traits ("access::composite_value_traits< " + type + ", id_" + + db.string () + " >"); + + os << "// " << class_name (c) << endl + << "//" << endl + << endl; + + // Containers. + // + { + instance<container_traits> t (c); + t->traverse (c); + } + + // grow () + // + if (generate_grow) + { + os << "bool " << traits << "::" << endl + << "grow (image_type& i," << endl + << truncated_vector << " t"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (t);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "bool grew (false);" + << endl; + + index_ = 0; + inherits (c, grow_base_inherits_); + names (c, grow_member_names_); + + os << "return grew;" + << "}"; + } + + // bind (image_type) + // + os << "void " << traits << "::" << endl + << "bind (" << bind_vector << " b," << endl + << "image_type& i," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (b);" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + if (readonly (c)) + os << "assert (sk != statement_update);" + << endl; + + os << "std::size_t n (0);" + << "ODB_POTENTIALLY_UNUSED (n);" + << endl; + + inherits (c, bind_base_inherits_); + names (c, bind_member_names_); + + os << "}"; + + // init (image, value) + // + os << (generate_grow ? "bool " : "void ") << traits << "::" << endl + << "init (image_type& i," << endl + << "const value_type& o," << endl + << db << "::statement_kind sk"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (sk);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl + << "using namespace " << db << ";" + << endl; + + if (readonly (c)) + os << "assert (sk != statement_update);" + << endl; + + if (generate_grow) + os << "bool grew (false);" + << endl; + + inherits (c, init_image_base_inherits_); + names (c, init_image_member_names_); + + if (generate_grow) + os << "return grew;"; + + os << "}"; + + // init (value, image) + // + os << "void " << traits << "::" << endl + << "init (value_type& o," << endl + << "const image_type& i," << endl + << "database* db"; + + if (versioned) + os << "," << endl + << "const schema_version_migration& svm"; + + os << ")" + << "{" + << "ODB_POTENTIALLY_UNUSED (o);" + << "ODB_POTENTIALLY_UNUSED (i);" + << "ODB_POTENTIALLY_UNUSED (db);"; + + if (versioned) + os << "ODB_POTENTIALLY_UNUSED (svm);"; + + os << endl; + + inherits (c, init_value_base_inherits_); + names (c, init_value_member_names_); + + os << "}"; + } + + private: + traversal::defines defines_; + typedefs typedefs_; + + instance<query_columns_type> query_columns_type_; + instance<view_query_columns_type> view_query_columns_type_; + + size_t index_; + instance<grow_base> grow_base_; + traversal::inherits grow_base_inherits_; + instance<grow_member> grow_member_; + traversal::names grow_member_names_; + instance<grow_member> grow_version_member_; + instance<grow_member> grow_discriminator_member_; + + + instance<bind_base> bind_base_; + traversal::inherits bind_base_inherits_; + instance<bind_member> bind_member_; + traversal::names bind_member_names_; + instance<bind_member> bind_id_member_; + instance<bind_member> bind_version_member_; + instance<bind_member> bind_discriminator_member_; + + instance<init_image_base> init_image_base_; + traversal::inherits init_image_base_inherits_; + instance<init_image_member> init_image_member_; + traversal::names init_image_member_names_; + + instance<init_image_member> init_id_image_member_; + instance<init_image_member> init_version_image_member_; + + instance<init_value_base> init_value_base_; + traversal::inherits init_value_base_inherits_; + instance<init_value_member> init_value_member_; + traversal::names init_value_member_names_; + + instance<init_view_pointer_member> init_view_pointer_member_pre_; + instance<init_view_pointer_member> init_view_pointer_member_post_; + traversal::names init_view_pointer_member_pre_names_; + traversal::names init_view_pointer_member_post_names_; + + instance<init_value_member> init_id_value_member_; + instance<init_value_member> init_id_value_member_id_image_; + instance<init_value_member> init_version_value_member_; + instance<init_value_member> init_named_version_value_member_; + instance<init_value_member> init_discriminator_value_member_; + instance<init_value_member> init_named_discriminator_value_member_; + }; + + struct include: virtual context + { + typedef include base; + + virtual void + generate () + { + extra_pre (); + + os << "#include <cassert>" << endl + << "#include <cstring> // std::memcpy" << endl; + + if (features.polymorphic_object) + os << "#include <typeinfo>" << endl; + + os << endl; + + if (features.polymorphic_object) + os << "#include <odb/polymorphic-map.hxx>" << endl; + + if (embedded_schema) + os << "#include <odb/schema-catalog-impl.hxx>" << endl; + + if (multi_dynamic) + os << "#include <odb/function-table.hxx>" << endl; + + os << endl; + + os << "#include <odb/" << db << "/traits.hxx>" << endl + << "#include <odb/" << db << "/database.hxx>" << endl + << "#include <odb/" << db << "/transaction.hxx>" << endl + << "#include <odb/" << db << "/connection.hxx>" << endl + << "#include <odb/" << db << "/statement.hxx>" << endl + << "#include <odb/" << db << "/statement-cache.hxx>" << endl; + + if (features.simple_object) + os << "#include <odb/" << db << "/simple-object-statements.hxx>" << endl; + + if (features.polymorphic_object) + os << "#include <odb/" << db << "/polymorphic-object-statements.hxx>" << endl; + + if (features.no_id_object) + os << "#include <odb/" << db << "/no-id-object-statements.hxx>" << endl; + + if (features.view) + os << "#include <odb/" << db << "/view-statements.hxx>" << endl; + + if (features.section) + os << "#include <odb/" << db << "/section-statements.hxx>" << endl; + + os << "#include <odb/" << db << "/container-statements.hxx>" << endl + << "#include <odb/" << db << "/exceptions.hxx>" << endl; + + if (options.generate_query ()) + { + if (options.generate_prepared ()) + os << "#include <odb/" << db << "/prepared-query.hxx>" << endl; + + if (features.simple_object) + os << "#include <odb/" << db << "/simple-object-result.hxx>" << endl; + + if (features.polymorphic_object) + os << "#include <odb/" << db << "/polymorphic-object-result.hxx>" << endl; + + if (features.no_id_object) + os << "#include <odb/" << db << "/no-id-object-result.hxx>" << endl; + + if (features.view) + os << "#include <odb/" << db << "/view-result.hxx>" << endl; + } + + extra_post (); + + os << endl; + } + + virtual void + extra_pre () + { + } + + virtual void + extra_post () + { + } + }; + } +} + +#endif // ODB_RELATIONAL_SOURCE_HXX diff --git a/odb/odb/relational/sqlite/common.cxx b/odb/odb/relational/sqlite/common.cxx new file mode 100644 index 0000000..03a3599 --- /dev/null +++ b/odb/odb/relational/sqlite/common.cxx @@ -0,0 +1,217 @@ +// file : odb/relational/sqlite/common.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <cassert> + +#include <odb/relational/sqlite/common.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + // + // member_base + // + + sql_type const& member_base:: + member_sql_type (semantics::data_member& m) + { + return parse_sql_type (column_type (m, key_prefix_), m); + } + + void member_base:: + traverse_simple (member_info& mi) + { + switch (mi.st->type) + { + case sql_type::INTEGER: + { + traverse_integer (mi); + break; + } + case sql_type::REAL: + { + traverse_real (mi); + break; + } + case sql_type::TEXT: + { + if (mi.st->stream) + traverse_text_stream (mi); + else + traverse_text (mi); + break; + } + case sql_type::BLOB: + { + if (mi.st->stream) + traverse_blob_stream (mi); + else + traverse_blob (mi); + break; + } + case sql_type::invalid: + { + assert (false); + break; + } + } + } + + // + // member_image_type + // + + member_image_type:: + member_image_type (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_image_type:: + member_image_type () + : relational::member_base (0, 0, string (), string ()) {} + + member_image_type:: + member_image_type (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : relational::member_base (type, ct, fq_type, key_prefix) {} + + string member_image_type:: + image_type (semantics::data_member& m) + { + type_.clear (); + member_base::traverse (m, true); + return type_; + } + + void member_image_type:: + traverse_composite (member_info& mi) + { + type_ = "composite_value_traits< " + mi.fq_type () + + ", id_sqlite >::image_type"; + } + + void member_image_type:: + traverse_integer (member_info&) + { + type_ = "long long"; + } + + void member_image_type:: + traverse_real (member_info&) + { + type_ = "double"; + } + + void member_image_type:: + traverse_string (member_info&) + { + type_ = "details::buffer"; + } + + void member_image_type:: + traverse_stream (member_info&) + { + type_ = "sqlite::stream_buffers"; + } + + entry<member_image_type> member_image_type_; + + // + // member_database_type + // + + member_database_type_id:: + member_database_type_id (base const& x) + : member_base::base (x), // virtual base + base (x) {} + + member_database_type_id:: + member_database_type_id () + : member_base::base (0, 0, string (), string ()), // virtual base + base (0, 0, string (), string ()) {} + + member_database_type_id:: + member_database_type_id (semantics::type* type, + const custom_cxx_type* ct, + string const& fq_type, + string const& key_prefix) + : member_base::base (type, ct, fq_type, key_prefix), // virtual base + base (type, ct, fq_type, key_prefix) {} + + string member_database_type_id:: + database_type_id (type& m) + { + type_id_.clear (); + member_base::traverse (m, true); + return type_id_; + } + + void member_database_type_id:: + traverse_composite (member_info&) + { + assert (false); + } + + void member_database_type_id:: + traverse_integer (member_info&) + { + type_id_ = "sqlite::id_integer"; + } + + void member_database_type_id:: + traverse_real (member_info&) + { + type_id_ = "sqlite::id_real"; + } + + void member_database_type_id:: + traverse_text (member_info&) + { + type_id_ = "sqlite::id_text"; + } + + void member_database_type_id:: + traverse_blob (member_info&) + { + type_id_ = "sqlite::id_blob"; + } + + void member_database_type_id:: + traverse_text_stream (member_info&) + { + type_id_ = "sqlite::id_text_stream"; + } + + void member_database_type_id:: + traverse_blob_stream (member_info&) + { + type_id_ = "sqlite::id_blob_stream"; + } + + entry<member_database_type_id> member_database_type_id_; + + // + // query_columns + // + + struct query_columns: relational::query_columns, context + { + query_columns (base const& x): base_impl (x) {} + + virtual string + database_type_id (semantics::data_member& m) + { + return member_database_type_id_.database_type_id (m); + } + + private: + member_database_type_id member_database_type_id_; + }; + entry<query_columns> query_columns_; + } +} diff --git a/odb/odb/relational/sqlite/common.hxx b/odb/odb/relational/sqlite/common.hxx new file mode 100644 index 0000000..4d6089e --- /dev/null +++ b/odb/odb/relational/sqlite/common.hxx @@ -0,0 +1,147 @@ +// file : odb/relational/sqlite/common.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SQLITE_COMMON_HXX +#define ODB_RELATIONAL_SQLITE_COMMON_HXX + +#include <odb/relational/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +namespace relational +{ + namespace sqlite + { + struct member_base: virtual relational::member_base_impl<sql_type>, context + { + member_base (base const& x): base (x), base_impl (x) {} + + // This c-tor is for the direct use inside the sqlite namespace. + // If you do use this c-tor, you should also explicitly call + // relational::member_base (aka base). + // + member_base () {} + + virtual sql_type const& + member_sql_type (semantics::data_member&); + + virtual void + traverse_simple (member_info&); + + virtual void + traverse_integer (member_info&) + { + } + + virtual void + traverse_real (member_info&) + { + } + + virtual void + traverse_text (member_info& m) + { + traverse_string (m); + } + + virtual void + traverse_blob (member_info& m) + { + traverse_string (m); + } + + // String covers both text and blob. + // + virtual void + traverse_string (member_info&) + { + } + + virtual void + traverse_text_stream (member_info& m) + { + traverse_stream (m); + } + + virtual void + traverse_blob_stream (member_info& m) + { + traverse_stream (m); + } + + virtual void + traverse_stream (member_info&) + { + } + }; + + struct member_image_type: relational::member_image_type, + member_base + { + member_image_type (base const&); + member_image_type (); + member_image_type (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + virtual string + image_type (semantics::data_member&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_real (member_info&); + + virtual void + traverse_string (member_info&); + + virtual void + traverse_stream (member_info&); + + private: + string type_; + }; + + struct member_database_type_id: relational::member_database_type_id, + member_base + { + member_database_type_id (base const&); + member_database_type_id (); + member_database_type_id (semantics::type* type, + const custom_cxx_type*, + string const& fq_type = string (), + string const& key_prefix = string ()); + + virtual string + database_type_id (type&); + + virtual void + traverse_composite (member_info&); + + virtual void + traverse_integer (member_info&); + + virtual void + traverse_real (member_info&); + + virtual void + traverse_text (member_info&); + + virtual void + traverse_blob (member_info&); + + virtual void + traverse_text_stream (member_info&); + + virtual void + traverse_blob_stream (member_info&); + + private: + string type_id_; + }; + } +} +#endif // ODB_RELATIONAL_SQLITE_COMMON_HXX diff --git a/odb/odb/relational/sqlite/context.cxx b/odb/odb/relational/sqlite/context.cxx new file mode 100644 index 0000000..9a4369f --- /dev/null +++ b/odb/odb/relational/sqlite/context.cxx @@ -0,0 +1,490 @@ +// file : odb/relational/sqlite/context.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <vector> +#include <cassert> +#include <sstream> + +#include <odb/sql-token.hxx> +#include <odb/sql-lexer.hxx> + +#include <odb/relational/sqlite/context.hxx> +#include <odb/relational/sqlite/common.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + namespace + { + struct type_map_entry + { + char const* const cxx_type; + char const* const db_type; + char const* const db_id_type; + bool const null; + }; + + type_map_entry type_map[] = + { + {"bool", "INTEGER", 0, false}, + + {"char", "TEXT", 0, false}, + {"wchar_t", "TEXT", 0, false}, + {"signed char", "INTEGER", 0, false}, + {"unsigned char", "INTEGER", 0, false}, + + {"short int", "INTEGER", 0, false}, + {"short unsigned int", "INTEGER", 0, false}, + + {"int", "INTEGER", 0, false}, + {"unsigned int", "INTEGER", 0, false}, + + {"long int", "INTEGER", 0, false}, + {"long unsigned int", "INTEGER", 0, false}, + + {"long long int", "INTEGER", 0, false}, + {"long long unsigned int", "INTEGER", 0, false}, + + // SQLite stores NaN as NULL. + // + {"float", "REAL", 0, true}, + {"double", "REAL", 0, true}, + + {"::std::string", "TEXT", 0, false}, + {"::std::wstring", "TEXT", 0, false} + }; + } + + context* context::current_; + + context:: + ~context () + { + if (current_ == this) + current_ = 0; + } + + context:: + context (ostream& os, + semantics::unit& u, + options_type const& ops, + features_type& f, + sema_rel::model* m) + : root_context (os, u, ops, f, data_ptr (new (shared) data (os))), + base_context (static_cast<data*> (root_context::data_.get ()), m), + data_ (static_cast<data*> (base_context::data_)) + { + assert (current_ == 0); + current_ = this; + + generate_grow = true; + need_alias_as = true; + insert_send_auto_id = true; + delay_freeing_statement_result = false; + need_image_clone = false; + generate_bulk = false; + global_index = true; + global_fkey = false; + data_->bind_vector_ = "sqlite::bind*"; + data_->truncated_vector_ = "bool*"; + + // Populate the C++ type to DB type map. + // + for (size_t i (0); i < sizeof (type_map) / sizeof (type_map_entry); ++i) + { + type_map_entry const& e (type_map[i]); + + type_map_type::value_type v ( + e.cxx_type, + db_type_type ( + e.db_type, e.db_id_type ? e.db_id_type : e.db_type, e.null)); + + data_->type_map_.insert (v); + } + } + + context:: + context () + : data_ (current ().data_) + { + } + + string const& context:: + convert_expr (string const& sqlt, semantics::data_member& m, bool to) + { + sql_type const& t (parse_sql_type (sqlt, m)); + return to ? t.to : t.from; + } + + namespace + { + struct has_grow: traversal::class_ + { + has_grow (bool& r, user_section* s) + : r_ (r), section_ (s) + { + *this >> inherits_ >> *this; + } + + virtual void + traverse (type& c) + { + // Ignore transient bases. + // + if (!(context::object (c) || context::composite (c))) + return; + + if (section_ == 0 && c.count ("sqlite-grow")) + r_ = c.get<bool> ("sqlite-grow"); + else + { + // r_ should be false. + // + inherits (c); + + if (!r_) + names (c); + + if (section_ == 0) + c.set ("sqlite-grow", r_); + } + } + + private: + bool& r_; + user_section* section_; + traversal::inherits inherits_; + }; + + struct has_grow_member: member_base + { + has_grow_member (bool& r, user_section* section = 0) + : relational::member_base (0, 0, string (), string (), section), + r_ (r) {} + + has_grow_member (bool& r, + user_section* section, + semantics::type* t, + const custom_cxx_type* ct, + string const& key_prefix = string ()) + : relational::member_base (t, ct, string (), key_prefix, section), + r_ (r) {} + + virtual bool + pre (member_info& mi) + { + // If we have a key prefix (container), then it can't be in a + // section (while mi.m can). The same for top-level -- if we got + // called, then we shouldn't ignore it. + // + return !key_prefix_.empty () || top_level_ || + (section_ == 0 && !separate_load (mi.m)) || + (section_ != 0 && *section_ == section (mi.m)); + } + + virtual void + traverse_composite (member_info& mi) + { + // By calling grow() instead of recursing, we reset any overrides. + // We also don't pass section since they don't apply inside + // composites. + // + r_ = r_ || context::grow (dynamic_cast<semantics::class_&> (mi.t)); + } + + virtual void + traverse_string (member_info&) + { + r_ = true; + } + + private: + bool& r_; + }; + } + + bool context:: + grow_impl (semantics::class_& c, user_section* section) + { + if (section == 0 && c.count ("sqlite-grow")) + return c.get<bool> ("sqlite-grow"); + + bool r (false); + has_grow ct (r, section); + has_grow_member mt (r, section); + traversal::names names; + ct >> names >> mt; + ct.traverse (c); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m) + { + bool r (false); + has_grow_member mt (r); + mt.traverse (m, true); + return r; + } + + bool context:: + grow_impl (semantics::data_member& m, + semantics::type& t, + const custom_cxx_type* ct, + string const& kp) + { + bool r (false); + has_grow_member mt (r, 0, &t, ct, kp); + mt.traverse (m, true); + return r; + } + + string context:: + database_type_impl (semantics::type& t, + semantics::names* hint, + bool id, + bool* null) + { + string r (base_context::database_type_impl (t, hint, id, null)); + + if (!r.empty ()) + return r; + + using semantics::array; + + // char[N] mapping. + // + if (array* a = dynamic_cast<array*> (&t)) + { + semantics::type& bt (a->base_type ()); + if (bt.is_a<semantics::fund_char> () || + bt.is_a<semantics::fund_wchar> ()) + { + if (a->size () != 0) + r = "TEXT"; + } + } + + return r; + } + + // + // SQL type parsing. + // + + namespace + { + struct sql_parser + { + typedef context::invalid_sql_type invalid_sql_type; + + sql_parser (custom_db_types const* ct): ct_ (ct) {} + + sql_type + parse (string sql) + { + sql_type r; + + // First run the type through the custom mapping, if requested. + // + if (ct_ != 0) + { + for (custom_db_types::const_iterator i (ct_->begin ()); + i != ct_->end (); ++i) + { + custom_db_type const& t (*i); + + if (t.type.match (sql)) + { + r.to = t.type.replace (sql, t.to); + r.from = t.type.replace (sql, t.from); + sql = t.type.replace (sql, t.as); + break; + } + } + } + + // Parse the type into a sequence of identifiers. + // + try + { + l_.lex (sql); + + for (sql_token t (l_.next ()); t.type () != sql_token::t_eos;) + { + sql_token::token_type tt (t.type ()); + + if (tt == sql_token::t_identifier) + { + ids_.push_back (context::upcase (t.identifier ())); + t = l_.next (); + + if (t.punctuation () == sql_token::p_lparen) + { + if (!parse_range ()) + return error (m_); + + t = l_.next (); + } + } + else + return error ("expected SQLite type name instead of '" + + t.string () + "'"); + } + } + catch (sql_lexer::invalid_input const& e) + { + return error ("invalid SQLite type declaration: " + e.message); + } + + if (ids_.empty ()) + return error ("expected SQLite type name"); + + // First check our own types. + // + if (ids_.size () == 2 && ids_[0] == "TEXT" && ids_[1] == "STREAM") + { + r.type = sql_type::TEXT; + r.stream = true; + } + if (ids_.size () == 2 && ids_[0] == "BLOB" && ids_[1] == "STREAM") + { + r.type = sql_type::BLOB; + r.stream = true; + } + // + // Apply the first four rules of the SQLite type to affinity + // conversion algorithm. + // + else if (find ("INT")) + r.type = sql_type::INTEGER; + else if (find ("TEXT") || find ("CHAR") || find ("CLOB")) + r.type = sql_type::TEXT; + else if (find ("BLOB")) + r.type = sql_type::BLOB; + else if (find ("REAL") || find ("FLOA") || find ("DOUB")) + r.type = sql_type::REAL; + else + { + // Instead of the fifth rule which maps everything else + // to NUMERICAL (which we don't have), map some commonly + // used type names to one of the above types. + // + string const& id (ids_[0]); + + if (id == "NUMERIC") + r.type = sql_type::REAL; + else if (id == "DECIMAL") + r.type = sql_type::TEXT; + else if (id == "BOOLEAN" || id == "BOOL") + r.type = sql_type::INTEGER; + else if (id == "DATE" || id == "TIME" || id == "DATETIME") + r.type = sql_type::TEXT; + else + return error ("unknown SQLite type '" + id + "'"); + } + + return r; + } + + bool + parse_range () + { + // Skip tokens until we get the closing paren. + // + for (sql_token t (l_.next ());; t = l_.next ()) + { + if (t.punctuation () == sql_token::p_rparen) + break; + + if (t.type () == sql_token::t_eos) + { + m_ = "missing ')' in SQLite type declaration"; + return false; + } + } + + return true; + } + + private: + sql_type + error (string const& m) + { + if (ct_ == 0) + return sql_type (); + else + throw invalid_sql_type (m); + } + + bool + find (string const& str) const + { + for (identifiers::const_iterator i (ids_.begin ()); + i != ids_.end (); ++i) + { + if (i->find (str) != string::npos) + return true; + } + + return false; + } + + private: + custom_db_types const* ct_; + sql_lexer l_; + string m_; // Error message. + + typedef vector<string> identifiers; + identifiers ids_; + }; + } + + sql_type const& context:: + parse_sql_type (string const& t, semantics::data_member& m, bool custom) + { + // If this proves to be too expensive, we can maintain a cache of + // parsed types across contexts. + // + data::sql_type_cache::iterator i (data_->sql_type_cache_.find (t)); + + if (i != data_->sql_type_cache_.end () + && (custom ? i->second.custom_cached : i->second.straight_cached)) + { + return (custom ? i->second.custom : i->second.straight); + } + else + { + try + { + sql_type st ( + parse_sql_type ( + t, + custom ? &unit.get<custom_db_types> ("custom-db-types") : 0)); + + if (custom) + return data_->sql_type_cache_[t].cache_custom (st); + else + return data_->sql_type_cache_[t].cache_straight (st); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } + } + } + + sql_type context:: + parse_sql_type (string const& sqlt, custom_db_types const* ct) + { + sql_parser p (ct); + return p.parse (sqlt); + } + } +} diff --git a/odb/odb/relational/sqlite/context.hxx b/odb/odb/relational/sqlite/context.hxx new file mode 100644 index 0000000..777998b --- /dev/null +++ b/odb/odb/relational/sqlite/context.hxx @@ -0,0 +1,146 @@ +// file : odb/relational/sqlite/context.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_SQLITE_CONTEXT_HXX +#define ODB_RELATIONAL_SQLITE_CONTEXT_HXX + +#include <map> + +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace sqlite + { + struct sql_type + { + // Keep the order in each block of types. + // + enum core_type + { + INTEGER, + REAL, + TEXT, + BLOB, + invalid + }; + + sql_type (): type (invalid), stream (false) {} + + core_type type; + bool stream; // TEXT or BLOB via sqlite3_blob_open(). + + // Conversion expressions for custom database types. + // + std::string to; + std::string from; + }; + + class context: public virtual relational::context + { + public: + sql_type const& + parse_sql_type (string const&, + semantics::data_member&, + bool custom = true); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + // If custom_db_types is NULL, then this function returns + // invalid type instead of throwing in case an unknown type + // is encountered. + // + static sql_type + parse_sql_type (string const&, custom_db_types const* = 0); + + protected: + virtual string const& + convert_expr (string const&, semantics::data_member&, bool); + + virtual bool + grow_impl (semantics::class_&, user_section*); + + virtual bool + grow_impl (semantics::data_member&); + + virtual bool + grow_impl (semantics::data_member&, + semantics::type&, + const custom_cxx_type*, + string const&); + + protected: + virtual string + database_type_impl (semantics::type&, semantics::names*, bool, bool*); + + public: + virtual + ~context (); + context (); + context (std::ostream&, + semantics::unit&, + options_type const&, + features_type& f, + sema_rel::model*); + + static context& + current () + { + return *current_; + } + + private: + static context* current_; + + private: + struct data: base_context::data + { + data (std::ostream& os): base_context::data (os) {} + + struct sql_type_cache_entry + { + sql_type_cache_entry () + : custom_cached (false), straight_cached (false) {} + + sql_type const& + cache_custom (sql_type const& t) + { + custom = t; + custom_cached = true; + return custom; + } + + sql_type const& + cache_straight (sql_type const& t) + { + straight = t; + straight_cached = true; + return straight; + } + + sql_type custom; // With custom mapping. + sql_type straight; // Without custom mapping. + + bool custom_cached; + bool straight_cached; + }; + + typedef std::map<string, sql_type_cache_entry> sql_type_cache; + sql_type_cache sql_type_cache_; + }; + + data* data_; + }; + } +} + +#endif // ODB_RELATIONAL_SQLITE_CONTEXT_HXX diff --git a/odb/odb/relational/sqlite/header.cxx b/odb/odb/relational/sqlite/header.cxx new file mode 100644 index 0000000..1aafe7a --- /dev/null +++ b/odb/odb/relational/sqlite/header.cxx @@ -0,0 +1,63 @@ +// file : odb/relational/sqlite/header.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/header.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +namespace relational +{ + namespace sqlite + { + namespace header + { + namespace relational = relational::header; + + struct image_member: relational::image_member_impl<sql_type>, + member_base + { + image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_real (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + + virtual void + traverse_stream (member_info& mi) + { + os << image_type << " " << mi.var << "value;" + << "std::size_t " << mi.var << "size;" + << "bool " << mi.var << "null;" + << endl; + } + }; + entry<image_member> image_member_; + } + } +} diff --git a/odb/odb/relational/sqlite/inline.cxx b/odb/odb/relational/sqlite/inline.cxx new file mode 100644 index 0000000..dd3274f --- /dev/null +++ b/odb/odb/relational/sqlite/inline.cxx @@ -0,0 +1,42 @@ +// file : odb/relational/sqlite/inline.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/inline.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + namespace inline_ + { + namespace relational = relational::inline_; + + struct null_member: relational::null_member_impl<sql_type>, + member_base + { + null_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_simple (member_info& mi) + { + if (get_) + os << "r = r && i." << mi.var << "null;"; + else + os << "i." << mi.var << "null = true;"; + } + }; + entry<null_member> null_member_; + } + } +} diff --git a/odb/odb/relational/sqlite/model.cxx b/odb/odb/relational/sqlite/model.cxx new file mode 100644 index 0000000..da16ded --- /dev/null +++ b/odb/odb/relational/sqlite/model.cxx @@ -0,0 +1,91 @@ +// file : odb/relational/sqlite/model.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#include <odb/relational/model.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + namespace model + { + namespace relational = relational::model; + + struct object_columns: relational::object_columns, context + { + object_columns (base const& x): base (x) {} + + virtual string + type (semantics::data_member& m) + { + // Translate BLOB|TEXT STREAM to just BLOB|TEXT. + // + string r (relational::object_columns::type (m)); + + sql_type const& t (parse_sql_type (r, m, false)); + if (t.stream) + { + switch (t.type) + { + case sql_type::BLOB: r = "BLOB"; break; + case sql_type::TEXT: r = "TEXT"; break; + default: break; + } + } + + return r; + } + + virtual bool + null (semantics::data_member& m) + { + return options.sqlite_override_null () || base::null (m); + } + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to INTEGER. + // + sql_type const& t (parse_sql_type (column_type (), m, false)); + if (t.type != sql_type::INTEGER) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: column with default value specified as C++ " + << "enumerator must map to SQLite INTEGER" << endl; + + throw operation_failed (); + } + + using semantics::enumerator; + + enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); + + ostringstream ostr; + + if (e.enum_ ().unsigned_ ()) + ostr << e.value (); + else + ostr << static_cast<long long> (e.value ()); + + return ostr.str (); + } + + virtual void + primary_key (sema_rel::primary_key& pk) + { + if (pk.auto_ () && options.sqlite_lax_auto_id ()) + pk.extra ()["lax"] = "true"; + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/odb/relational/sqlite/schema.cxx b/odb/odb/relational/sqlite/schema.cxx new file mode 100644 index 0000000..f5549b4 --- /dev/null +++ b/odb/odb/relational/sqlite/schema.cxx @@ -0,0 +1,455 @@ +// file : odb/relational/sqlite/schema.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/schema.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +namespace relational +{ + namespace sqlite + { + namespace schema + { + namespace relational = relational::schema; + + // + // Drop. + // + + struct drop_column: trav_rel::drop_column, relational::common + { + drop_column (relational::common const& c) + : relational::common (c), first_ (true) {} + + virtual void + traverse (sema_rel::drop_column& dc) + { + // SQLite does not support dropping columns. If this column is + // not NULLable, then there is nothing we can do. Otherwise, do + // a logical DROP by setting all the values to NULL. + // + sema_rel::column& c (find<sema_rel::column> (dc)); + + if (!c.null ()) + { + cerr << "error: SQLite does not support dropping of columns" << + endl; + cerr << "info: first dropped column is '" << dc.name () << + "' in table '" << dc.table ().name () << "'" << endl; + cerr << "info: could have performed logical drop if the column " << + "allowed NULL values" << endl; + throw operation_failed (); + } + + if (first_) + first_ = false; + else + os << "," << endl + << " "; + + os << quote_id (dc.name ()) << " = NULL"; + } + + private: + bool first_; + }; + // Not registered as an override. + + struct drop_index: relational::drop_index, context + { + drop_index (base const& x): base (x) {} + + virtual string + name (sema_rel::index& in) + { + // In SQLite, index names can be qualified with the database. + // + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + sema_rel::qname n (t.name ().qualifier ()); + n.append (in.name ()); + return quote_id (n); + } + }; + entry<drop_index> drop_index_; + + struct drop_table: relational::drop_table, context + { + drop_table (base const& x): base (x) {} + + virtual void + traverse (sema_rel::table& t, bool migration) + { + // In SQLite there is no way to drop foreign keys except as part + // of the table. + // + if (pass_ != 2) + return; + + // Polymorphic base cleanup code. Because we cannot drop foreign + // keys, we will trigger cascade deletion. The only way to work + // around this problem is to delete from the root table and rely + // on the cascade to clean up the rest. + // + if (migration && t.extra ()["kind"] == "polymorphic derived object") + { + using sema_rel::model; + using sema_rel::table; + using sema_rel::primary_key; + using sema_rel::foreign_key; + + model& m (dynamic_cast<model&> (t.scope ())); + + table* p (&t); + do + { + // The polymorphic link is the first primary key. + // + for (table::names_iterator i (p->names_begin ()); + i != p->names_end (); ++i) + { + if (foreign_key* fk = dynamic_cast<foreign_key*> ( + &i->nameable ())) + { + p = m.find<table> (fk->referenced_table ()); + assert (p != 0); // Base table should be there. + break; + } + } + } + while (p->extra ()["kind"] != "polymorphic root object"); + + primary_key& rkey (*p->find<primary_key> ("")); + primary_key& dkey (*t.find<primary_key> ("")); + assert (rkey.contains_size () == dkey.contains_size ()); + delete_ (p->name (), t.name (), rkey, dkey); + } + + drop (t, migration); + } + }; + entry<drop_table> 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) + { + using sema_rel::alter_table; + using sema_rel::add_column; + using sema_rel::add_foreign_key; + + alter_table& at (static_cast<alter_table&> (ac.scope ())); + + pre_statement (); + + os << "ALTER TABLE " << quote_id (at.name ()) << endl + << " ADD COLUMN "; + + // In SQLite it is impossible to alter a column later, so unless + // it has a default value, we add it as NULL. Without this, it + // will be impossible to add a column to a table that contains + // some rows. + // + create (ac); + + // SQLite doesn't support adding foreign keys other than inline + // via a column definition. See if we can handle any. + // + add_foreign_key* afk (0); + + for (add_column::contained_iterator i (ac.contained_begin ()); + i != ac.contained_end (); + ++i) + { + if ((afk = dynamic_cast<add_foreign_key*> (&i->key ()))) + { + // Check that it is a single-column foreign key. Also make + // sure the column and foreign key are from the same changeset. + // + if (afk->contains_size () != 1 || &ac.scope () != &afk->scope ()) + afk = 0; + else + break; + } + } + + if (afk != 0) + { + os << " CONSTRAINT " << quote_id (afk->name ()) << " REFERENCES " << + quote_id (afk->referenced_table ().uname ()) << " (" << + quote_id (afk->referenced_columns ()[0]) << ")"; + + bool del (afk->on_delete () != sema_rel::foreign_key::no_action); + bool def (!afk->not_deferrable ()); + + if (del || def) + { + instance<relational::create_foreign_key> cfk (*this); + + if (del) + cfk->on_delete (afk->on_delete ()); + + if (def) + cfk->deferrable (afk->deferrable ()); + } + + afk->set ("sqlite-fk-defined", true); // Mark it as defined. + } + + os << endl; + post_statement (); + } + + virtual void + auto_ (sema_rel::primary_key& pk) + { + if (pk.extra ().count ("lax")) + os << " /*AUTOINCREMENT*/"; + else + os << " AUTOINCREMENT"; + } + }; + entry<create_column> create_column_; + + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (base const& x): base (x) {} + + virtual void + traverse (sema_rel::foreign_key& fk) + { + // In SQLite, all constraints are defined as part of a table. + // + os << "," << endl + << " CONSTRAINT "; + + create (fk); + } + + virtual string + table_name (sema_rel::foreign_key& fk) + { + // In SQLite, the referenced table cannot be qualified with the + // database name (it has to be in the same database anyway). + // + return quote_id (fk.referenced_table ().uname ()); + } + }; + entry<create_foreign_key> 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 SQLite, index names can be qualified with the database. + // + sema_rel::table& t (static_cast<sema_rel::table&> (in.scope ())); + sema_rel::qname n (t.name ().qualifier ()); + n.append (in.name ()); + return quote_id (n); + } + + virtual string + table_name (sema_rel::index& in) + { + // In SQLite, the index table cannot be qualified with the + // database name (it has to be in the same database). + // + return quote_id ( + static_cast<sema_rel::table&> (in.scope ()).name ().uname ()); + } + }; + entry<create_index> create_index_; + + struct create_table: relational::create_table, context + { + create_table (base const& x): base (x) {} + + void + traverse (sema_rel::table& t) + { + // For SQLite we do everything in a single pass since there + // is no way to add constraints later. + // + if (pass_ == 1) + create (t); + } + }; + entry<create_table> create_table_; + + // + // Alter. + // + + 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) + { + // SQLite can only add a single column per ALTER TABLE statement. + // + instance<create_column> cc (*this); + trav_rel::unames n (*cc); + names (at, n); + + // SQLite does not support altering columns. + // + if (sema_rel::alter_column* ac = check<sema_rel::alter_column> (at)) + { + cerr << "error: SQLite does not support altering of columns" + << endl; + cerr << "info: first altered column is '" << ac->name () << + "' in table '" << at.name () << "'" << endl; + throw operation_failed (); + } + + // SQLite does not support dropping constraints. We are going to + // ignore this if the column is NULL'able since in most cases + // the constraint is going to be dropped as a result of the + // column drop (e.g., an object pointer member got deleted). + // If we were not to allow this, then it would be impossible + // to do logical drop for pointer columns. + // + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + using sema_rel::foreign_key; + using sema_rel::drop_foreign_key; + + drop_foreign_key* dfk ( + dynamic_cast<drop_foreign_key*> (&i->nameable ())); + + if (dfk == 0) + continue; + + foreign_key& fk (find<foreign_key> (*dfk)); + + for (foreign_key::contains_iterator j (fk.contains_begin ()); + j != fk.contains_end (); ++j) + { + if (j->column ().null ()) + continue; + + cerr << "error: SQLite does not support dropping of foreign " << + "keys" << endl; + cerr << "info: first dropped foreign key is '" << dfk->name () << + "' in table '" << at.name () << "'" << endl; + cerr << "info: could have ignored it if the contained " << + "column(s) allowed NULL values" << endl; + throw operation_failed (); + } + } + } + }; + entry<alter_table_pre> 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) + { + // SQLite does not support altering columns (we have to do this + // in both alter_table_pre/post because of the + // check_alter_column_null() test in the common code). + // + if (sema_rel::alter_column* ac = check<sema_rel::alter_column> (at)) + { + cerr << "error: SQLite does not support altering of columns" + << endl; + cerr << "info: first altered column is '" << ac->name () << + "' in table '" << at.name () << "'" << endl; + throw operation_failed (); + } + + // Try to do logical column drop. + // + if (check<sema_rel::drop_column> (at)) + { + pre_statement (); + + os << "UPDATE " << quote_id (at.name ()) << endl + << " SET "; + + drop_column dc (*this); + trav_rel::unames n (dc); + names (at, n); + os << endl; + + post_statement (); + } + + // SQLite doesn't support adding foreign keys other than inline + // via a column definition. See if there are any that we couldn't + // handle that way. + // + for (sema_rel::alter_table::names_iterator i (at.names_begin ()); + i != at.names_end (); ++i) + { + sema_rel::add_foreign_key* afk ( + dynamic_cast<sema_rel::add_foreign_key*> (&i->nameable ())); + + if (afk == 0 || afk->count ("sqlite-fk-defined")) + continue; + + cerr << "error: SQLite does not support adding foreign keys" + << endl; + cerr << "info: first added foreign key is '" << afk->name () << + "' in table '" << at.name () << "'" << endl; + throw operation_failed (); + } + } + }; + entry<alter_table_post> alter_table_post_; + + // + // Schema version table. + // + + struct version_table: relational::version_table, context + { + version_table (base const& x): base (x) {} + + virtual void + create_table () + { + pre_statement (); + + os << "CREATE TABLE IF NOT EXISTS " << qt_ << " (" << endl + << " " << qn_ << " TEXT NOT NULL PRIMARY KEY," << endl + << " " << qv_ << " INTEGER NOT NULL," << endl + << " " << qm_ << " INTEGER NOT NULL)" << endl; + + post_statement (); + } + + virtual void + create (sema_rel::version v) + { + pre_statement (); + + os << "INSERT OR IGNORE INTO " << qt_ << " (" << endl + << " " << qn_ << ", " << qv_ << ", " << qm_ << ")" << endl + << " VALUES (" << qs_ << ", " << v << ", 0)" << endl; + + post_statement (); + } + }; + entry<version_table> version_table_; + } + } +} diff --git a/odb/odb/relational/sqlite/source.cxx b/odb/odb/relational/sqlite/source.cxx new file mode 100644 index 0000000..5a4b9d3 --- /dev/null +++ b/odb/odb/relational/sqlite/source.cxx @@ -0,0 +1,471 @@ +// file : odb/relational/sqlite/source.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/relational/source.hxx> + +#include <odb/relational/sqlite/common.hxx> +#include <odb/relational/sqlite/context.hxx> + +using namespace std; + +namespace relational +{ + namespace sqlite + { + namespace source + { + namespace relational = relational::source; + + struct query_parameters: relational::query_parameters, context + { + query_parameters (base const& x): base (x) {} + + virtual string + next (semantics::data_member& m, + const string& column, + const string& sqlt) + { + // Handle stream columns. Specifically, we somehow need to + // pass the column name to the code that runs in the + // statement. So what we are going to do is encode it + // in the parameter name. + // + if (sk_ == statement_insert || sk_ == statement_update) + { + const sql_type& t (parse_sql_type (sqlt, m, false)); + if (t.stream) + { + // The column name is quoted. + // + string r (column); + r[0] = '$'; // Replace leading '"'. + r.resize (r.size () - 1); // Remove trailing '"'. + + // Verify it only contains allowed characters. + // + for (size_t i (1); i != r.size (); ++i) + { + char c (r[i]); + if (c != '_' && + (c < '0' || c > '9') && + (c < 'a' || c > 'z') && + (c < 'A' || c > 'Z')) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: unsupported character '" << c << "' in " + << sqlt << " column name " << column << endl; + + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": info: STREAM column can contain alpha-numeric " + << "characters plus '_'" << endl; + + throw operation_failed (); + } + } + + // For TEXT columns, since we use the *_bind_zeroblob() + // function (there is no *_bind_zerotext()), the value + // that will be stored is BLOB, not TEXT, unless we + // explicitly CAST it. The user better make sure the + // encoding of raw TEXT data they are going to write + // matches the database encoding. + // + if (t.type == sql_type::TEXT) + r = "CAST(" + r + " AS TEXT)"; + + return r; + } + } + + return "?"; + } + }; + entry<query_parameters> query_parameters_; + + // + // bind + // + + struct bind_member: relational::bind_member_impl<sql_type>, + member_base + { + bind_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + traverse_integer (member_info& mi) + { + os << b << ".type = sqlite::bind::integer;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_real (member_info& mi) + { + os << b << ".type = sqlite::bind::real;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_text (member_info& mi) + { + os << b << ".type = sqlite::image_traits<" << endl + << " " << mi.fq_type () << "," << endl + << " sqlite::id_text>::bind_value;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_blob (member_info& mi) + { + os << b << ".type = sqlite::bind::blob;" + << b << ".buffer = " << arg << "." << mi.var << "value.data ();" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".capacity = " << arg << "." << mi.var << + "value.capacity ();" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + + virtual void + traverse_stream (member_info& mi) + { + os << b << ".type = sqlite::bind::stream;" + << b << ".buffer = &" << arg << "." << mi.var << "value;" + << b << ".size = &" << arg << "." << mi.var << "size;" + << b << ".is_null = &" << arg << "." << mi.var << "null;"; + } + }; + entry<bind_member> bind_member_; + + // + // grow + // + + struct grow_member: relational::grow_member_impl<sql_type>, + member_base + { + grow_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) {} + + virtual void + traverse_integer (member_info&) + { + os << e << " = false;" + << endl; + } + + virtual void + traverse_real (member_info&) + { + os << e << " = false;" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << "if (" << e << ")" << endl + << "{" + << "i." << mi.var << "value.capacity (i." << mi.var << "size);" + << "grew = true;" + << "}"; + } + + virtual void + traverse_stream (member_info&) + { + os << e << " = false;" + << endl; + } + }; + entry<grow_member> grow_member_; + + // + // init image + // + + struct init_image_member: relational::init_image_member_impl<sql_type>, + member_base + { + init_image_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + set_null (member_info& mi) + { + os << "i." << mi.var << "null = true;"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_real (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;"; + } + + virtual void + traverse_string (member_info& mi) + { + os << "std::size_t cap (i." << mi.var << "value.capacity ());" + << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;" + << "grew = grew || (cap != i." << mi.var << "value.capacity ());"; + } + + virtual void + traverse_stream (member_info& mi) + { + os << traits << "::set_image (" << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "is_null," << endl + << member << ");" + << "i." << mi.var << "null = is_null;"; + } + }; + entry<init_image_member> init_image_member_; + + // + // init value + // + + struct init_value_member: relational::init_value_member_impl<sql_type>, + member_base + { + init_value_member (base const& x) + : member_base::base (x), // virtual base + member_base::base_impl (x), // virtual base + base_impl (x), + member_base (x) + { + } + + virtual void + get_null (string const& var) const + { + os << "i." << var << "null"; + } + + virtual void + traverse_integer (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_real (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_string (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + + virtual void + traverse_stream (member_info& mi) + { + os << traits << "::set_value (" << endl + << member << "," << endl + << "i." << mi.var << "value," << endl + << "i." << mi.var << "size," << endl + << "i." << mi.var << "null);" + << endl; + } + }; + entry<init_value_member> init_value_member_; + + struct statement_columns_common: context + { + void + process (relational::statement_columns& cs, statement_kind sk) + { + using relational::statement_columns; + + // For SELECT statements, add _ROWID_ "follow-up" column to + // each stream column. The reason we need both, and not just + // ROWID is the NULL value. Let's hope that SELECT'ing a BLOB + // but not actually reading it with sqlite3_result_blob() is + // as fast as not SELECT'ing it. + // + if (sk != statement_select) + return; + + for (statement_columns::iterator i (cs.begin ()); + i != cs.end (); ++i) + { + if (parse_sql_type (i->type, *i->member).stream) + { + // Column is already table-qualified and quoted. Do some + // surgery to replace it with _ROWID_. That is, we want to + // transform "table"."column" to "table"."_ROWID_". + // + string c (i->column); + string::size_type n (c.size ()), p (c.rfind ('"', n - 2)); + assert (p != string::npos); + string as (c, p + 1, n - p - 2); + c.resize (p); + c += "\"_ROWID_\""; + + // We are going to pack this "tightly", without any newlines, + // so that the statement processing code treats them as a + // single column. + // + i->column += ','; + i->column += c; + } + } + } + }; + + struct container_traits: relational::container_traits, + statement_columns_common + { + container_traits (base const& x): base (x) {} + + virtual void + cache_result (string const&) + { + // Caching is not necessary since SQLite can execute several + // interleaving statements. + // + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool) + { + statement_columns_common::process (cols, sk); + } + }; + entry<container_traits> container_traits_; + + struct section_traits: relational::section_traits, + statement_columns_common + { + section_traits (base const& x): base (x) {} + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool) + { + statement_columns_common::process (cols, sk); + } + }; + entry<section_traits> section_traits_; + + struct class_: relational::class_, statement_columns_common + { + class_ (base const& x): base (x) {} + + virtual void + init_auto_id (semantics::data_member& m, string const& im) + { + // Don't set the id value to NULL if this is a nullable wrapper. + // This will allow the user to control whether the value is auto or + // manually assigned by using something like this: + // + // #pragma db auto + // odb::nullable<int64_t> id; + // + semantics::type& t (utype (m)); + if (wrapper (t) && t.template get<bool> ("wrapper-null-handler")) + return; + + os << im << "null = true;" + << endl; + } + + virtual string + select_trailer (type&) + { + // SQLite has not support for FOR UPDATE and since this is an + // optimization, we simply ignore it. + // + return ""; + } + + virtual string + join_syntax (view_object const& vo) + { + const char* n (0); + + if (vo.join == view_object::full) + n = "FULL OUTER JOIN"; + else if (vo.join == view_object::right) + n = "RIGHT OUTER JOIN"; + + if (n != 0) + { + error (vo.loc) << n << " is not supported by SQLite" << endl; + throw operation_failed (); + } + + return base::join_syntax (vo); + } + + virtual void + process_statement_columns (relational::statement_columns& cols, + statement_kind sk, + bool) + { + statement_columns_common::process (cols, sk); + } + }; + entry<class_> class_entry_; + } + } +} diff --git a/odb/odb/relational/validator.cxx b/odb/odb/relational/validator.cxx new file mode 100644 index 0000000..50c887e --- /dev/null +++ b/odb/odb/relational/validator.cxx @@ -0,0 +1,638 @@ +// file : odb/relational/validator.cxx +// license : GNU GPL v3; see accompanying LICENSE file + +#include <map> +#include <iostream> + +#include <odb/diagnostics.hxx> +#include <odb/traversal.hxx> +#include <odb/relational/common.hxx> +#include <odb/relational/context.hxx> +#include <odb/relational/validator.hxx> + +using namespace std; + +namespace relational +{ + namespace + { + // + // Pass 2. + // + + struct data_member2: traversal::data_member, context + { + data_member2 (bool& valid) + : valid_ (valid) + { + } + + virtual void + traverse (type& m) + { + if (transient (m)) + return; + + if (null (m)) + { + if (semantics::class_* c = composite_wrapper (utype (m))) + { + if (has_a (*c, test_container)) + { + os << m.file () << ":" << m.line () << ":" << m.column () << ":" + << " error: composite member containing containers cannot " + << "be null" << endl; + + os << c->file () << ":" << c->line () << ":" << c->column () + << ": info: composite value type is defined here" << endl; + + valid_ = false; + } + } + } + + // Check on-delete. + // + if (m.count ("on-delete")) + { + const char* kp (container (m) ? "value" : ""); + location l (m.location ()); + + // Make sure it is a pointer or a member with points_to pragma. + // + if (!object_pointer (utype (m, kp)) && !points_to (m)) + { + error (l) << "on_delete specified for non-object pointer" << endl; + valid_ = false; + } + + // Make sure it is not inverse. + // + if (inverse (m, kp)) + { + error (l) << "on_delete specified for inverse object " << + "pointer" << endl; + valid_ = false; + } + + // Make sure the pointer is nullable if asked to set it to NULL. + // + using sema_rel::foreign_key; + + if (m.get<foreign_key::action_type> ("on-delete") == + foreign_key::set_null && + !null (m, kp)) + { + error (l) << "set_null specified for non-nullable object " + "pointer" << endl; + valid_ = false; + } + } + } + + bool& valid_; + }; + + struct object_no_id_members: object_members_base + { + object_no_id_members (bool& valid) + : object_members_base (false, false, true), valid_ (valid), dm_ (0) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_&) + { + if (inverse (m)) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: inverse object pointer member '" << member_prefix_ + << m.name () << "' in an object without an object id" << endl; + + valid_ = false; + } + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: container member '" << member_prefix_ << m.name () + << "' in an object without an object id" << endl; + + valid_ = false; + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + semantics::data_member* old_dm (dm_); + + if (dm_ == 0) + dm_ = m; + + object_members_base::traverse_composite (m, c); + + dm_ = old_dm; + } + + private: + bool& valid_; + semantics::data_member* dm_; // Direct object data member. + }; + + struct composite_id_members: object_members_base + { + composite_id_members (bool& valid) + : object_members_base (false, false, true), valid_ (valid), dm_ (0) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_&) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: object pointer member '" << member_prefix_ << m.name () + << "' in a composite value type that is used as an object id" + << endl; + + valid_ = false; + } + + virtual void + traverse_simple (semantics::data_member& m) + { + if (readonly (member_path_, member_scope_)) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: readonly member '" << member_prefix_ << m.name () + << "' in a composite value type that is used as an object id" + << endl; + + valid_ = false; + } + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: container member '" << member_prefix_ << m.name () + << "' in a composite value type that is used as an object id" + << endl; + + valid_ = false; + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + semantics::data_member* old_dm (dm_); + + if (dm_ == 0) + dm_ = m; + + object_members_base::traverse_composite (m, c); + + dm_ = old_dm; + } + + private: + bool& valid_; + semantics::data_member* dm_; // Direct composite member. + }; + + struct view_members: object_members_base + { + view_members (bool& valid) + : object_members_base (false, false, true), valid_ (valid), dm_ (0) + { + } + + virtual void + traverse_pointer (semantics::data_member& m, semantics::class_&) + { + if (dm_ != 0 && object_pointer (utype (m))) + { + location const& l (dm_->location ()); + + error (l) << "nested view data member '" << member_prefix_ + << m.name () << "' is an object pointer" << endl; + info (l) << "views can only contain direct object pointer members" + << endl; + + valid_ = false; + } + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type&) + { + semantics::data_member& dm (dm_ != 0 ? *dm_ : m); + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << " error: view data member '" << member_prefix_ << m.name () + << "' is a container" << endl; + + os << dm.file () << ":" << dm.line () << ":" << dm.column () << ":" + << ": info: views cannot contain containers" << endl; + + valid_ = false; + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + semantics::data_member* old_dm (dm_); + + if (dm_ == 0) + dm_ = m; + + object_members_base::traverse_composite (m, c); + + dm_ = old_dm; + } + + private: + bool& valid_; + semantics::data_member* dm_; // Direct view data member. + }; + + struct class2: traversal::class_, context + { + class2 (bool& valid) + : valid_ (valid), + typedefs_ (true), + data_member_ (valid), + object_no_id_members_ (valid), + composite_id_members_ (valid), + view_members_ (valid) + { + *this >> defines_ >> *this; + *this >> typedefs_ >> *this; + + data_member_names_ >> data_member_; + } + + virtual void + traverse (type& c) + { + class_kind_type ck (class_kind (c)); + switch (ck) + { + case class_object: + names (c); + traverse_object (c); + break; + case class_view: + names (c); + traverse_view (c); + break; + case class_composite: + names (c); + traverse_composite (c); + break; + case class_other: + break; + } + + // Make sure indexes are not defined for anything other than objects. + // + if (c.count ("index") && ck != class_object) + { + indexes& ins (c.get<indexes> ("index")); + + for (indexes::iterator i (ins.begin ()); i != ins.end (); ++i) + { + error (i->loc) << "index definition on a non-persistent class" + << endl; + valid_ = false; + } + } + } + + virtual void + traverse_object (type& c) + { + data_member_path* id (id_member (c)); + + if (id != 0) + { + if (semantics::class_* cm = composite_wrapper (utype (*id))) + { + location idl (id->front ()->location ()); + + // Composite id cannot be auto. + // + if (auto_ (*id)) + { + error (idl) << "composite id cannot be automatically assigned" + << endl; + valid_ = false; + } + + // Make sure we don't have any containers or pointers in this + // composite value type. + // + if (valid_) + { + composite_id_members_.traverse (*cm); + + if (!valid_) + info (idl) << "composite id is defined here" << endl; + } + + // Check that the composite value type is default-constructible. + // + if (!cm->default_ctor ()) + { + os << cm->file () << ":" << cm->line () << ":" << cm->column () + << ": error: composite value type that is used as object id " + << "is not default-constructible" << endl; + + os << cm->file () << ":" << cm->line () << ":" << cm->column () + << ": info: provide default constructor for this value type" + << endl; + + info (idl) << "composite id is defined here" << endl; + + valid_ = false; + } + } + } + else + { + if (!abstract (c)) + { + // Make sure we don't have any containers or inverse pointers. + // + object_no_id_members_.traverse (c); + } + } + + names (c, data_member_names_); + + // Validate bulk operation support. + // + for (bool i (true); i && c.count ("bulk"); i = false) + { + location_t l (c.get<location_t> ("bulk-location")); + + if (polymorphic (c)) + { + error (l) << "bulk operations on polymorphic objects are " + "not supported" << endl; + valid_ = false; + break; + } + + if (has_a (c, test_straight_container)) + { + error (l) << "bulk operations on objects with containers are " + "not supported" << endl; + valid_ = false; + break; + } + + bool update (true); + + // Unless we only have manually-updated sections, we cannot generate + // the bulk update operation. + // + user_sections& uss (c.get<user_sections> ("user-sections")); + + for (user_sections::iterator i (uss.begin ()); + update && i != uss.end (); + ++i) + { + const user_section& s (*i); + + // Skip special sections. + // + if (s.special != user_section::special_ordinary) + continue; + + // Always-updated section still needs a separate statement + // (since it may not be loaded). + // + if (!s.update_empty () && s.update != user_section::update_manual) + update = false; + } + + c.set ("bulk-persist", true); + if (update) c.set ("bulk-update", true); + c.set ("bulk-erase", true); + } + + // Validate indexes. + // + { + indexes& ins (c.get<indexes> ("index")); + + // Make sure index members are not transient, inverse, or + // containers. + // + for (indexes::iterator i (ins.begin ()); i != ins.end (); ++i) + { + index& in (*i); + + for (index::members_type::iterator i (in.members.begin ()); + i != in.members.end (); ++i) + { + index::member& im (*i); + semantics::data_member& m (*im.path.back ()); + + if (transient (m)) + { + error (im.loc) << "index member is transient" << endl; + valid_ = false; + } + + if (inverse (m)) + { + error (im.loc) << "index member is an inverse object " << + "pointer" << endl; + valid_ = false; + } + + if (container (m)) + { + error (im.loc) << "index member is a container" << endl; + valid_ = false; + } + } + } + } + } + + virtual void + traverse_view (type& c) + { + const view_query& vq (c.get<view_query> ("query")); + + // Make sure we don't have any containers or object pointers. + // + view_members_.traverse (c); + + names (c, data_member_names_); + + // Allow certain kinds of empty views. + // + if (vq.kind != view_query::runtime && + vq.kind != view_query::complete_execute) + { + // Allow all the members to be deleted. + // + column_count_type const& cc (column_count (c)); + + if (cc.total == 0) + { + os << c.file () << ":" << c.line () << ":" << c.column () << ":" + << " error: no persistent data members in the class" << endl; + valid_ = false; + } + } + } + + virtual void + traverse_composite (type& c) + { + names (c, data_member_names_); + } + + public: + bool& valid_; + + traversal::defines defines_; + typedefs typedefs_; + + data_member2 data_member_; + traversal::names data_member_names_; + + object_no_id_members object_no_id_members_; + composite_id_members composite_id_members_; + view_members view_members_; + }; + } + + void + validate (options const&, + features&, + semantics::unit& u, + semantics::path const&, + unsigned short pass) + { + bool valid (true); + + // Validate custom type mapping. + // + if (pass == 1) + { + // Create an empty list if we don't have one. This makes the + // rest of the code simpler. + // + if (!u.count ("custom-db-types")) + u.set ("custom-db-types", custom_db_types ()); + + custom_db_types & cts (u.get<custom_db_types> ("custom-db-types")); + + for (custom_db_types::iterator i (cts.begin ()); i != cts.end (); ++i) + { + custom_db_type& ct (*i); + + if (ct.type.empty ()) + { + error (ct.loc) << "'type' clause expected in db pragma map" << endl; + valid = false; + } + + if (ct.as.empty ()) + { + error (ct.loc) << "'as' clause expected in db pragma map" << endl; + valid = false; + } + + if (ct.to.empty ()) + ct.to = "(?)"; + else + { + size_t p (ct.to.find ("(?)")); + + if (p == string::npos) + { + error (ct.loc) << "no '(?)' expression in the 'to' clause " + << "of db pragma map" << endl; + valid = false; + } + else if (ct.to.find ("(?)", p + 3) != string::npos) + { + error (ct.loc) << "multiple '(?)' expressions in the 'to' " + << "clause of db pragma map" << endl; + valid = false; + } + } + + if (ct.from.empty ()) + ct.from = "(?)"; + else + { + size_t p (ct.from.find ("(?)")); + + if (p == string::npos) + { + error (ct.loc) << "no '(?)' expression in the 'from' clause " + << "of db pragma map" << endl; + valid = false; + } + else if (ct.from.find ("(?)", p + 3) != string::npos) + { + error (ct.loc) << "multiple '(?)' expressions in the 'from' " + << "clause of db pragma map" << endl; + valid = false; + } + } + } + } + + if (!valid) + throw operation_failed (); + + if (pass == 1) + { + } + else + { + traversal::unit unit; + traversal::defines unit_defines; + typedefs unit_typedefs (true); + traversal::namespace_ ns; + class2 c (valid); + + unit >> unit_defines >> ns; + unit_defines >> c; + unit >> unit_typedefs >> c; + + traversal::defines ns_defines; + typedefs ns_typedefs (true); + + ns >> ns_defines >> ns; + ns_defines >> c; + ns >> ns_typedefs >> c; + + unit.dispatch (u); + } + + if (!valid) + throw operation_failed (); + } +} diff --git a/odb/odb/relational/validator.hxx b/odb/odb/relational/validator.hxx new file mode 100644 index 0000000..d6602f7 --- /dev/null +++ b/odb/odb/relational/validator.hxx @@ -0,0 +1,24 @@ +// file : odb/relational/validator.hxx +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_VALIDATOR_HXX +#define ODB_RELATIONAL_VALIDATOR_HXX + +#include <odb/options.hxx> +#include <odb/features.hxx> +#include <odb/semantics/unit.hxx> + +namespace relational +{ + // The first pass is performed before processing. The second -- after. + // Throws operation_failed to signal a failure. + // + void + validate (options const&, + features&, + semantics::unit&, + semantics::path const&, + unsigned short pass); +} + +#endif // ODB_RELATIONAL_VALIDATOR_HXX |