summaryrefslogtreecommitdiff
path: root/odb/relational/mysql
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2011-10-24 16:32:51 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2011-10-24 16:32:51 +0200
commit08a47c70ad517b80b72914d47d547463f576bcd3 (patch)
tree8a6ab07cf05e8668ea3c91735dfe97e2a98f3f05 /odb/relational/mysql
parenta976183dc95a8b7a9bd7a308c3ea94f08982c426 (diff)
Generate database schema from database model instead of C++ model
We now first create the so-called database model from C++ model and then use that to generate the database schema. The new approach also adds more general support for primary/foreign keys, including multi- column keys. Finally, for MySQL we now generate out-of-line foreign key definitions. Because MySQL does not support deferred constraints checking, deferred foreign keys are written commented out, for documentation.
Diffstat (limited to 'odb/relational/mysql')
-rw-r--r--odb/relational/mysql/context.cxx96
-rw-r--r--odb/relational/mysql/context.hxx20
-rw-r--r--odb/relational/mysql/model.cxx111
-rw-r--r--odb/relational/mysql/schema.cxx171
4 files changed, 242 insertions, 156 deletions
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_;
}
}
}