summaryrefslogtreecommitdiff
path: root/odb/odb/relational
diff options
context:
space:
mode:
Diffstat (limited to 'odb/odb/relational')
-rw-r--r--odb/odb/relational/changelog.cxx1239
-rw-r--r--odb/odb/relational/common-query.cxx169
-rw-r--r--odb/odb/relational/common-query.hxx63
-rw-r--r--odb/odb/relational/common.cxx27
-rw-r--r--odb/odb/relational/common.hxx273
-rw-r--r--odb/odb/relational/common.txx125
-rw-r--r--odb/odb/relational/context.cxx169
-rw-r--r--odb/odb/relational/context.hxx289
-rw-r--r--odb/odb/relational/context.ixx44
-rw-r--r--odb/odb/relational/generate.hxx81
-rw-r--r--odb/odb/relational/header.cxx1146
-rw-r--r--odb/odb/relational/header.hxx1466
-rw-r--r--odb/odb/relational/inline.cxx47
-rw-r--r--odb/odb/relational/inline.hxx693
-rw-r--r--odb/odb/relational/model.cxx121
-rw-r--r--odb/odb/relational/model.hxx868
-rw-r--r--odb/odb/relational/mssql/common.cxx603
-rw-r--r--odb/odb/relational/mssql/common.hxx293
-rw-r--r--odb/odb/relational/mssql/context.cxx766
-rw-r--r--odb/odb/relational/mssql/context.hxx194
-rw-r--r--odb/odb/relational/mssql/header.cxx312
-rw-r--r--odb/odb/relational/mssql/inline.cxx42
-rw-r--r--odb/odb/relational/mssql/model.cxx66
-rw-r--r--odb/odb/relational/mssql/schema.cxx651
-rw-r--r--odb/odb/relational/mssql/source.cxx1201
-rw-r--r--odb/odb/relational/mysql/common.cxx400
-rw-r--r--odb/odb/relational/mysql/common.hxx171
-rw-r--r--odb/odb/relational/mysql/context.cxx868
-rw-r--r--odb/odb/relational/mysql/context.hxx194
-rw-r--r--odb/odb/relational/mysql/header.cxx136
-rw-r--r--odb/odb/relational/mysql/inline.cxx42
-rw-r--r--odb/odb/relational/mysql/model.cxx161
-rw-r--r--odb/odb/relational/mysql/schema.cxx489
-rw-r--r--odb/odb/relational/mysql/source.cxx724
-rw-r--r--odb/odb/relational/oracle/common.cxx522
-rw-r--r--odb/odb/relational/oracle/common.hxx203
-rw-r--r--odb/odb/relational/oracle/context.cxx795
-rw-r--r--odb/odb/relational/oracle/context.hxx188
-rw-r--r--odb/odb/relational/oracle/header.cxx230
-rw-r--r--odb/odb/relational/oracle/inline.cxx42
-rw-r--r--odb/odb/relational/oracle/model.cxx64
-rw-r--r--odb/odb/relational/oracle/schema.cxx696
-rw-r--r--odb/odb/relational/oracle/source.cxx646
-rw-r--r--odb/odb/relational/pgsql/common.cxx351
-rw-r--r--odb/odb/relational/pgsql/common.hxx159
-rw-r--r--odb/odb/relational/pgsql/context.cxx786
-rw-r--r--odb/odb/relational/pgsql/context.hxx192
-rw-r--r--odb/odb/relational/pgsql/header.cxx285
-rw-r--r--odb/odb/relational/pgsql/inline.cxx42
-rw-r--r--odb/odb/relational/pgsql/model.cxx101
-rw-r--r--odb/odb/relational/pgsql/schema.cxx266
-rw-r--r--odb/odb/relational/pgsql/source.cxx1140
-rw-r--r--odb/odb/relational/processor.cxx1564
-rw-r--r--odb/odb/relational/processor.hxx15
-rw-r--r--odb/odb/relational/schema-source.cxx281
-rw-r--r--odb/odb/relational/schema-source.hxx126
-rw-r--r--odb/odb/relational/schema.cxx174
-rw-r--r--odb/odb/relational/schema.hxx1606
-rw-r--r--odb/odb/relational/source.cxx6345
-rw-r--r--odb/odb/relational/source.hxx7158
-rw-r--r--odb/odb/relational/sqlite/common.cxx217
-rw-r--r--odb/odb/relational/sqlite/common.hxx147
-rw-r--r--odb/odb/relational/sqlite/context.cxx490
-rw-r--r--odb/odb/relational/sqlite/context.hxx146
-rw-r--r--odb/odb/relational/sqlite/header.cxx63
-rw-r--r--odb/odb/relational/sqlite/inline.cxx42
-rw-r--r--odb/odb/relational/sqlite/model.cxx91
-rw-r--r--odb/odb/relational/sqlite/schema.cxx455
-rw-r--r--odb/odb/relational/sqlite/source.cxx471
-rw-r--r--odb/odb/relational/validator.cxx638
-rw-r--r--odb/odb/relational/validator.hxx24
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 << "&section_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 = &lt;
+ }
+ 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 = &lt;
+ }
+ 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 = &lt;
+ }
+ }
+ 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 = &lt;
+ }
+ }
+
+ 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