diff options
Diffstat (limited to 'odb/relational')
25 files changed, 2065 insertions, 1227 deletions
diff --git a/odb/relational/common.hxx b/odb/relational/common.hxx index 92edba6..1627a74 100644 --- a/odb/relational/common.hxx +++ b/odb/relational/common.hxx @@ -318,12 +318,28 @@ namespace relational } template <typename T> + inline traversal::relational::edge_base& + operator>> (instance<T>& n, traversal::relational::edge_base& e) + { + n->edge_traverser (e); + return e; + } + + template <typename T> inline traversal::node_base& operator>> (traversal::edge_base& e, instance<T>& n) { e.node_traverser (*n); return *n; } + + template <typename T> + inline traversal::relational::node_base& + operator>> (traversal::relational::edge_base& e, instance<T>& n) + { + e.node_traverser (*n); + return *n; + } } #endif // ODB_RELATIONAL_COMMON_HXX diff --git a/odb/relational/context.cxx b/odb/relational/context.cxx index 363b3bf..f080563 100644 --- a/odb/relational/context.cxx +++ b/odb/relational/context.cxx @@ -23,18 +23,18 @@ namespace relational context:: context () : data_ (current ().data_), - generate_grow (data_->generate_grow_), - need_alias_as (data_->need_alias_as_), + model (current ().model), + generate_grow (current ().generate_grow), + need_alias_as (current ().need_alias_as), bind_vector (data_->bind_vector_), truncated_vector (data_->truncated_vector_) { } context:: - context (data* d) + context (data* d, sema_rel::model* m) : data_ (d), - generate_grow (data_->generate_grow_), - need_alias_as (data_->need_alias_as_), + model (m), bind_vector (data_->bind_vector_), truncated_vector (data_->truncated_vector_) { diff --git a/odb/relational/context.hxx b/odb/relational/context.hxx index cd2bce5..96e8954 100644 --- a/odb/relational/context.hxx +++ b/odb/relational/context.hxx @@ -8,8 +8,14 @@ #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, @@ -129,7 +135,7 @@ namespace relational struct data; typedef context base_context; - context (data*); + context (data*, sema_rel::model*); private: static context* current_; @@ -139,15 +145,14 @@ namespace relational { data (std::ostream& os): root_context::data (os) {} - bool generate_grow_; - bool need_alias_as_; - string bind_vector_; string truncated_vector_; }; data* data_; public: + sema_rel::model* model; + bool generate_grow; bool need_alias_as; diff --git a/odb/relational/generate.hxx b/odb/relational/generate.hxx index c7c7837..bf4b4aa 100644 --- a/odb/relational/generate.hxx +++ b/odb/relational/generate.hxx @@ -6,6 +6,10 @@ #ifndef ODB_RELATIONAL_GENERATE_HXX #define ODB_RELATIONAL_GENERATE_HXX +#include <cutl/shared-ptr.hxx> + +#include <odb/semantics/relational/model.hxx> + namespace relational { namespace header @@ -26,6 +30,12 @@ namespace relational generate (); } + namespace model + { + cutl::shared_ptr<semantics::relational::model> + generate (); + } + namespace schema { void diff --git a/odb/relational/model.cxx b/odb/relational/model.cxx new file mode 100644 index 0000000..e44ffec --- /dev/null +++ b/odb/relational/model.cxx @@ -0,0 +1,168 @@ +// file : odb/relational/model.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#include <odb/gcc.hxx> + +#include <cassert> +#include <limits> +#include <sstream> + +#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: + { + // No default value. + return ""; + } + case default_value::null: + { + return default_null (m); + break; + } + case default_value::boolean: + { + return default_bool (m, dv->value == "true"); + break; + } + case default_value::number: + { + tree n (dv->node); + + switch (TREE_CODE (n)) + { + case INTEGER_CST: + { + HOST_WIDE_INT hwl (TREE_INT_CST_LOW (n)); + HOST_WIDE_INT hwh (TREE_INT_CST_HIGH (n)); + + unsigned long long l (hwl); + unsigned long long h (hwh); + unsigned short width (HOST_BITS_PER_WIDE_INT); + + unsigned long long v ((h << width) + l); + + return default_integer (m, v, dv->value == "-"); + break; + } + case REAL_CST: + { + double v; + + REAL_VALUE_TYPE d (TREE_REAL_CST (n)); + + if (REAL_VALUE_ISINF (d)) + v = numeric_limits<double>::infinity (); + else if (REAL_VALUE_ISNAN (d)) + v = numeric_limits<double>::quiet_NaN (); + else + { + char tmp[256]; + real_to_decimal (tmp, &d, sizeof (tmp), 0, true); + istringstream is (tmp); + is >> v; + } + + if (dv->value == "-") + v = -v; + + return default_float (m, v); + break; + } + default: + assert (false); + } + break; + } + case default_value::string: + { + return default_string (m, dv->value); + break; + } + case default_value::enumerator: + { + return default_enum (m, dv->node, dv->value); + break; + } + } + + return ""; + } + + cutl::shared_ptr<sema_rel::model> + generate () + { + context ctx; + cutl::shared_ptr<sema_rel::model> m (new (shared) sema_rel::model); + + traversal::unit unit; + traversal::defines unit_defines; + traversal::namespace_ ns; + instance<class_> c (*m); + + unit >> unit_defines >> ns; + unit_defines >> c; + + traversal::defines ns_defines; + + ns >> ns_defines >> ns; + ns_defines >> c; + + try + { + unit.dispatch (ctx.unit); + } + catch (sema_rel::duplicate_name const& e) + { + semantics::node& n (*e.nameable.get<semantics::node*> ("cxx-node")); + semantics::node& d (*e.duplicate.get<semantics::node*> ("cxx-node")); + + cerr << d.file () << ":" << d.line () << ":" << d.column () + << ": error: " << e.duplicate.kind () << " name '" + << e.nameable.name () << "' conflicts with an already defined " + << e.nameable.kind () << " name" + << endl; + + cerr << n.file () << ":" << n.line () << ":" << n.column () + << ": info: conflicting " << e.nameable.kind () << " is " + << "defined here" + << endl; + + cerr << d.file () << ":" << d.line () << ":" << d.column () + << ": error: use '#pragma db column' or '#pragma db table' " + << "to change one of the names" + << endl; + + throw operation_failed (); + } + + return m; + } + } +} diff --git a/odb/relational/model.hxx b/odb/relational/model.hxx new file mode 100644 index 0000000..3d8cc4b --- /dev/null +++ b/odb/relational/model.hxx @@ -0,0 +1,528 @@ +// file : odb/relational/model.hxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#ifndef ODB_RELATIONAL_MODEL_HXX +#define ODB_RELATIONAL_MODEL_HXX + +#include <set> +#include <cassert> +#include <sstream> + +#include <odb/emitter.hxx> + +#include <odb/semantics/relational.hxx> + +#include <odb/relational/common.hxx> +#include <odb/relational/context.hxx> + +namespace relational +{ + namespace model + { + typedef std::set<std::string> tables; + + struct object_columns: object_columns_base, virtual context + { + typedef object_columns base; + + object_columns (sema_rel::model& model, + sema_rel::table& table, + string const& prefix = string ()) + : model_ (model), + table_ (table), + prefix_ (prefix), + id_override_ (false) + { + } + + virtual void + traverse_object (semantics::class_& c) + { + if (context::top_object != &c) + { + // We are in one of the bases. Set the id_prefix to its + // (unqualified) name. + // + string t (id_prefix_); + id_prefix_ = c.name () + "::"; + object_columns_base::traverse_object (c); + id_prefix_ = t; + } + else + object_columns_base::traverse_object (c); + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + string t (id_prefix_); + + if (m != 0) + // Member of a composite type. Add the data member to id_prefix. + // + if (!id_override_) + id_prefix_ += m->name () + "."; + else + id_override_ = false; + else + // Composite base. Add its unqualified name to id_prefix. + // + id_prefix_ += c.name () + "::"; + + object_columns_base::traverse_composite (m, c); + + id_prefix_ = t; + } + + virtual void + traverse (semantics::data_member& m, + semantics::class_& c, + std::string const& kp, + std::string const& dn) + { + // This overrides the member name for a composite container value + // or key. + // + if (!kp.empty ()) + { + id_prefix_ = kp + "."; + id_override_ = true; + } + + object_columns_base::traverse (m, c, kp, dn); + } + + using object_columns_base::traverse; + + virtual bool + traverse_column (semantics::data_member& m, string const& name, bool) + { + // Ignore inverse object pointers. + // + if (inverse (m)) + return false; + + string id (id_prefix_ + (prefix_.empty () ? m.name () : prefix_)); + + sema_rel::column& c ( + model_.new_node<sema_rel::column> ( + id, column_type (m, prefix_), context::null (m, prefix_))); + c.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::names> (table_, c, name); + + // An id member cannot have a default value. + // + if (!context::id (m)) + { + string const& d (default_ (m)); + + if (!d.empty ()) + c.default_ (d); + } + + // If we have options, add them. + // + string const& o (column_options (m, prefix_)); + + if (!o.empty ()) + c.options (o); + + constraints (m, name, id, c); + reference (m, name, id, c); + + return true; + } + + virtual string + default_null (semantics::data_member&) + { + return "NULL"; + } + + virtual string + default_bool (semantics::data_member&, bool v) + { + // Most databases do not support boolean literals. Those that + // do should override this. + // + return (v ? "1" : "0"); + } + + virtual string + default_integer (semantics::data_member&, unsigned long long v, bool neg) + { + std::ostringstream ostr; + ostr << (neg ? "-" : "") << v; + return ostr.str (); + } + + virtual string + default_float (semantics::data_member&, double v) + { + std::ostringstream ostr; + ostr << v; + return ostr.str (); + } + + virtual string + default_string (semantics::data_member&, string const& v) + { + return quote_string (v); + } + + virtual string + default_enum (semantics::data_member&, + tree /*enumerator*/, + string const& /*name*/) + { + // Has to be implemented by the database-specific override. + // + assert (false); + } + + virtual void + constraints (semantics::data_member& m, + string const& /* name */, + string const& /* id */, + sema_rel::column& c) + { + if (!id (m)) + return; + + sema_rel::primary_key& pk ( + model_.new_node<sema_rel::primary_key> (m.count ("auto"))); + pk.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::contains> (pk, c); + + // In most databases the primary key constraint can be manipulated + // without an explicit name. So we use the special empty name for + // primary keys in order not to clash with columns and other + // constraints. If the target database does not support unnamed + // primary key manipulation, then the database-specific code will + // have to come up with a suitable name. + // + model_.new_edge<sema_rel::names> (table_, pk, ""); + } + + virtual void + reference (semantics::data_member& m, + string const& name, + string const& id, + sema_rel::column& c) + { + semantics::class_* p (object_pointer (member_utype (m, prefix_))); + + if (p == 0) + return; + + sema_rel::foreign_key& fk ( + model_.new_node<sema_rel::foreign_key> ( + id, + table_name (*p), + true)); // deferred + + fk.set ("cxx-node", static_cast<semantics::node*> (&m)); + + fk.referenced_columns ().push_back (column_name (*id_member (*p))); + + model_.new_edge<sema_rel::contains> (fk, c); + + // Derive the constraint name. Generally, we want it to be based + // on the column name. This is straightforward for single column + // references. In case of the composite ids, we will need to use + // the column prefix which is based on the data member name, + // unless overridden (see how the column pragma works for members + // of composite value types). @@ This is a TODO. Perhaps use the + // up-to-and-including composite member prefix? Though it can be + // empty. + // + model_.new_edge<sema_rel::names> (table_, fk, name + "_fk"); + } + + protected: + string + default_ (semantics::data_member&); + + protected: + sema_rel::model& model_; + sema_rel::table& table_; + string prefix_; + string id_prefix_; + bool id_override_; + }; + + struct member_create: object_members_base, virtual context + { + typedef member_create base; + + member_create (sema_rel::model& model) + : object_members_base (false, true, false), model_ (model) + { + } + + virtual void + traverse_object (semantics::class_& c) + { + if (context::top_object != &c) + { + // We are in one of the bases. Set the id_prefix to its + // (unqualified) name. + // + string t (id_prefix_); + id_prefix_ = c.name () + "::"; + object_members_base::traverse_object (c); + id_prefix_ = t; + } + else + { + // Top-level object. Set its id as a prefix. + // + id_prefix_ = string (c.fq_name (), 2) + "::"; + object_members_base::traverse_object (c); + } + } + + virtual void + traverse_composite (semantics::data_member* m, semantics::class_& c) + { + string t (id_prefix_); + + if (m != 0) + // Member of a composite type. Add the data member to id_prefix. + // + id_prefix_ += m->name () + "."; + else + // Composite base. Add its unqualified name to id_prefix. + // + id_prefix_ += c.name () + "::"; + + object_members_base::traverse_composite (m, c); + + id_prefix_ = t; + } + + virtual void + traverse_container (semantics::data_member& m, semantics::type& ct) + { + using semantics::type; + using semantics::data_member; + + // Ignore inverse containers of object pointers. + // + if (inverse (m, "value")) + return; + + container_kind_type ck (container_kind (ct)); + type& vt (container_vt (ct)); + + string const& name (table_name (m, table_prefix_)); + + // Add the [] decorator to distinguish this id from non-container + // ids (we don't want to ever end up comparing, for example, an + // object table to a container table). + // + string id (id_prefix_ + m.name () + "[]"); + + sema_rel::container_table& t ( + model_.new_node<sema_rel::container_table> (id)); + t.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::names> (model_, t, name); + + // object_id (simple value, for now) + // + string id_name (column_name (m, "id", "object_id")); + { + instance<object_columns> oc (model_, t, "id"); + oc->traverse_column (m, id_name, true); + } + + // Foreign key for the object id. + // + { + sema_rel::foreign_key& fk ( + model_.new_node<sema_rel::foreign_key> ( + id + ".id", + table_name (*context::top_object), + false, // immediate + sema_rel::foreign_key::cascade)); + + fk.set ("cxx-node", static_cast<semantics::node*> (&m)); + + fk.referenced_columns ().push_back ( + column_name ( + *id_member (*context::top_object))); + + // All the columns we have in this table so far are for the + // object id. + // + for (sema_rel::table::names_iterator i (t.names_begin ()); + i != t.names_end (); + ++i) + model_.new_edge<sema_rel::contains> ( + fk, dynamic_cast<sema_rel::column&> (i->nameable ())); + + // Derive the constraint name. See the comment for the other + // foreign key code above. + // + model_.new_edge<sema_rel::names> (t, fk, id_name + "_fk"); + } + + // index (simple value) + // + string index_name; + bool ordered (ck == ck_ordered && !unordered (m)); + if (ordered) + { + instance<object_columns> oc (model_, t, "index"); + index_name = column_name (m, "index", "index"); + oc->traverse_column (m, index_name, true); + } + + // key (simple or composite value) + // + if (ck == ck_map || ck == ck_multimap) + { + type& kt (container_kt (ct)); + + if (semantics::class_* ckt = composite_wrapper (kt)) + { + instance<object_columns> oc (model_, t); + oc->traverse (m, *ckt, "key", "key"); + } + else + { + instance<object_columns> oc (model_, t, "key"); + string const& name (column_name (m, "key", "key")); + oc->traverse_column (m, name, true); + } + } + + // value (simple or composite value) + // + { + if (semantics::class_* cvt = composite_wrapper (vt)) + { + instance<object_columns> oc (model_, t); + oc->traverse (m, *cvt, "value", "value"); + } + else + { + instance<object_columns> oc (model_, t, "value"); + string const& name (column_name (m, "value", "value")); + oc->traverse_column (m, name, true); + } + } + + // Create indexes. + // + using sema_rel::index; + using sema_rel::column; + + { + index& i (model_.new_node<index> (id + ".id")); + i.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::contains> ( + i, dynamic_cast<column&> (t.find (id_name)->nameable ())); + + //@@ Once id can be composite, we need to revise this (see + // a comment for the foreign key generation above). + // + model_.new_edge<sema_rel::names> ( + model_, i, name + '_' + id_name + "_i"); + } + + if (ordered) + { + index& i (model_.new_node<index> (id + ".index")); + i.set ("cxx-node", static_cast<semantics::node*> (&m)); + + model_.new_edge<sema_rel::contains> ( + i, dynamic_cast<column&> (t.find (index_name)->nameable ())); + + // This is always a single column (simple value). + // + model_.new_edge<sema_rel::names> ( + model_, i, name + '_' + index_name + "_i"); + } + } + + protected: + sema_rel::model& model_; + string id_prefix_; + }; + + struct class_: traversal::class_, virtual context + { + typedef class_ base; + + class_ (sema_rel::model& model) + : model_ (model) + { + } + + virtual void + traverse (type& c) + { + if (c.file () != unit.file ()) + return; + + if (!object (c) || abstract (c)) + return; + + string const& name (table_name (c)); + + // If the table with this name was already created, assume the + // user knows what they are doing and skip it. + // + if (tables_.count (name)) + { + c.set ("model-range-first", model_.names_end ()); + c.set ("model-range-last", model_.names_end ()); + return; + } + + string id (c.fq_name (), 2); // Remove leading '::'. + + sema_rel::object_table& t( + model_.new_node<sema_rel::object_table> (id)); + + t.set ("cxx-node", static_cast<semantics::node*> (&c)); + + model_.new_edge<sema_rel::names> (model_, t, name); + + sema_rel::model::names_iterator begin (--model_.names_end ()); + + { + instance<object_columns> oc (model_, t); + oc->traverse (c); + } + + tables_.insert (name); + + // Create tables for members. + // + { + instance<member_create> mc (model_); + mc->traverse (c); + } + + sema_rel::model::names_iterator end (--model_.names_end ()); + + c.set ("model-range-first", begin); + c.set ("model-range-last", end); + } + + protected: + sema_rel::model& model_; + tables tables_; + }; + } +} + +#endif // ODB_RELATIONAL_MODEL_HXX diff --git a/odb/relational/mysql/context.cxx b/odb/relational/mysql/context.cxx index 08f2df5..8862edf 100644 --- a/odb/relational/mysql/context.cxx +++ b/odb/relational/mysql/context.cxx @@ -67,16 +67,19 @@ namespace relational } context:: - context (ostream& os, semantics::unit& u, options_type const& ops) + context (ostream& os, + semantics::unit& u, + options_type const& ops, + sema_rel::model* m) : root_context (os, u, ops, data_ptr (new (shared) data (os))), - base_context (static_cast<data*> (root_context::data_.get ())), + base_context (static_cast<data*> (root_context::data_.get ()), m), data_ (static_cast<data*> (base_context::data_)) { assert (current_ == 0); current_ = this; - data_->generate_grow_ = true; - data_->need_alias_as_ = true; + generate_grow = true; + need_alias_as = true; data_->bind_vector_ = "MYSQL_BIND*"; data_->truncated_vector_ = "my_bool*"; @@ -299,9 +302,6 @@ namespace relational // SQL type parsing. // - static sql_type - parse_sql_type (semantics::data_member& m, std::string const& sql); - sql_type const& context:: column_sql_type (semantics::data_member& m, string const& kp) { @@ -310,18 +310,30 @@ namespace relational : "mysql-" + kp + "-column-sql-type"); if (!m.count (key)) - m.set (key, parse_sql_type (m, column_type (m, kp))); + { + try + { + m.set (key, parse_sql_type (column_type (m, kp))); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } + } return m.get<sql_type> (key); } - static sql_type - parse_sql_type (semantics::data_member& m, string const& sql) + sql_type context:: + parse_sql_type (string const& sqlt) { try { sql_type r; - sql_lexer l (sql); + sql_lexer l (sqlt); // While most type names use single identifier, there are // a couple of exceptions to this rule: @@ -568,16 +580,13 @@ namespace relational if (r.type == sql_type::invalid) { - cerr << m.file () << ":" << m.line () << ":" << - m.column () << ":"; - if (tt == sql_token::t_identifier) - cerr << " error: unknown MySQL type '" << - t.identifier () << "'" << endl; + { + throw invalid_sql_type ( + "unknown MySQL type '" + t.identifier () + "'"); + } else - cerr << " error: expected MySQL type name" << endl; - - throw operation_failed (); + throw invalid_sql_type ("expected MySQL type name"); } // Fall through. @@ -598,11 +607,9 @@ namespace relational { if (t.type () != sql_token::t_string_lit) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: string literal expected in MySQL ENUM " - << "or SET declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "string literal expected in MySQL ENUM or SET " + "declaration"); } if (r.type == sql_type::ENUM) @@ -614,11 +621,8 @@ namespace relational break; else if (t.punctuation () != sql_token::p_comma) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: comma expected in MySQL ENUM or " - << "SET declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "comma expected in MySQL ENUM or SET declaration"); } t = l.next (); @@ -628,11 +632,8 @@ namespace relational { if (t.type () != sql_token::t_int_lit) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: integer range expected in MySQL type " - << "declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "integer range expected in MySQL type declaration"); } unsigned int v; @@ -640,11 +641,9 @@ namespace relational if (!(is >> v && is.eof ())) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: invalid range value '" << t.literal () - << "'in MySQL type declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "invalid range value '" + t.literal () + "' in MySQL " + "type declaration"); } r.range = true; @@ -671,11 +670,8 @@ namespace relational if (t.punctuation () != sql_token::p_rparen) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: expected ')' in MySQL type declaration" - << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "expected ')' in MySQL type declaration"); } s = parse_sign; @@ -729,10 +725,7 @@ namespace relational if (r.type == sql_type::invalid) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: incomplete MySQL type declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ("incomplete MySQL type declaration"); } // If range is omitted for CHAR or BIT types, it defaults to 1. @@ -747,11 +740,8 @@ namespace relational } catch (sql_lexer::invalid_input const& e) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: invalid MySQL type declaration: " << e.message - << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "invalid MySQL type declaration: " + e.message); } } } diff --git a/odb/relational/mysql/context.hxx b/odb/relational/mysql/context.hxx index 26568ec..2615397 100644 --- a/odb/relational/mysql/context.hxx +++ b/odb/relational/mysql/context.hxx @@ -84,6 +84,21 @@ namespace relational column_sql_type (semantics::data_member&, string const& key_prefix = string ()); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + static sql_type + parse_sql_type (string const&); + protected: virtual bool grow_impl (semantics::class_&); @@ -106,7 +121,10 @@ namespace relational virtual ~context (); context (); - context (std::ostream&, semantics::unit&, options_type const&); + context (std::ostream&, + semantics::unit&, + options_type const&, + sema_rel::model*); static context& current () diff --git a/odb/relational/mysql/model.cxx b/odb/relational/mysql/model.cxx new file mode 100644 index 0000000..0ebf65f --- /dev/null +++ b/odb/relational/mysql/model.cxx @@ -0,0 +1,111 @@ +// file : odb/relational/mysql/model.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// 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 (column_sql_type (m)); + + 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_; + } + } +} diff --git a/odb/relational/mysql/schema.cxx b/odb/relational/mysql/schema.cxx index 485fa9f..428db1c 100644 --- a/odb/relational/mysql/schema.cxx +++ b/odb/relational/mysql/schema.cxx @@ -20,143 +20,110 @@ namespace relational // Create. // - struct create_common: virtual relational::create_common + struct create_column: relational::create_column, context { - virtual void - create_table_post () - { - os << ")"; - - string const& engine (options.mysql_engine ()); - - if (engine != "default") - os << endl - << " ENGINE=" << engine; - - os << endl; - } - }; - - struct object_columns: relational::object_columns, context - { - object_columns (base const& x): base (x) {} + create_column (base const& x): base (x) {} virtual void - null (semantics::data_member& m) + null (sema_rel::column& c) { // MySQL TIMESTAMP is by default NOT NULL. If we want it // to contain NULL values, we need to explicitly declare // the column as NULL. // - if (context::null (m, prefix_) && - column_sql_type (m, prefix_).type == sql_type::TIMESTAMP) - os << " NULL"; - else - base::null (m); + if (c.null ()) + { + // This should never fail since we have already parsed this. + // + sql_type const& t (parse_sql_type (c.type ())); + + if (t.type == sql_type::TIMESTAMP) + { + os << " NULL"; + return; + } + } + + base::null (c); } virtual void - default_bool (semantics::data_member&, bool v) + auto_ (sema_rel::column&) { - // MySQL has TRUE and FALSE as just aliases for 1 and 0. Still - // use them for self-documentation. - // - os << " DEFAULT " << (v ? "TRUE" : "FALSE"); + 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) {} virtual void - default_enum (semantics::data_member& m, tree en, string const& name) + traverse (sema_rel::foreign_key& fk) { - // Make sure the column is mapped to an ENUM or integer type. + // MySQL does not support deferred constraint checking. Output + // such foreign keys as comments, for documentation, unless we + // are generating embedded schema. // - sql_type const& t (column_sql_type (m)); - - 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) + if (fk.deferred ()) { - // 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. + // Don't bloat C++ code with comment strings if we are + // generating embedded schema. // - size_t pos (0); - - for (enum_::enumerates_iterator i (e.enumerates_begin ()), - end (e.enumerates_end ()); i != end; ++i) + if (format_ != schema_format::embedded) { - if (&i->enumerator () == &er) - break; - - pos++; - } + os << endl + << endl + << " /*" << endl; - if (pos < t.enumerators.size ()) - os << " DEFAULT " << t.enumerators[pos]; - else - { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: unable to map C++ enumerator '" << name - << "' to MySQL ENUM value" << endl; + base::create (fk); - throw operation_failed (); + os << endl + << " */"; } } else - { - if (e.unsigned_ ()) - os << " DEFAULT " << er.value (); - else - os << " DEFAULT " << static_cast<long long> (er.value ()); - } + base::traverse (fk); } - virtual void - constraints (semantics::data_member& m) + virtual string + name (sema_rel::foreign_key& fk) { - base::constraints (m); - - if (m.count ("auto")) - os << " AUTO_INCREMENT"; + // In MySQL, foreign key names are database-global. Make them + // unique by prefixing the key name with table name. + // + return static_cast<sema_rel::table&> (fk.scope ()).name () + + '_' + fk.name (); } + virtual void + deferred () + { + // MySQL doesn't support deferred. + } }; - entry<object_columns> object_columns_; + entry<create_foreign_key> create_foreign_key_; - struct member_create: relational::member_create, create_common + struct create_table: relational::create_table, context { - member_create (base const& x): base (x) {} - }; - entry<member_create> member_create_; + create_table (base const& x): base (x) {} - struct class_create: relational::class_create, create_common - { - class_create (base const& x): base (x) {} + virtual void + create_post () + { + os << ")"; + + string const& engine (options.mysql_engine ()); + + if (engine != "default") + os << endl + << " ENGINE=" << engine; + + os << endl; + } }; - entry<class_create> class_create_; + entry<create_table> create_table_; } } } diff --git a/odb/relational/oracle/context.cxx b/odb/relational/oracle/context.cxx index 236c725..3fe3e6d 100644 --- a/odb/relational/oracle/context.cxx +++ b/odb/relational/oracle/context.cxx @@ -67,16 +67,19 @@ namespace relational } context:: - context (ostream& os, semantics::unit& u, options_type const& ops) + context (ostream& os, + semantics::unit& u, + options_type const& ops, + sema_rel::model* m) : root_context (os, u, ops, data_ptr (new (shared) data (os))), - base_context (static_cast<data*> (root_context::data_.get ())), + base_context (static_cast<data*> (root_context::data_.get ()), m), data_ (static_cast<data*> (base_context::data_)) { assert (current_ == 0); current_ = this; - data_->generate_grow_ = false; - data_->need_alias_as_ = false; + generate_grow = false; + need_alias_as = false; data_->bind_vector_ = "oracle::bind*"; // Populate the C++ type to DB type map. @@ -143,9 +146,6 @@ namespace relational // SQL type parsing. // - static sql_type - parse_sql_type (semantics::data_member& m, std::string const& sql); - sql_type const& context:: column_sql_type (semantics::data_member& m, string const& kp) { @@ -154,18 +154,30 @@ namespace relational : "oracle-" + kp + "-column-sql-type"); if (!m.count (key)) - m.set (key, parse_sql_type (m, column_type (m, kp))); + { + try + { + m.set (key, parse_sql_type (column_type (m, kp))); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } + } return m.get<sql_type> (key); } - static sql_type - parse_sql_type (semantics::data_member& m, string const& sql) + sql_type context:: + parse_sql_type (string const& sqlt) { try { sql_type r; - sql_lexer l (sql); + sql_lexer l (sqlt); // While most type names use single identifier, there are // a couple of exceptions to this rule: @@ -304,11 +316,9 @@ namespace relational (prefix == "TIMESTAMP WITH LOCAL TIME" || prefix == "TIMESTAMP WITH TIME")) { - cerr << m.file () << ":" << m.line () << ":" - << m.column ()<< ": error: Oracle timestamps with time " - << "zones are not currently supported" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "Oracle timestamps with time zones are not currently " + "supported"); } // // String and binary types. @@ -387,24 +397,18 @@ namespace relational // else if (id == "LONG") { - cerr << m.file () << ":" << m.line () << ":" - << m.column () << ": error: LONG types are not " - << " supported" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "Oracle LONG types are not supported"); } else { - cerr << m.file () << ":" << m.line () << ":" << - m.column () << ":"; - if (tt == sql_token::t_identifier) - cerr << " error: unknown Oracle type '" << - t.identifier () << "'" << endl; + { + throw invalid_sql_type ( + "unknown Oracle type '" + t.identifier () + "'"); + } else - cerr << " error: expected Oracle type name" << endl; - - throw operation_failed (); + throw invalid_sql_type ("expected Oracle type name"); } } @@ -440,16 +444,13 @@ namespace relational if (r.type == sql_type::invalid) { - cerr << m.file () << ":" << m.line () << ":" << - m.column () << ":"; - if (tt == sql_token::t_identifier) - cerr << " error: unknown Oracle type '" << - prefix + t.identifier () << "'" << endl; + { + throw invalid_sql_type ( + "unknown Oracle type '" + t.identifier () + "'"); + } else - cerr << " error: expected Oracle type name" << endl; - - throw operation_failed (); + throw invalid_sql_type ("expected Oracle type name"); } } @@ -465,11 +466,8 @@ namespace relational if (t.type () != sql_token::t_int_lit) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: integer range expected in Oracle type " - << "declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "integer range expected in Oracle type declaration"); } // Parse the range. @@ -480,12 +478,9 @@ namespace relational if (!(is >> v && is.eof ())) { - cerr << m.file () << ":" << m.line () << ":" - << m.column () - << ": error: invalid range value '" << t.literal () - << "'in Oracle type declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "invalid range value '" + t.literal () + "' in Oracle " + "type declaration"); } r.range = true; @@ -502,11 +497,8 @@ namespace relational if (t.type () != sql_token::t_int_lit) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: integer scale expected in Oracle type " - << "declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "integer scale expected in Oracle type declaration"); } short v; @@ -514,12 +506,9 @@ namespace relational if (!(is >> v && is.eof ())) { - cerr << m.file () << ":" << m.line () << ":" - << m.column () - << ": error: invalid scale value '" << t.literal () - << "'in Oracle type declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "invalid scale value '" + t.literal () + "' in Oracle " + "type declaration"); } r.scale = true; @@ -535,20 +524,16 @@ namespace relational r.byte_semantics = false; else if (id != "BYTE") { - cerr << m.file () << ":" << m.line () << ":" - << m.column () - << ": error: invalid keyword '" << t.literal () - << "'in Oracle type declaration" << endl; + throw invalid_sql_type ( + "invalid keyword '" + t.literal () + "' in Oracle " + "type declaration"); } } if (t.punctuation () != sql_token::p_rparen) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: expected ')' in Oracle type declaration" - << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "expected ')' in Oracle type declaration"); } } @@ -594,21 +579,16 @@ namespace relational if (r.type == sql_type::invalid) { - cerr << "error: incomplete Oracle type declaration: " << prefix - << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "incomplete Oracle type declaration: '" + prefix + "'"); } return r; } catch (sql_lexer::invalid_input const& e) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: invalid Oracle type declaration: " << e.message - << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "invalid Oracle type declaration: " + e.message); } } } diff --git a/odb/relational/oracle/context.hxx b/odb/relational/oracle/context.hxx index d334f56..e99b273 100644 --- a/odb/relational/oracle/context.hxx +++ b/odb/relational/oracle/context.hxx @@ -74,6 +74,22 @@ namespace relational column_sql_type (semantics::data_member&, string const& key_prefix = string ()); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + static sql_type + parse_sql_type (string const&); + + public: static bool unsigned_integer (semantics::type&); @@ -90,7 +106,10 @@ namespace relational ~context (); context (); - context (std::ostream&, semantics::unit&, options_type const&); + context (std::ostream&, + semantics::unit&, + options_type const&, + sema_rel::model*); static context& current () diff --git a/odb/relational/oracle/model.cxx b/odb/relational/oracle/model.cxx new file mode 100644 index 0000000..a71d351 --- /dev/null +++ b/odb/relational/oracle/model.cxx @@ -0,0 +1,74 @@ +// file : odb/relational/oracle/model.cxx +// author : Constantin Michael <constantin@codesynthesis.com> +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// 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_bool (semantics::data_member&, bool v) + { + // Oracle seems to have the TRUE and FALSE literals, though no + // boolean type. + // + return v ? "TRUE" : "FALSE"; + } + + virtual string + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to Oracle NUMBER. + // + sql_type t (column_sql_type (m)); + + 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 + reference (semantics::data_member&) + { + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/relational/oracle/schema.cxx b/odb/relational/oracle/schema.cxx index 8469761..a371235 100644 --- a/odb/relational/oracle/schema.cxx +++ b/odb/relational/oracle/schema.cxx @@ -3,7 +3,7 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file -#include <string> +#include <set> #include <odb/relational/schema.hxx> @@ -27,8 +27,13 @@ namespace relational virtual void line (const std::string& l) { - base::line (l); - last_ = l; + // SQLPlus doesn't like empty line in the middle of a statement. + // + if (!l.empty ()) + { + base::line (l); + last_ = l; + } } virtual void @@ -61,8 +66,11 @@ namespace relational schema_file (const base& x): base (x) {} virtual void - pre () + 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 @@ -70,7 +78,7 @@ namespace relational } virtual void - post () + epilogue () { os << "EXIT;" << endl; } @@ -81,10 +89,12 @@ namespace relational // Drop. // - struct drop_common: virtual relational::drop_common + struct drop_table: relational::drop_table, context { + drop_table (base const& x): base (x) {} + virtual void - drop_table (string const& table) + drop (string const& table) { // Oracle has no IF EXISTS conditional for dropping objects. The // PL/SQL approach below seems to be the least error-prone and the @@ -107,7 +117,7 @@ namespace relational << " END;" << endl << " BEGIN" << endl << " EXECUTE IMMEDIATE 'DROP TRIGGER " << - quote_id (table + "_trig") << "';" << endl + quote_id (table + "_trg") << "';" << endl << " EXCEPTION" << endl << " WHEN OTHERS THEN" << endl << " IF SQLCODE != -4080 THEN RAISE; END IF;" << endl @@ -115,250 +125,176 @@ namespace relational << "END;" << endl; } }; - - struct member_drop: relational::member_drop, drop_common - { - member_drop (base const& x): base (x) {} - }; - entry<member_drop> member_drop_; - - struct class_drop: relational::class_drop, drop_common - { - class_drop (base const& x): base (x) {} - }; - entry<class_drop> class_drop_; + entry<drop_table> drop_table_; // // Create. // - struct object_columns: relational::object_columns, context + struct create_foreign_key; + + struct create_table: relational::create_table, context { - object_columns (base const& x): base (x) {} + create_table (base const& x): base (x) {} - virtual void - null (semantics::data_member& m) - { - sql_type::core_type t (column_sql_type (m, prefix_).type); + void + traverse (sema_rel::table&); - // Oracle interprets empty VARCHAR2 and NVARCHAR2 strings as NULL. As - // an empty string is always valid within the C++ context, VARCHAR2 - // and NVARCHAR2 columns are always specified as nullable. - // - if (t != sql_type::VARCHAR2 && t != sql_type::NVARCHAR2) - base::null (m); - } + private: + friend class create_foreign_key; + set<string> tables_; // Set of tables we have already defined. + }; + entry<create_table> create_table_; + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} virtual void - default_enum (semantics::data_member& m, tree en, string const&) + null (sema_rel::column& c) { - // Make sure the column is mapped to Oracle NUMBER. + // Oracle interprets empty VARCHAR2 and NVARCHAR2 strings as + // NULL. As an empty string is always valid within the C++ + // context, VARCHAR2 and NVARCHAR2 columns are always + // specified as nullable. // - sql_type t (column_sql_type (m)); - - if (t.type != sql_type::NUMBER) + if (!c.null ()) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: column with default value specified as C++ " - << "enumerator must map to Oracle NUMBER" << endl; + // This should never fail since we have already parsed this. + // + sql_type const& t (parse_sql_type (c.type ())); - throw operation_failed (); + if (t.type == sql_type::VARCHAR2 || t.type == sql_type::NVARCHAR2) + return; } - using semantics::enumerator; - - enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); - - if (e.enum_ ().unsigned_ ()) - os << " DEFAULT " << e.value (); - else - os << " DEFAULT " << static_cast<long long> (e.value ()); - } - - virtual void - reference (semantics::data_member&) - { + base::null (c); } }; - entry<object_columns> object_columns_; + entry<create_column> create_column_; - struct object_columns_references: - object_columns_base, relational::common, context + struct create_foreign_key: relational::create_foreign_key, context { - object_columns_references (emitter& e, - ostream& os, - string const& table, - string const& prefix = string ()) - : relational::common (e, os), - table_ (table), - prefix_ (prefix) + create_foreign_key (schema_format f, relational::create_table& ct) + : base (f, ct) { } - virtual bool - traverse_column (semantics::data_member& m, string const& name, bool) - { - if (inverse (m)) - return false; - - if (semantics::class_* c = - object_pointer (member_utype (m, prefix_))) - { - pre_statement (); + create_foreign_key (base const& x): base (x) {} - os << "ALTER TABLE " << quote_id (table_) << endl - << " ADD FOREIGN KEY (" << quote_id (name) << ")" << endl - << " REFERENCES " << table_qname (*c) << endl - << " DEFERRABLE INITIALLY DEFERRED" << endl; + virtual void + traverse (sema_rel::foreign_key& fk) + { + // 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 + // (see add_foreign_key below). + // + create_table& ct (static_cast<create_table&> (create_table_)); - post_statement (); - } - else if (prefix_ == "id") + if (ct.tables_.find (fk.referenced_table ()) != ct.tables_.end ()) { - semantics::class_& c (*context::top_object); - - pre_statement (); - - // We don't need INITIALLY DEFERRED here since the object row - // must exist before any container row. - // - os << "ALTER TABLE " << quote_id (table_) << endl - << " ADD FOREIGN KEY (" << quote_id (name) << ")" << endl - << " REFERENCES " << table_qname (c) << endl - << " ON DELETE CASCADE" << endl; - - post_statement (); + base::traverse (fk); + fk.set ("oracle-fk-defined", true); // Mark it as defined. } - - return true; } - private: - string table_; - string prefix_; + virtual string + name (sema_rel::foreign_key& fk) + { + // In Oracle, foreign key names are schema-global. Make them + // unique by prefixing the key name with table name. + // + return static_cast<sema_rel::table&> (fk.scope ()).name () + + '_' + fk.name (); + } }; + entry<create_foreign_key> create_foreign_key_; - struct member_create: object_members_base, context + struct add_foreign_key: create_foreign_key, relational::common { - member_create (emitter& e, ostream& os, relational::tables& tables) - : object_members_base (false, true, false), - e_ (e), - os_ (os), - tables_ (tables) + add_foreign_key (schema_format f, relational::create_table& ct) + : create_foreign_key (f, ct), common (ct.emitter (), ct.stream ()) { } virtual void - traverse_container (semantics::data_member& m, semantics::type& t) + traverse (sema_rel::foreign_key& fk) { - using semantics::type; - using semantics::data_member; - - // Ignore inverse containers of object pointers. - // - if (inverse (m, "value")) - return; - - string const& name (table_name (m, table_prefix_)); - - if (tables_.count (name)) - return; + if (!fk.count ("oracle-fk-defined")) + { + sema_rel::table& t (dynamic_cast<sema_rel::table&> (fk.scope ())); - type& vt (container_vt (t)); + pre_statement (); - // object_id - // - { - object_columns_references ocr (e_, os_, name, "id"); - string id_name (column_name (m, "id", "object_id")); - ocr.traverse_column (m, id_name, true); - } + os << "ALTER TABLE " << quote_id (t.name ()) << " ADD" << endl; + base::create (fk); + os << endl; - // value - // - if (semantics::class_* cvt = composite_wrapper (vt)) - { - object_columns_references ocr (e_, os_, name); - ocr.traverse (m, *cvt, "value", "value"); - } - else - { - object_columns_references ocr (e_, os_, name, "value"); - string const& value_name (column_name (m, "value", "value")); - ocr.traverse_column (m, value_name, true); + post_statement (); } - - tables_.insert (name); } - - private: - emitter& e_; - ostream& os_; - relational::tables& tables_; }; - struct class_create: relational::class_create, context + void create_table:: + traverse (sema_rel::table& t) { - class_create (base const& x): base (x) {} - - virtual void - traverse (type& c) + if (pass_ == 1) { - if (pass_ != 2) - { - base::traverse (c); - return; - } + tables_.insert (t.name ()); // Add it before to cover self-refs. + base::traverse (t); - if (c.file () != unit.file ()) - return; - - if (!object (c) || abstract (c)) - return; - - string const& name (table_name (c)); + // Create the sequence and trigger if we have auto primary key. + // + using sema_rel::primary_key; - if (tables_[pass_].count (name)) - return; + sema_rel::table::names_iterator i (t.find ("")); // Special name. - semantics::data_member* id (id_member (c)); + primary_key* pk (i != t.names_end () + ? &dynamic_cast<primary_key&> (i->nameable ()) + : 0); - if (id->count ("auto")) + if (pk != 0 && pk->auto_ ()) { - string seq_name (quote_id (name + "_seq")); + string const& tname (t.name ()); + string const& cname (pk->contains_begin ()->column ().name ()); + string seq_name (tname + "_seq"); + string trg_name (tname + "_trg"); + + // Sequence. + // pre_statement (); - os_ << "CREATE SEQUENCE " << seq_name << endl - << " START WITH 1 INCREMENT BY 1" << endl - << endl; + os_ << "CREATE SEQUENCE " << quote_id (seq_name) << endl + << " START WITH 1 INCREMENT BY 1" << endl; post_statement (); + // Trigger. + // pre_statement (); - os_ << "CREATE TRIGGER " << - quote_id (name + "_trig") << endl - << " BEFORE INSERT ON " << quote_id (name) << endl + os_ << "CREATE TRIGGER " << quote_id (trg_name) << endl + << " BEFORE INSERT ON " << quote_id (tname) << endl << " FOR EACH ROW" << endl << "BEGIN" << endl - << " SELECT " << seq_name << ".nextval INTO :new." << - column_qname (*id) << " FROM DUAL;" << endl + << " SELECT " << quote_id (seq_name) << ".nextval " << + "INTO :new." << quote_id (cname) << " FROM DUAL;" << endl << "END;" << endl; post_statement (); } - object_columns_references ocr (e_, os_, name); - ocr.traverse (c); - - tables_[pass_].insert (name); - - member_create mc (e_, os_, tables_[pass_]); - mc.traverse (c); + return; } - }; - entry<class_create> class_create_; + + // Add foreign keys. + // + instance<add_foreign_key> fk (format_, *this); + trav_rel::names n (*fk); + names (t, n); + } } } } diff --git a/odb/relational/pgsql/context.cxx b/odb/relational/pgsql/context.cxx index 28a6cbd..c17b41f 100644 --- a/odb/relational/pgsql/context.cxx +++ b/odb/relational/pgsql/context.cxx @@ -67,16 +67,19 @@ namespace relational } context:: - context (ostream& os, semantics::unit& u, options_type const& ops) + context (ostream& os, + semantics::unit& u, + options_type const& ops, + sema_rel::model* m) : root_context (os, u, ops, data_ptr (new (shared) data (os))), - base_context (static_cast<data*> (root_context::data_.get ())), + base_context (static_cast<data*> (root_context::data_.get ()), m), data_ (static_cast<data*> (base_context::data_)) { assert (current_ == 0); current_ = this; - data_->generate_grow_ = true; - data_->need_alias_as_ = true; + generate_grow = true; + need_alias_as = true; data_->bind_vector_ = "pgsql::bind*"; data_->truncated_vector_ = "bool*"; @@ -232,9 +235,6 @@ namespace relational // SQL type parsing. // - static sql_type - parse_sql_type (semantics::data_member& m, std::string const& sql); - sql_type const& context:: column_sql_type (semantics::data_member& m, string const& kp) { @@ -243,18 +243,30 @@ namespace relational : "pgsql-" + kp + "-column-sql-type"); if (!m.count (key)) - m.set (key, parse_sql_type (m, column_type (m, kp))); + { + try + { + m.set (key, parse_sql_type (column_type (m, kp))); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } + } return m.get<sql_type> (key); } - static sql_type - parse_sql_type (semantics::data_member& m, string const& sql) + sql_type context:: + parse_sql_type (string const& sqlt) { try { sql_type r; - sql_lexer l (sql); + sql_lexer l (sqlt); // While most type names use single identifier, there are // a couple of exceptions to this rule: @@ -368,11 +380,8 @@ namespace relational } else if (id == "TIMETZ") { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: PostgreSQL time zones are not currently " - << "supported" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "PostgreSQL time zones are not currently supported"); } else if (id == "TIMESTAMP") { @@ -380,11 +389,8 @@ namespace relational } else if (id == "TIMESTAMPTZ") { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: PostgreSQL time zones are not currently " - << "supported" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "PostgreSQL time zones are not currently supported"); } // // String and binary types. @@ -450,16 +456,13 @@ namespace relational if (r.type == sql_type::invalid) { - cerr << m.file () << ":" << m.line () << ":" << - m.column () << ":"; - if (tt == sql_token::t_identifier) - cerr << " error: unknown PostgreSQL type '" << - t.identifier () << "'" << endl; + { + throw invalid_sql_type ( + "unknown PostgreSQL type '" + t.identifier () + "'"); + } else - cerr << " error: expected PostgreSQL type name" << endl; - - throw operation_failed (); + throw invalid_sql_type ("expected PostgreSQL type name"); } // Fall through. @@ -474,11 +477,8 @@ namespace relational if (t.type () != sql_token::t_int_lit) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: integer range expected in PostgreSQL type " - << "declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "integer range expected in PostgreSQL type declaration"); } unsigned int v; @@ -486,11 +486,9 @@ namespace relational if (!(is >> v && is.eof ())) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: invalid range value '" << t.literal () - << "'in PostgreSQL type declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "invalid range value '" + t.literal () + "' in PostgreSQL " + "type declaration"); } r.range = true; @@ -508,11 +506,8 @@ namespace relational if (t.punctuation () != sql_token::p_rparen) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: expected ')' in PostgreSQL type " - << "declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "expected ')' in PostgreSQL type declaration"); } s = parse_suffix; @@ -549,11 +544,9 @@ namespace relational if (id3 == "ZONE") { - cerr << m.file () << ":" << m.line () << ":" - << m.column ()<< ": error: PostgreSQL time " - << "zones are not currently supported" << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "PostgreSQL time zones are not currently " + "supported"); } } } @@ -596,10 +589,7 @@ namespace relational if (r.type == sql_type::invalid) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: incomplete PostgreSQL type declaration" << endl; - - throw operation_failed (); + throw invalid_sql_type ("incomplete PostgreSQL type declaration"); } // If range is omitted for CHAR or BIT types, it defaults to 1. @@ -614,11 +604,8 @@ namespace relational } catch (sql_lexer::invalid_input const& e) { - cerr << m.file () << ":" << m.line () << ":" << m.column () - << ": error: invalid PostgreSQL type declaration: " << e.message - << endl; - - throw operation_failed (); + throw invalid_sql_type ( + "invalid PostgreSQL type declaration: " + e.message); } } } diff --git a/odb/relational/pgsql/context.hxx b/odb/relational/pgsql/context.hxx index a4fbc00..cab7095 100644 --- a/odb/relational/pgsql/context.hxx +++ b/odb/relational/pgsql/context.hxx @@ -73,6 +73,21 @@ namespace relational column_sql_type (semantics::data_member&, string const& key_prefix = string ()); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + static sql_type + parse_sql_type (string const&); + protected: virtual bool grow_impl (semantics::class_&); @@ -92,7 +107,10 @@ namespace relational ~context (); context (); - context (std::ostream&, semantics::unit&, options_type const&); + context (std::ostream&, + semantics::unit&, + options_type const&, + sema_rel::model*); static context& current () diff --git a/odb/relational/pgsql/model.cxx b/odb/relational/pgsql/model.cxx new file mode 100644 index 0000000..83ab289 --- /dev/null +++ b/odb/relational/pgsql/model.cxx @@ -0,0 +1,71 @@ +// file : odb/relational/pgsql/model.cxx +// author : Constantin Michael <constantin@codesynthesis.com> +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : GNU GPL v3; see accompanying LICENSE file + +#include <sstream> + +#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 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 (column_sql_type (m).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/relational/pgsql/schema.cxx b/odb/relational/pgsql/schema.cxx index 450d056..c723818 100644 --- a/odb/relational/pgsql/schema.cxx +++ b/odb/relational/pgsql/schema.cxx @@ -3,11 +3,15 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file +#include <set> + #include <odb/relational/schema.hxx> #include <odb/relational/pgsql/common.hxx> #include <odb/relational/pgsql/context.hxx> +using namespace std; + namespace relational { namespace pgsql @@ -20,42 +24,50 @@ namespace relational // Drop. // - struct drop_common: virtual relational::drop_common + struct drop_table: relational::drop_table, context { + drop_table (base const& x): base (x) {} + virtual void - drop_table (string const& table) + drop (string const& table) { - os << "DROP TABLE IF EXISTS " << quote_id (table) << " CASCADE" - << endl; + os << "DROP TABLE IF EXISTS " << quote_id (table) << + " CASCADE" << endl; } }; - - struct member_drop: relational::member_drop, drop_common - { - member_drop (base const& x): base (x) {} - }; - entry<member_drop> member_drop_; - - struct class_drop: relational::class_drop, drop_common - { - class_drop (base const& x): base (x) {} - }; - entry<class_drop> class_drop_; + entry<drop_table> drop_table_; // // Create. // - struct object_columns: relational::object_columns, context + struct create_foreign_key; + + struct create_table: relational::create_table, context { - object_columns (base const& x): base (x) {} + create_table (base const& x): base (x) {} + + void + traverse (sema_rel::table&); + + private: + friend class create_foreign_key; + set<string> tables_; // Set of tables we have already defined. + }; + entry<create_table> create_table_; + + struct create_column: relational::create_column, context + { + create_column (base const& x): base (x) {} virtual void - type (semantics::data_member& m) + type (sema_rel::column& c, bool auto_) { - if (m.count ("auto")) + if (auto_) { - sql_type const& t (column_sql_type (m)); + // 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) os << "SERIAL"; @@ -63,7 +75,9 @@ namespace relational os << "BIGSERIAL"; else { - cerr << m.file () << ":" << m.line () << ":" << m.column () + semantics::node& n (*c.get<semantics::node*> ("cxx-node")); + + cerr << n.file () << ":" << n.line () << ":" << n.column () << ": error: automatically assigned object id must map " << "to PostgreSQL INTEGER or BIGINT" << endl; @@ -71,204 +85,87 @@ namespace relational } } else - { - base::type (m); - } + base::type (c, auto_); } + }; + entry<create_column> create_column_; - virtual void - default_bool (semantics::data_member&, bool v) + struct create_foreign_key: relational::create_foreign_key, context + { + create_foreign_key (schema_format f, relational::create_table& ct) + : base (f, ct) { - os << " DEFAULT " << (v ? "TRUE" : "FALSE"); } + create_foreign_key (base const& x): base (x) {} + virtual void - default_enum (semantics::data_member& m, tree en, string const&) + traverse (sema_rel::foreign_key& fk) { - // Make sure the column is mapped to an integer type. + // 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 + // (see add_foreign_key below). // - switch (column_sql_type (m).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; + create_table& ct (static_cast<create_table&> (create_table_)); - throw operation_failed (); - } + if (ct.tables_.find (fk.referenced_table ()) != ct.tables_.end ()) + { + base::traverse (fk); + fk.set ("pgsql-fk-defined", true); // Mark it as defined. } - - using semantics::enumerator; - - enumerator& e (dynamic_cast<enumerator&> (*unit.find (en))); - - if (e.enum_ ().unsigned_ ()) - os << " DEFAULT " << e.value (); - else - os << " DEFAULT " << static_cast<long long> (e.value ()); } virtual void - reference (semantics::data_member&) + deferred () { + os << endl + << " INITIALLY DEFERRED"; } }; - entry<object_columns> object_columns_; + entry<create_foreign_key> create_foreign_key_; - struct object_columns_references: - object_columns_base, relational::common, context + struct add_foreign_key: create_foreign_key, relational::common { - object_columns_references (emitter& e, - ostream& os, - string const& table, - string const& prefix = string ()) - : relational::common (e, os), - table_ (table), - prefix_ (prefix) + add_foreign_key (schema_format f, relational::create_table& ct) + : create_foreign_key (f, ct), common (ct.emitter (), ct.stream ()) { } - virtual bool - traverse_column (semantics::data_member& m, string const& name, bool) + virtual void + traverse (sema_rel::foreign_key& fk) { - if (inverse (m)) - return false; - - if (semantics::class_* c = - object_pointer (member_utype (m, prefix_))) - { - pre_statement (); - - os << "ALTER TABLE " << quote_id (table_) << endl - << " ADD FOREIGN KEY (" << quote_id (name) << ")" << endl - << " REFERENCES " << table_qname (*c) << endl - << " INITIALLY DEFERRED" << endl; - - post_statement (); - } - else if (prefix_ == "id") + if (!fk.count ("pgsql-fk-defined")) { - semantics::class_& c (*context::top_object); + sema_rel::table& t (dynamic_cast<sema_rel::table&> (fk.scope ())); pre_statement (); - // We don't need INITIALLY DEFERRED here since the object row - // must exist before any container row. - // - os << "ALTER TABLE " << quote_id (table_) << endl - << " ADD FOREIGN KEY (" << quote_id (name) << ")" << endl - << " REFERENCES " << table_qname (c) << endl - << " ON DELETE CASCADE" << endl; + os << "ALTER TABLE " << quote_id (t.name ()) << " ADD" << endl; + base::create (fk); + os << endl; post_statement (); } - - return true; } - - private: - string table_; - string prefix_; }; - struct member_create: object_members_base, context + void create_table:: + traverse (sema_rel::table& t) { - member_create (emitter& e, ostream& os, relational::tables& tables) - : object_members_base (false, true, false), - e_ (e), - os_ (os), - tables_ (tables) + if (pass_ == 1) { + tables_.insert (t.name ()); // Add it before to cover self-refs. + base::traverse (t); + return; } - virtual void - traverse_container (semantics::data_member& m, semantics::type& t) - { - using semantics::type; - using semantics::data_member; - - // Ignore inverse containers of object pointers. - // - if (inverse (m, "value")) - return; - - string const& name (table_name (m, table_prefix_)); - - if (tables_.count (name)) - return; - - type& vt (container_vt (t)); - - // object_id - // - { - object_columns_references ocr (e_, os_, name, "id"); - string id_name (column_name (m, "id", "object_id")); - ocr.traverse_column (m, id_name, true); - } - - // value - // - if (semantics::class_* cvt = composite_wrapper (vt)) - { - object_columns_references ocr (e_, os_, name); - ocr.traverse (m, *cvt, "value", "value"); - } - else - { - object_columns_references ocr (e_, os_, name, "value"); - string const& value_name (column_name (m, "value", "value")); - ocr.traverse_column (m, value_name, true); - } - - tables_.insert (name); - } - - private: - emitter& e_; - ostream& os_; - relational::tables& tables_; - }; - - struct class_create: relational::class_create - { - class_create (base const& x): base (x) {} - - virtual void - traverse (type& c) - { - if (pass_ != 2) - { - base::traverse (c); - return; - } - - if (c.file () != unit.file ()) - return; - - if (!object (c) || abstract (c)) - return; - - string const& name (table_name (c)); - - if (tables_[pass_].count (name)) - return; - - object_columns_references ocr (e_, os_, name); - ocr.traverse (c); - - tables_[pass_].insert (name); - - member_create mc (e_, os_, tables_[pass_]); - mc.traverse (c); - } - }; - entry<class_create> class_create_; + // Add foreign keys. + // + instance<add_foreign_key> fk (format_, *this); + trav_rel::names n (*fk); + names (t, n); + } } } } diff --git a/odb/relational/schema.cxx b/odb/relational/schema.cxx index 031bf64..8a06427 100644 --- a/odb/relational/schema.cxx +++ b/odb/relational/schema.cxx @@ -3,8 +3,6 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : GNU GPL v3; see accompanying LICENSE file -#include <odb/gcc.hxx> - #include <cassert> #include <limits> #include <sstream> @@ -20,101 +18,6 @@ namespace relational { namespace schema { - // object_columns - // - void 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: - { - // No default value. - break; - } - case default_value::null: - { - default_null (m); - break; - } - case default_value::boolean: - { - default_bool (m, dv->value == "true"); - break; - } - case default_value::number: - { - tree n (dv->node); - - switch (TREE_CODE (n)) - { - case INTEGER_CST: - { - HOST_WIDE_INT hwl (TREE_INT_CST_LOW (n)); - HOST_WIDE_INT hwh (TREE_INT_CST_HIGH (n)); - - unsigned long long l (hwl); - unsigned long long h (hwh); - unsigned short width (HOST_BITS_PER_WIDE_INT); - - unsigned long long v ((h << width) + l); - - default_integer (m, v, dv->value == "-"); - break; - } - case REAL_CST: - { - double v; - - REAL_VALUE_TYPE d (TREE_REAL_CST (n)); - - if (REAL_VALUE_ISINF (d)) - v = numeric_limits<double>::infinity (); - else if (REAL_VALUE_ISNAN (d)) - v = numeric_limits<double>::quiet_NaN (); - else - { - char tmp[256]; - real_to_decimal (tmp, &d, sizeof (tmp), 0, true); - istringstream is (tmp); - is >> v; - } - - if (dv->value == "-") - v = -v; - - default_float (m, v); - break; - } - default: - assert (false); - } - break; - } - case default_value::string: - { - default_string (m, dv->value); - break; - } - case default_value::enumerator: - { - default_enum (m, dv->node, dv->value); - break; - } - } - } - static char const file_header[] = "/* This file was generated by ODB, object-relational mapping (ORM)\n" " * compiler for C++.\n" @@ -128,33 +31,36 @@ namespace relational os << file_header; - instance<schema_emitter> emitter; - instance<schema_file> file; - file->pre (); + file->prologue (); + + instance<schema_emitter> em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); // Drop. // { - traversal::unit unit; - traversal::defines unit_defines; - traversal::namespace_ ns; - instance<class_drop> c (*emitter); - unit >> unit_defines >> ns; - unit_defines >> c; + instance<drop_model> model (*em, emos, f); + trav_rel::names names; + instance<drop_table> table (*em, emos, f); + instance<drop_index> index (*em, emos, f); - traversal::defines ns_defines; - - ns >> ns_defines >> ns; - ns_defines >> c; + model >> names; + names >> table; + names >> index; // Pass 1 and 2. // for (unsigned short pass (1); pass < 3; ++pass) { - c->pass (pass); - unit.dispatch (ctx.unit); + model->pass (pass); + table->pass (pass); + index->pass (pass); + + model->traverse (*ctx.model); } } @@ -163,29 +69,28 @@ namespace relational // Create. // { - traversal::unit unit; - traversal::defines unit_defines; - traversal::namespace_ ns; - instance<class_create> c (*emitter); + instance<create_model> model (*em, emos, f); + trav_rel::names names; + instance<create_table> table (*em, emos, f); + instance<create_index> index (*em, emos, f); - unit >> unit_defines >> ns; - unit_defines >> c; - - traversal::defines ns_defines; - - ns >> ns_defines >> ns; - ns_defines >> c; + model >> names; + names >> table; + names >> index; // Pass 1 and 2. // for (unsigned short pass (1); pass < 3; ++pass) { - c->pass (pass); - unit.dispatch (ctx.unit); + model->pass (pass); + table->pass (pass); + index->pass (pass); + + model->traverse (*ctx.model); } } - file->post (); + file->epilogue (); } } } diff --git a/odb/relational/schema.hxx b/odb/relational/schema.hxx index ecafe2c..855f67f 100644 --- a/odb/relational/schema.hxx +++ b/odb/relational/schema.hxx @@ -11,7 +11,6 @@ #include <cassert> #include <odb/emitter.hxx> - #include <odb/relational/common.hxx> #include <odb/relational/context.hxx> @@ -19,11 +18,11 @@ namespace relational { namespace schema { - typedef std::set<std::string> tables; - struct common: virtual context { - common (emitter& e, ostream& os): e_ (e), os_ (os) {} + typedef ::emitter emitter_type; + + common (emitter_type& e, ostream& os): e_ (e), os_ (os) {} void pre_statement () @@ -39,8 +38,20 @@ namespace relational e_.post (); } + emitter_type& + emitter () const + { + return e_; + } + + ostream& + stream () const + { + return os_; + } + protected: - emitter& e_; + emitter_type& e_; ostream& os_; }; @@ -57,7 +68,7 @@ namespace relational virtual void line (const std::string& l) { - if (first_) + if (first_ && !l.empty ()) first_ = false; else os << endl; @@ -78,7 +89,7 @@ namespace relational }; // - // File. + // File prologue/epilogue. // struct schema_file: virtual context @@ -86,12 +97,12 @@ namespace relational typedef schema_file base; virtual void - pre () + prologue () { } virtual void - post () + epilogue () { } }; @@ -100,474 +111,467 @@ namespace relational // Drop. // - struct drop_common: virtual context + struct drop_table: trav_rel::table, common { + typedef drop_table base; + + drop_table (emitter_type& e, ostream& os, schema_format f) + : common (e, os), format_ (f) + { + } + virtual void - drop_table (string const& table) + drop (string const& table) { os << "DROP TABLE IF EXISTS " << quote_id (table) << endl; } virtual void - drop_index (string const& /*table*/, string const& /*column*/) + traverse (sema_rel::table& t) { - // Most database systems drop indexes together with the table. + // By default we do everything in a single pass. But some + // databases may require the second pass. // + if (pass_ > 1) + return; - //os << "DROP INDEX IF EXISTS " << quote_id (table + '_' + column) - // << endl; + pre_statement (); + drop (t.name ()); + post_statement (); } + + void + pass (unsigned short p) + { + pass_ = p; + } + + protected: + schema_format format_; + unsigned short pass_; }; - struct member_drop: object_members_base, common, virtual drop_common + struct drop_index: trav_rel::index, common { - typedef member_drop base; + typedef drop_index base; - member_drop (emitter& e, ostream& os, std::vector<tables>& t) - : object_members_base (false, true, false), - common (e, os), - tables_ (t) + drop_index (emitter_type& e, ostream& os, schema_format f) + : common (e, os), format_ (f) { } - void - pass (unsigned short p) + virtual void + drop (string const& /*index*/) { - pass_ = p; + // Most database systems drop indexes together with the table. + // + //os << "DROP INDEX IF EXISTS " << quote_id (index); } virtual void - traverse_container (semantics::data_member& m, semantics::type& c) + traverse (sema_rel::index& in) { - // Ignore inverse containers of object pointers. + // By default we do everything in a single pass. But some + // databases may require the second pass. // - if (inverse (m, "value")) - return; - - string const& name (table_name (m, table_prefix_)); - - if (tables_[pass_].count (name)) + if (pass_ > 1) return; - // Drop table. - // - pre_statement (); - drop_table (name); - post_statement (); - - tables_[pass_].insert (name); - - // Drop indexes. - // pre_statement (); - drop_index (name, column_name (m, "id", "object_id")); + drop (in.name ()); post_statement (); + } - if (container_kind (c) == ck_ordered && !unordered (m)) - { - pre_statement (); - drop_index (name, column_name (m, "index", "index")); - post_statement (); - } + void + pass (unsigned short p) + { + pass_ = p; } protected: - std::vector<tables>& tables_; + schema_format format_; unsigned short pass_; }; - struct class_drop: traversal::class_, common, virtual drop_common + struct drop_model: trav_rel::model, common { - typedef class_drop base; + typedef drop_model base; - class_drop (emitter& e) - : common (e, os_), os_ (e), member_drop_ (e, os_, tables_) + drop_model (emitter_type& e, ostream& os, schema_format f) + : common (e, os), format_ (f) { - tables_.push_back (tables ()); // Dummy entry. - } - - class_drop (class_drop const& x) - : root_context (), //@@ -Wextra - context (), - common (x.e_, os_), os_ (x.e_), member_drop_ (x.e_, os_, tables_) - { - tables_.push_back (tables ()); // Dummy entry. - } - - void - pass (unsigned short p) - { - pass_ = p; - - if (tables_.size () == pass_) - tables_.push_back (tables ()); - - member_drop_->pass (p); } + // This version is only called for file schema. + // virtual void - traverse (type& c) + traverse (sema_rel::model& m) { - // By default we do everything in a single pass. But some - // databases may require the second pass. - // - if (pass_ == 1) - drop (c); + traverse (m.names_begin (), m.names_end ()); } virtual void - drop (type& c) + traverse (sema_rel::model::names_iterator begin, + sema_rel::model::names_iterator end) { - if (c.file () != unit.file ()) - return; - - if (!object (c) || abstract (c)) - return; - - string const& name (table_name (c)); - - if (tables_[pass_].count (name)) - return; - - // Drop tables for members. Do it before dropping the primary - // table -- some databases may prefer it that way. + // Traverse named entities in the reverse order. This way we + // drop them in the order opposite to creating. // - member_drop_->traverse (c); + if (begin != end) + { + for (--end;; --end) + { + dispatch (*end); - pre_statement (); - drop_table (name); - post_statement (); + if (begin == end) + break; + } + } + } - tables_[pass_].insert (name); + void + pass (unsigned short p) + { + pass_ = p; } protected: - emitter_ostream os_; + schema_format format_; unsigned short pass_; - std::vector<tables> tables_; // Seperate table for each pass. - instance<member_drop> member_drop_; }; // // Create. // + struct create_table; - struct object_columns: object_columns_base, virtual context + struct create_column: trav_rel::column, virtual context { - typedef object_columns base; + typedef create_column base; - object_columns (string const& prefix = string ()) - : prefix_ (prefix) + create_column (schema_format f, create_table& ct) + : format_ (f), create_table_ (ct), first_ (true) { } - virtual bool - traverse_column (semantics::data_member& m, - string const& name, - bool first) + virtual void + traverse (sema_rel::column& c) { - // Ignore inverse object pointers. - // - if (inverse (m)) - return false; - - if (!first) + if (first_) + first_ = false; + else os << "," << endl; - os << " " << quote_id (name) << " "; + create (c); + } - type (m); - null (m); + virtual void + create (sema_rel::column& c) + { + using sema_rel::column; - // An id member cannot have a default value. + // See if this column is (part of) a primary key. // - if (!m.count ("id")) - default_ (m); + 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 ()) << " "; - // If we have options, add them. + type (c, pk != 0 && pk->auto_ ()); + null (c); + + // If this is a single-column primary key, generate it inline. // - string const& o (column_options (m, prefix_)); + if (pk != 0 && pk->contains_size () == 1) + primary_key (); - if (!o.empty ()) - os << " " << o; + if (pk != 0 && pk->auto_ ()) + auto_ (c); - constraints (m); - reference (m); + if (!c.default_ ().empty ()) + os << " DEFAULT " << c.default_ (); - return true; + if (!c.options ().empty ()) + os << " " << c.options (); } virtual void - type (semantics::data_member& m) + type (sema_rel::column& c, bool /*auto*/) { - os << column_type (m, prefix_); + os << c.type (); } virtual void - null (semantics::data_member& m) + null (sema_rel::column& c) { - if (!context::null (m, prefix_)) + if (!c.null ()) os << " NOT NULL"; } virtual void - default_null (semantics::data_member&) + primary_key () { - os << " DEFAULT NULL"; + os << " PRIMARY KEY"; } virtual void - default_bool (semantics::data_member&, bool v) + auto_ (sema_rel::column&) { - // Most databases do not support boolean literals. Those that - // do should override this. - // - os << " DEFAULT " << (v ? "1" : "0"); } - virtual void - default_integer (semantics::data_member&, unsigned long long v, bool neg) - { - os << " DEFAULT " << (neg ? "-" : "") << v; - } + protected: + schema_format format_; + create_table& create_table_; + bool first_; + }; - virtual void - default_float (semantics::data_member&, double v) - { - os << " DEFAULT " << v; - } + struct create_primary_key: trav_rel::primary_key, virtual context + { + typedef create_primary_key base; - virtual void - default_string (semantics::data_member&, string const& v) + create_primary_key (schema_format f, create_table& ct) + : format_ (f), create_table_ (ct) { - os << " DEFAULT " << quote_string (v); } virtual void - default_enum (semantics::data_member&, - tree /*enumerator*/, - string const& /*name*/) + traverse (sema_rel::primary_key& pk) { - // Has to be implemented by the database-specific override. + // Single-column primary keys are generated inline in the + // column declaration. // - assert (false); - } + if (pk.contains_size () == 1) + return; - virtual void - constraints (semantics::data_member& m) - { - if (m.count ("id")) - os << " PRIMARY KEY"; + // We will always follow a column. + // + os << "," << endl + << endl; + + create (pk); } virtual void - reference (semantics::data_member& m) + create (sema_rel::primary_key& pk) { - if (semantics::class_* c = object_pointer (member_utype (m, prefix_))) - { - os << " REFERENCES " << table_qname (*c) << " (" << - column_qname (*id_member (*c)) << ")"; - } - else if (prefix_ == "id") + 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) { - // Container id column references the object table. It also - // cascades on delete so that we can delete the object with - // a single delete statement (needed for erase_query()). - // - semantics::class_& c (*context::top_object); - - os << " REFERENCES " << table_qname (c) << " (" << - column_qname (*id_member (c)) << ") ON DELETE CASCADE"; + if (pk.contains_size () > 1) + { + if (i != pk.contains_begin ()) + os << ","; + + os << endl + << " "; + } + + os << quote_id (i->column ().name ()); } - } - protected: - void - default_ (semantics::data_member&); + os << ")"; + } protected: - string prefix_; + schema_format format_; + create_table& create_table_; }; - struct create_common: virtual context + struct create_foreign_key: trav_rel::foreign_key, virtual context { - virtual void - create_table_pre (string const& table) - { - os << "CREATE TABLE " << quote_id (table) << " (" << endl; - } + typedef create_foreign_key base; - virtual void - create_table_post () + create_foreign_key (schema_format f, create_table& ct) + : format_ (f), create_table_ (ct) { - os << ")" << endl; } virtual void - create_index (string const& table, string const& column) + traverse (sema_rel::foreign_key& fk) { - os << "CREATE INDEX " << quote_id (table + '_' + column) << endl - << " ON " << quote_id (table) << " (" << quote_id (column) << ")" + // We will always follow a column or another key. + // + os << "," << endl << endl; - } - }; - - struct member_create: object_members_base, common, virtual create_common - { - typedef member_create base; - member_create (emitter& e, ostream& os, std::vector<tables>& t) - : object_members_base (false, true, false), - common (e, os), - tables_ (t) - { - } - - void - pass (unsigned short p) - { - pass_ = p; + create (fk); } virtual void - traverse_container (semantics::data_member& m, semantics::type& t) + create (sema_rel::foreign_key& fk) { - using semantics::type; - using semantics::data_member; + using sema_rel::foreign_key; - // Ignore inverse containers of object pointers. - // - if (inverse (m, "value")) - return; + os << " CONSTRAINT " << quote_id (name (fk)) << endl + << " FOREIGN KEY ("; - container_kind_type ck (container_kind (t)); - type& vt (container_vt (t)); - - string const& name (table_name (m, table_prefix_)); - - if (tables_[pass_].count (name)) - return; - - pre_statement (); - create_table_pre (name); - - // object_id (simple value) - // - string id_name (column_name (m, "id", "object_id")); + for (foreign_key::contains_iterator i (fk.contains_begin ()); + i != fk.contains_end (); + ++i) { - instance<object_columns> oc ("id"); - oc->traverse_column (m, id_name, true); - } + if (fk.contains_size () > 1) + { + if (i != fk.contains_begin ()) + os << ","; - // index (simple value) - // - string index_name; - bool ordered (ck == ck_ordered && !unordered (m)); - if (ordered) - { - os << "," << endl; + os << endl + << " "; + } - instance<object_columns> oc ("index"); - index_name = column_name (m, "index", "index"); - oc->traverse_column (m, index_name, true); + os << quote_id (i->column ().name ()); } - // key (simple or composite value) - // - if (ck == ck_map || ck == ck_multimap) - { - type& kt (container_kt (t)); + os << ")" << endl + << " REFERENCES " << quote_id (fk.referenced_table ()) << " ("; - os << "," << endl; + foreign_key::columns const& refs (fk.referenced_columns ()); - if (semantics::class_* ckt = composite_wrapper (kt)) - { - instance<object_columns> oc; - oc->traverse (m, *ckt, "key", "key"); - } - else + for (foreign_key::columns::const_iterator i (refs.begin ()); + i != refs.end (); + ++i) + { + if (refs.size () > 1) { - instance<object_columns> oc ("key"); - string const& name (column_name (m, "key", "key")); - oc->traverse_column (m, name, true); + if (i != refs.begin ()) + os << ","; + + os << endl + << " "; } + + os << quote_id (*i); } - // value (simple or composite value) - // - { - os << "," << endl; + os << ")"; - if (semantics::class_* cvt = composite_wrapper (vt)) - { - instance<object_columns> oc; - oc->traverse (m, *cvt, "value", "value"); - } - else - { - instance<object_columns> oc ("value"); - string const& name (column_name (m, "value", "value")); - oc->traverse_column (m, name, true); - } - } + if (fk.on_delete () != foreign_key::no_action) + on_delete (fk.on_delete ()); - create_table_post (); - post_statement (); + if (fk.deferred ()) + deferred (); + } - tables_[pass_].insert (name); + virtual string + name (sema_rel::foreign_key& fk) + { + return fk.name (); + } - // Create indexes. - // - pre_statement (); - create_index (name, id_name); - post_statement (); + virtual void + on_delete (sema_rel::foreign_key::action a) + { + using sema_rel::foreign_key; - if (ordered) + switch (a) { - pre_statement (); - create_index (name, index_name); - post_statement (); + case foreign_key::cascade: + { + os << endl + << " ON DELETE CASCADE"; + break; + } + case foreign_key::no_action: + break; } } + virtual void + deferred () + { + os << endl + << " DEFERRABLE INITIALLY DEFERRED"; + } + protected: - std::vector<tables>& tables_; - unsigned short pass_; + schema_format format_; + create_table& create_table_; }; - struct class_create: traversal::class_, common, virtual create_common + struct create_table: trav_rel::table, common { - typedef class_create base; + typedef create_table base; - class_create (emitter& e) - : common (e, os_), os_ (e), member_create_ (e, os_, tables_) + create_table (emitter_type& e, ostream& os, schema_format f) + : common (e, os), format_ (f) { - tables_.push_back (tables ()); // Dummy entry. } - class_create (class_create const& x) - : root_context (), //@@ -Wextra - context (), - common (x.e_, os_), - os_ (x.e_), - member_create_ (x.e_, os_, tables_) + virtual void + create_pre (string const& table) { - tables_.push_back (tables ()); // Dummy entry. + os << "CREATE TABLE " << quote_id (table) << " (" << endl; + } + + virtual void + create_post () + { + os << ")" << endl; + } + + virtual void + traverse (sema_rel::table& t) + { + // By default we do everything in a single pass. But some + // databases may require the second pass. + // + if (pass_ > 1) + return; + + pre_statement (); + create_pre (t.name ()); + + instance<create_column> c (format_, *this); + instance<create_primary_key> pk (format_, *this); + instance<create_foreign_key> fk (format_, *this); + trav_rel::names n; + + n >> c; + n >> pk; + n >> fk; + + names (t, n); + + create_post (); + post_statement (); } void pass (unsigned short p) { pass_ = p; + } - if (tables_.size () == pass_) - tables_.push_back (tables ()); + protected: + schema_format format_; + unsigned short pass_; + }; - member_create_->pass (p); + struct create_index: trav_rel::index, common + { + typedef create_index base; + + create_index (emitter_type& e, ostream& os, schema_format f) + : common (e, os), format_ (f) + { } virtual void - traverse (type& c) + traverse (sema_rel::index& in) { // By default we do everything in a single pass. But some // databases may require the second pass. @@ -575,43 +579,83 @@ namespace relational if (pass_ > 1) return; - if (c.file () != unit.file ()) - return; + pre_statement (); + create (in); + post_statement (); + } - if (!object (c) || abstract (c)) - return; + virtual void + create (sema_rel::index& in) + { + using sema_rel::index; - string const& name (table_name (c)); + os << "CREATE INDEX " << quote_id (in.name ()) << endl + << " ON " << quote_id (in.table ().name ()) << " ("; - // If the table with this name was already created, assume the - // user knows what they are doing and skip it. - // - if (tables_[pass_].count (name)) - return; + for (index::contains_iterator i (in.contains_begin ()); + i != in.contains_end (); + ++i) + { + if (in.contains_size () > 1) + { + if (i != in.contains_begin ()) + os << ","; - pre_statement (); - create_table_pre (name); + os << endl + << " "; + } - { - instance<object_columns> oc; - oc->traverse (c); + os << quote_id (i->column ().name ()); } - create_table_post (); - post_statement (); + os << ")" << endl; + } - tables_[pass_].insert (name); + void + pass (unsigned short p) + { + pass_ = p; + } - // Create tables for members. - // - member_create_->traverse (c); + protected: + schema_format format_; + unsigned short pass_; + }; + + struct create_model: trav_rel::model, common + { + typedef create_model base; + + create_model (emitter_type& e, ostream& os, schema_format f) + : common (e, os), format_ (f) + { + } + + // This version is only called for file schema. + // + virtual void + traverse (sema_rel::model& m) + { + traverse (m.names_begin (), m.names_end ()); + } + + virtual void + traverse (sema_rel::model::names_iterator begin, + sema_rel::model::names_iterator end) + { + for (; begin != end; ++begin) + dispatch (*begin); + } + + void + pass (unsigned short p) + { + pass_ = p; } protected: - emitter_ostream os_; + schema_format format_; unsigned short pass_; - std::vector<tables> tables_; // Seperate table for each pass. - instance<member_create> member_create_; }; } } diff --git a/odb/relational/source.hxx b/odb/relational/source.hxx index a216653..9b04ae4 100644 --- a/odb/relational/source.hxx +++ b/odb/relational/source.hxx @@ -2098,6 +2098,10 @@ namespace relational query_parameters& qp_; }; + //@@ (im)-perfect forwarding. + // + static schema_format format_embedded (schema_format::embedded); + // // struct class_: traversal::class_, virtual context @@ -2110,8 +2114,13 @@ namespace relational bind_id_member_ ("id_"), init_id_image_member_ ("id_", "id"), init_id_value_member_ ("id"), - schema_drop_ (schema_emitter_), - schema_create_ (schema_emitter_) + stream_ (emitter_), + drop_model_ (emitter_, stream_, format_embedded), + drop_table_ (emitter_, stream_, format_embedded), + drop_index_ (emitter_, stream_, format_embedded), + create_model_ (emitter_, stream_, format_embedded), + create_table_ (emitter_, stream_, format_embedded), + create_index_ (emitter_, stream_, format_embedded) { init (); } @@ -2124,8 +2133,13 @@ namespace relational bind_id_member_ ("id_"), init_id_image_member_ ("id_", "id"), init_id_value_member_ ("id"), - schema_drop_ (schema_emitter_), - schema_create_ (schema_emitter_) + stream_ (emitter_), + drop_model_ (emitter_, stream_, format_embedded), + drop_table_ (emitter_, stream_, format_embedded), + drop_index_ (emitter_, stream_, format_embedded), + create_model_ (emitter_, stream_, format_embedded), + create_table_ (emitter_, stream_, format_embedded), + create_index_ (emitter_, stream_, format_embedded) { init (); } @@ -2147,6 +2161,17 @@ namespace relational init_value_base_inherits_ >> init_value_base_; init_value_member_names_ >> init_value_member_; + + if (embedded_schema) + { + drop_model_ >> drop_names_; + drop_names_ >> drop_table_; + drop_names_ >> drop_index_; + + create_model_ >> create_names_; + create_names_ >> create_table_; + create_names_ >> create_index_; + } } virtual void @@ -3035,6 +3060,9 @@ namespace relational virtual void line (const string& l) { + if (l.empty ()) + return; // Ignore empty lines. + if (first_) { first_ = false; @@ -3088,6 +3116,17 @@ namespace relational virtual void schema (type& c) { + typedef sema_rel::model::names_iterator iterator; + + iterator begin (c.get<iterator> ("model-range-first")); + iterator end (c.get<iterator> ("model-range-last")); + + if (begin == model->names_end ()) + return; // This class doesn't have any model entities (e.g., + // a second class mapped to the same table). + + ++end; // Transform the range from [begin, end] to [begin, end). + string const& type (c.fq_name ()); string traits ("access::object_traits< " + type + " >"); @@ -3110,10 +3149,14 @@ namespace relational for (unsigned short pass (1); pass < 3; ++pass) { - schema_emitter_.pass (pass); - schema_drop_->pass (pass); - schema_drop_->traverse (c); - close = close || !schema_emitter_.empty (); + emitter_.pass (pass); + drop_model_->pass (pass); + drop_table_->pass (pass); + drop_index_->pass (pass); + + drop_model_->traverse (begin, end); + + close = close || !emitter_.empty (); } if (close) // Close the last case and the switch block. @@ -3134,10 +3177,14 @@ namespace relational for (unsigned short pass (1); pass < 3; ++pass) { - schema_emitter_.pass (pass); - schema_create_->pass (pass); - schema_create_->traverse (c); - close = close || !schema_emitter_.empty (); + emitter_.pass (pass); + create_model_->pass (pass); + create_table_->pass (pass); + create_index_->pass (pass); + + create_model_->traverse (begin, end); + + close = close || !emitter_.empty (); } if (close) // Close the last case and the switch block. @@ -4050,9 +4097,18 @@ namespace relational traversal::names init_value_member_names_; instance<init_value_member> init_id_value_member_; - schema_emitter schema_emitter_; - instance<schema::class_drop> schema_drop_; - instance<schema::class_create> schema_create_; + schema_emitter emitter_; + emitter_ostream stream_; + + trav_rel::names drop_names_; + instance<schema::drop_model> drop_model_; + instance<schema::drop_table> drop_table_; + instance<schema::drop_index> drop_index_; + + trav_rel::names create_names_; + instance<schema::create_model> create_model_; + instance<schema::create_table> create_table_; + instance<schema::create_index> create_index_; }; struct include: virtual context diff --git a/odb/relational/sqlite/context.cxx b/odb/relational/sqlite/context.cxx index 466c5b9..a769fac 100644 --- a/odb/relational/sqlite/context.cxx +++ b/odb/relational/sqlite/context.cxx @@ -65,16 +65,19 @@ namespace relational } context:: - context (ostream& os, semantics::unit& u, options_type const& ops) + context (ostream& os, + semantics::unit& u, + options_type const& ops, + sema_rel::model* m) : root_context (os, u, ops, data_ptr (new (shared) data (os))), - base_context (static_cast<data*> (root_context::data_.get ())), + base_context (static_cast<data*> (root_context::data_.get ()), m), data_ (static_cast<data*> (base_context::data_)) { assert (current_ == 0); current_ = this; - data_->generate_grow_ = true; - data_->need_alias_as_ = true; + generate_grow = true; + need_alias_as = true; data_->bind_vector_ = "sqlite::bind*"; data_->truncated_vector_ = "bool*"; @@ -222,8 +225,8 @@ namespace relational { struct sql_parser { - sql_parser (semantics::data_member& m, std::string const& sql) - : m_ (m), l_ (sql) + sql_parser (std::string const& sql) + : l_ (sql) { } @@ -269,27 +272,20 @@ namespace relational } else { - cerr << m_.file () << ":" << m_.line () << ":" << m_.column () - << ": error: expected SQLite type name instead of '" - << t << "'" << endl; - throw operation_failed (); + throw context::invalid_sql_type ( + "expected SQLite type name instead of '" + t.string () + + "'"); } } } catch (sql_lexer::invalid_input const& e) { - cerr << m_.file () << ":" << m_.line () << ":" << m_.column () - << ": error: invalid SQLite type declaration: " << e.message - << endl; - throw operation_failed (); + throw context::invalid_sql_type ( + "invalid SQLite type declaration: " + e.message); } if (ids_.empty ()) - { - cerr << m_.file () << ":" << m_.line () << ":" << m_.column () - << ": error: expected SQLite type name" << endl; - throw operation_failed (); - } + throw context::invalid_sql_type ("expected SQLite type name"); sql_type r; @@ -322,9 +318,8 @@ namespace relational r.type = sql_type::TEXT; else { - cerr << m_.file () << ":" << m_.line () << ":" << m_.column () - << " error: unknown SQLite type '" << id << "'" << endl; - throw operation_failed (); + throw context::invalid_sql_type ( + "unknown SQLite type '" + id + "'"); } } @@ -343,10 +338,8 @@ namespace relational if (t.type () == sql_token::t_eos) { - cerr << m_.file () << ":" << m_.line () << ":" << m_.column () - << ": error: missing ')' in SQLite type declaration" - << endl; - throw operation_failed (); + throw context::invalid_sql_type ( + "missing ')' in SQLite type declaration"); } } } @@ -368,7 +361,6 @@ namespace relational typedef vector<string> identifiers; private: - semantics::data_member& m_; sql_lexer l_; identifiers ids_; }; @@ -383,11 +375,27 @@ namespace relational if (!m.count (key)) { - sql_parser p (m, column_type (m, kp)); - m.set (key, p.parse ()); + try + { + m.set (key, parse_sql_type (column_type (m, kp))); + } + catch (invalid_sql_type const& e) + { + cerr << m.file () << ":" << m.line () << ":" << m.column () + << ": error: " << e.message () << endl; + + throw operation_failed (); + } } return m.get<sql_type> (key); } + + sql_type context:: + parse_sql_type (string const& t) + { + sql_parser p (t); + return p.parse (); + } } } diff --git a/odb/relational/sqlite/context.hxx b/odb/relational/sqlite/context.hxx index b5c3d85..de4e4c7 100644 --- a/odb/relational/sqlite/context.hxx +++ b/odb/relational/sqlite/context.hxx @@ -37,6 +37,21 @@ namespace relational column_sql_type (semantics::data_member&, string const& key_prefix = string ()); + public: + struct invalid_sql_type + { + invalid_sql_type (string const& message): message_ (message) {} + + string const& + message () const {return message_;} + + private: + string message_; + }; + + static sql_type + parse_sql_type (string const&); + protected: virtual bool grow_impl (semantics::class_&); @@ -55,7 +70,10 @@ namespace relational virtual ~context (); context (); - context (std::ostream&, semantics::unit&, options_type const&); + context (std::ostream&, + semantics::unit&, + options_type const&, + sema_rel::model*); static context& current () diff --git a/odb/relational/sqlite/model.cxx b/odb/relational/sqlite/model.cxx new file mode 100644 index 0000000..1324255 --- /dev/null +++ b/odb/relational/sqlite/model.cxx @@ -0,0 +1,58 @@ +// file : odb/relational/sqlite/model.cxx +// author : Boris Kolpackov <boris@codesynthesis.com> +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// 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 + default_enum (semantics::data_member& m, tree en, string const&) + { + // Make sure the column is mapped to INTEGER. + // + if (column_sql_type (m).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 (); + } + }; + entry<object_columns> object_columns_; + } + } +} diff --git a/odb/relational/sqlite/schema.cxx b/odb/relational/sqlite/schema.cxx index 589ce7b..6562905 100644 --- a/odb/relational/sqlite/schema.cxx +++ b/odb/relational/sqlite/schema.cxx @@ -20,66 +20,20 @@ namespace relational // Create. // - struct object_columns: relational::object_columns, context + struct create_column: relational::create_column, context { - object_columns (base const& x): base (x) {} + create_column (base const& x): base (x) {} virtual void - default_enum (semantics::data_member& m, tree en, string const&) + auto_ (sema_rel::column&) { - // Make sure the column is mapped to INTEGER. - // - if (column_sql_type (m).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))); - - if (e.enum_ ().unsigned_ ()) - os << " DEFAULT " << e.value (); + if (options.sqlite_lax_auto_id ()) + os << " /*AUTOINCREMENT*/"; else - os << " DEFAULT " << static_cast<long long> (e.value ()); + os << " AUTOINCREMENT"; } - - virtual void - constraints (semantics::data_member& m) - { - base::constraints (m); - - if (m.count ("auto")) - { - if (options.sqlite_lax_auto_id ()) - os << " /*AUTOINCREMENT*/"; - else - os << " AUTOINCREMENT"; - } - } - - virtual void - reference (semantics::data_member& m) - { - // In SQLite, by default, constraints are immediate. - // - if (semantics::class_* c = - object_pointer (member_utype (m, prefix_))) - { - os << " REFERENCES " << table_qname (*c) << " (" << - column_qname (*id_member (*c)) << ") " << - "DEFERRABLE INITIALLY DEFERRED"; - } - else - base::reference (m); - } - }; - entry<object_columns> object_columns_; + entry<create_column> create_column_; } } } |