aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2013-04-03 11:22:42 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2013-04-10 18:46:44 +0200
commitb0391e168b489811708ca7ba5f71a0eb67b46ffe (patch)
treece15fb5ce0998ec27696152054609bb5dd4f45c3
parente356a9112750e836197a8545bcf6cedad0c1ebe1 (diff)
Generate add/drop table migration statements
-rw-r--r--odb/generator.cxx672
-rw-r--r--odb/options.cli47
-rw-r--r--odb/relational/changelog.cxx35
-rw-r--r--odb/relational/generate.hxx12
-rw-r--r--odb/relational/mssql/schema.cxx63
-rw-r--r--odb/relational/mysql/schema.cxx88
-rw-r--r--odb/relational/oracle/schema.cxx82
-rw-r--r--odb/relational/pgsql/schema.cxx11
-rw-r--r--odb/relational/schema.cxx52
-rw-r--r--odb/relational/schema.hxx159
-rw-r--r--odb/semantics/relational/changelog.hxx6
-rw-r--r--odb/semantics/relational/changeset.cxx6
-rw-r--r--odb/semantics/relational/changeset.hxx53
13 files changed, 822 insertions, 464 deletions
diff --git a/odb/generator.cxx b/odb/generator.cxx
index 616190b..5c9e897 100644
--- a/odb/generator.cxx
+++ b/odb/generator.cxx
@@ -5,6 +5,7 @@
#include <cctype> // std::toupper, std::is{alpha,upper,lower}
#include <string>
#include <memory> // std::auto_ptr
+#include <iomanip>
#include <fstream>
#include <sstream>
#include <iostream>
@@ -23,6 +24,7 @@
#include <odb/generator.hxx>
#include <odb/semantics/relational/model.hxx>
+#include <odb/semantics/relational/changeset.hxx>
#include <odb/semantics/relational/changelog.hxx>
#include <odb/generate.hxx>
@@ -34,6 +36,7 @@ using namespace cutl;
using semantics::path;
typedef vector<string> strings;
typedef vector<path> paths;
+typedef vector<cutl::shared_ptr<ofstream> > ofstreams;
namespace
{
@@ -48,13 +51,25 @@ namespace
" */\n\n";
void
- open (ifstream& ifs, string const& path)
+ open (ifstream& ifs, path const& p)
{
- ifs.open (path.c_str (), ios_base::in | ios_base::binary);
+ ifs.open (p.string ().c_str (), ios_base::in | ios_base::binary);
if (!ifs.is_open ())
{
- cerr << path << ": error: unable to open in read mode" << endl;
+ cerr << "error: unable to open '" << p << "' in read mode" << endl;
+ throw generator::failed ();
+ }
+ }
+
+ void
+ open (ofstream& ofs, path const& p, ios_base::openmode m = ios_base::out)
+ {
+ ofs.open (p.string ().c_str (), ios_base::out | m);
+
+ if (!ofs.is_open ())
+ {
+ cerr << "error: unable to open '" << p << "' in write mode" << endl;
throw generator::failed ();
}
}
@@ -70,12 +85,37 @@ namespace
}
void
- append (ostream& os, string const& file)
+ append (ostream& os, path const& file)
{
ifstream ifs;
open (ifs, file);
os << ifs.rdbuf ();
}
+
+ // Append prologue/interlude/epilogue.
+ //
+ void
+ append_logue (ostream& os,
+ database db,
+ database_map<vector<string> > const& text,
+ database_map<string> const& file,
+ char const* begin_comment,
+ char const* end_comment)
+ {
+ bool t (text.count (db) != 0);
+ bool f (file.count (db) != 0);
+
+ if (t || f)
+ {
+ os << begin_comment << endl;
+ if (t)
+ append (os, text[db]);
+ if (f)
+ append (os, path (file[db]));
+ os << end_comment << endl
+ << endl;
+ }
+ }
}
void generator::
@@ -85,6 +125,9 @@ generate (options const& ops,
path const& p,
paths const& inputs)
{
+ namespace sema_rel = semantics::relational;
+ using cutl::shared_ptr;
+
try
{
database db (ops.database ()[0]);
@@ -93,7 +136,8 @@ generate (options const& ops,
// First create the database model.
//
bool gen_schema (ops.generate_schema () && db != database::common);
- cutl::shared_ptr<semantics::relational::model> model;
+
+ shared_ptr<sema_rel::model> model;
if (gen_schema)
{
@@ -115,52 +159,21 @@ generate (options const& ops,
}
}
- // Output files.
+ // Input files.
//
- fs::auto_removes auto_rm;
-
path file (ops.input_name ().empty ()
? p.leaf ()
: path (ops.input_name ()).leaf ());
string base (file.base ().string ());
- string hxx_name (base + ops.odb_file_suffix ()[db] + ops.hxx_suffix ());
- string ixx_name (base + ops.odb_file_suffix ()[db] + ops.ixx_suffix ());
- string cxx_name (base + ops.odb_file_suffix ()[db] + ops.cxx_suffix ());
- string sch_name (base + ops.schema_file_suffix ()[db] + ops.cxx_suffix ());
- string sql_name (base + ops.sql_file_suffix ()[db] + ops.sql_suffix ());
-
- path hxx_path (hxx_name);
- path ixx_path (ixx_name);
- path cxx_path (cxx_name);
- path sch_path (sch_name);
- path sql_path (sql_name);
-
- if (!ops.output_dir ().empty ())
- {
- path dir (ops.output_dir ());
- hxx_path = dir / hxx_path;
- ixx_path = dir / ixx_path;
- cxx_path = dir / cxx_path;
- sch_path = dir / sch_path;
- sql_path = dir / sql_path;
- }
-
- path in_log_path, out_log_path;
+ path in_log_path;
path log_dir (ops.changelog_dir ().empty () ? "" : ops.changelog_dir ());
if (ops.changelog_in_specified ())
{
in_log_path = path (ops.changelog_in ());
- out_log_path = path (ops.changelog_out ());
- if (!log_dir.empty ())
- {
- if (!in_log_path.absolute ())
- in_log_path = log_dir / in_log_path;
-
- if (!out_log_path.absolute ())
- out_log_path = log_dir / out_log_path;
- }
+ if (!log_dir.empty () && !in_log_path.absolute ())
+ in_log_path = log_dir / in_log_path;
}
else if (ops.changelog_specified ())
{
@@ -168,8 +181,6 @@ generate (options const& ops,
if (!in_log_path.absolute () && !log_dir.empty ())
in_log_path = log_dir / in_log_path;
-
- out_log_path = in_log_path;
}
else
{
@@ -181,23 +192,38 @@ generate (options const& ops,
in_log_path = log_dir / in_log_path;
else
in_log_path = p.directory () / in_log_path; // Use input directory.
-
- out_log_path = in_log_path;
}
// Load the old changelog and generate a new one.
//
- bool gen_log (gen_schema && unit.count ("model-version") != 0);
- cutl::shared_ptr<semantics::relational::changelog> changelog;
- cutl::shared_ptr<semantics::relational::changelog> old_changelog;
+ bool gen_changelog (gen_schema && unit.count ("model-version") != 0);
+ cutl::shared_ptr<sema_rel::changelog> changelog;
+ cutl::shared_ptr<sema_rel::changelog> old_changelog;
string old_changelog_xml;
- if (gen_log)
+ path out_log_path;
+ if (ops.changelog_out_specified ())
{
- ifstream log (in_log_path.string ().c_str (),
- ios_base::in | ios_base::binary);
+ out_log_path = path (ops.changelog_out ());
- if (log.is_open ())
+ if (!log_dir.empty () && !out_log_path.absolute ())
+ out_log_path = log_dir / out_log_path;
+ }
+ else
+ out_log_path = in_log_path;
+
+ if (gen_changelog)
+ {
+ ifstream log;
+
+ // Unless we are forced to re-initialize the changelog, load the
+ // old one.
+ //
+ if (!ops.init_changelog ())
+ log.open (in_log_path.string ().c_str (),
+ ios_base::in | ios_base::binary);
+
+ if (log.is_open ()) // The changelog might not exist.
{
try
{
@@ -224,8 +250,7 @@ generate (options const& ops,
is.exceptions (ios_base::badbit | ios_base::failbit);
xml::parser p (is, in_log_path.string ());
- old_changelog.reset (
- new (shared) semantics::relational::changelog (p));
+ old_changelog.reset (new (shared) sema_rel::changelog (p));
}
catch (const ios_base::failure& e)
{
@@ -244,25 +269,83 @@ generate (options const& ops,
unit.get<model_version> ("model-version"),
old_changelog.get (),
in_log_path.string (),
- out_log_path.string ());
+ out_log_path.string (),
+ ops.init_changelog ());
}
+ // Output files.
//
- //
- bool gen_cxx (!ops.generate_schema_only ());
+ fs::auto_removes auto_rm;
- ofstream hxx;
- if (gen_cxx)
+ string hxx_name (base + ops.odb_file_suffix ()[db] + ops.hxx_suffix ());
+ string ixx_name (base + ops.odb_file_suffix ()[db] + ops.ixx_suffix ());
+ string cxx_name (base + ops.odb_file_suffix ()[db] + ops.cxx_suffix ());
+ string sch_name (base + ops.schema_file_suffix ()[db] + ops.cxx_suffix ());
+ string sql_name (base + ops.sql_file_suffix ()[db] + ops.sql_suffix ());
+
+ path hxx_path (hxx_name);
+ path ixx_path (ixx_name);
+ path cxx_path (cxx_name);
+ path sch_path (sch_name);
+ path sql_path (sql_name);
+ paths mig_pre_paths;
+ paths mig_post_paths;
+
+ bool gen_migration (gen_changelog && !ops.suppress_migration ());
+ bool gen_sql_migration (
+ gen_migration && ops.schema_format ()[db].count (schema_format::sql));
+
+ if (gen_sql_migration)
{
- hxx.open (hxx_path.string ().c_str (), ios_base::out);
+ for (sema_rel::changelog::contains_changeset_iterator i (
+ changelog->contains_changeset_begin ());
+ i != changelog->contains_changeset_end (); ++i)
+ {
+ sema_rel::changeset& cs (i->changeset ());
+
+ // Default format: %N[-D%]-%3V-{pre|post}.sql
+ //
+ string n (base);
+
+ if (md != multi_database::disabled)
+ n += '-' + db.string ();
+
+ ostringstream os;
+ os << setfill ('0') << setw (3) << cs.version ();
+ n += '-' + os.str ();
- if (!hxx.is_open ())
+ mig_pre_paths.push_back (path (n + "-pre" + ops.sql_suffix ()));
+ mig_post_paths.push_back (path (n + "-post" + ops.sql_suffix ()));
+ }
+ }
+
+ if (!ops.output_dir ().empty ())
+ {
+ path dir (ops.output_dir ());
+ hxx_path = dir / hxx_path;
+ ixx_path = dir / ixx_path;
+ cxx_path = dir / cxx_path;
+ sch_path = dir / sch_path;
+ sql_path = dir / sql_path;
+
+ if (gen_sql_migration)
{
- cerr << "error: unable to open '" << hxx_path << "' in write mode"
- << endl;
- throw failed ();
+ for (paths::size_type i (0); i < mig_pre_paths.size (); ++i)
+ {
+ mig_pre_paths[i] = dir / mig_pre_paths[i];
+ mig_post_paths[i] = dir / mig_post_paths[i];
+ }
}
+ }
+ //
+ //
+ bool gen_cxx (!ops.generate_schema_only ());
+
+ ofstream hxx;
+ if (gen_cxx)
+ {
+ open (hxx, hxx_path);
auto_rm.add (hxx_path);
}
@@ -271,15 +354,7 @@ generate (options const& ops,
ofstream ixx;
if (gen_cxx)
{
- ixx.open (ixx_path.string ().c_str (), ios_base::out);
-
- if (!ixx.is_open ())
- {
- cerr << "error: unable to open '" << ixx_path << "' in write mode"
- << endl;
- throw failed ();
- }
-
+ open (ixx, ixx_path);
auto_rm.add (ixx_path);
}
@@ -288,16 +363,22 @@ generate (options const& ops,
ofstream cxx;
if (gen_cxx && (db != database::common || md == multi_database::dynamic))
{
- cxx.open (cxx_path.string ().c_str (), ios_base::out);
+ open (cxx, cxx_path);
+ auto_rm.add (cxx_path);
+ }
- if (!cxx.is_open ())
- {
- cerr << "error: unable to open '" << cxx_path << "' in write mode"
- << endl;
- throw failed ();
- }
+ //
+ //
+ bool gen_sep_schema (
+ gen_cxx &&
+ gen_schema &&
+ ops.schema_format ()[db].count (schema_format::separate));
- auto_rm.add (cxx_path);
+ ofstream sch;
+ if (gen_sep_schema)
+ {
+ open (sch, sch_path);
+ auto_rm.add (sch_path);
}
//
@@ -307,41 +388,31 @@ generate (options const& ops,
ofstream sql;
if (gen_sql_schema)
{
- sql.open (sql_path.string ().c_str (), ios_base::out);
-
- if (!sql.is_open ())
- {
- cerr << "error: unable to open '" << sql_path << "' in write mode"
- << endl;
- throw failed ();
- }
-
+ open (sql, sql_path);
auto_rm.add (sql_path);
}
//
//
- bool gen_sep_schema (
- gen_cxx &&
- gen_schema &&
- ops.schema_format ()[db].count (schema_format::separate));
-
- ofstream sch;
- if (gen_sep_schema)
+ ofstreams mig_pre, mig_post;
+ if (gen_sql_migration)
{
- sch.open (sch_path.string ().c_str (), ios_base::out);
-
- if (!sch.is_open ())
+ for (paths::size_type i (0); i < mig_pre_paths.size (); ++i)
{
- cerr << "error: unable to open '" << sch_path << "' in write mode"
- << endl;
- throw failed ();
- }
+ shared_ptr<ofstream> pre (new (shared) ofstream);
+ shared_ptr<ofstream> post (new (shared) ofstream);
- auto_rm.add (sch_path);
+ open (*pre, mig_pre_paths[i]);
+ auto_rm.add (mig_pre_paths[i]);
+ mig_pre.push_back (pre);
+
+ open (*post, mig_post_paths[i]);
+ auto_rm.add (mig_post_paths[i]);
+ mig_post.push_back (post);
+ }
}
- // Print C++ headers.
+ // Print output file headers.
//
if (gen_cxx)
{
@@ -358,6 +429,15 @@ generate (options const& ops,
if (gen_sql_schema)
sql << sql_file_header;
+ if (gen_sql_migration)
+ {
+ for (ofstreams::size_type i (0); i < mig_pre.size (); ++i)
+ {
+ *mig_pre[i] << sql_file_header;
+ *mig_post[i] << sql_file_header;
+ }
+ }
+
typedef compiler::ostream_filter<compiler::cxx_indenter, char> ind_filter;
typedef compiler::ostream_filter<compiler::sloc_counter, char> sloc_filter;
@@ -398,23 +478,12 @@ generate (options const& ops,
// Copy prologue.
//
- {
- bool p (ops.hxx_prologue ().count (db) != 0);
- bool pf (ops.hxx_prologue_file ().count (db) != 0);
-
- if (p || pf)
- {
- hxx << "// Begin prologue." << endl
- << "//" << endl;
- if (p)
- append (hxx, ops.hxx_prologue ()[db]);
- if (pf)
- append (hxx, ops.hxx_prologue_file ()[db]);
- hxx << "//" << endl
- << "// End prologue." << endl
- << endl;
- }
- }
+ append_logue (hxx,
+ db,
+ ops.hxx_prologue (),
+ ops.hxx_prologue_file (),
+ "// Begin prologue.\n//",
+ "//\n// End prologue.");
// Include main file(s).
//
@@ -470,23 +539,12 @@ generate (options const& ops,
// Copy epilogue.
//
- {
- bool e (ops.hxx_epilogue ().count (db) != 0);
- bool ef (ops.hxx_epilogue_file ().count (db) != 0);
-
- if (e || ef)
- {
- hxx << "// Begin epilogue." << endl
- << "//" << endl;
- if (e)
- append (hxx, ops.hxx_epilogue ()[db]);
- if (ef)
- append (hxx, ops.hxx_epilogue_file ()[db]);
- hxx << "//" << endl
- << "// End epilogue." << endl
- << endl;
- }
- }
+ append_logue (hxx,
+ db,
+ ops.hxx_epilogue (),
+ ops.hxx_epilogue_file (),
+ "// Begin epilogue.\n//",
+ "//\n// End epilogue.");
hxx << "#include <odb/post.hxx>" << endl
<< endl;
@@ -510,23 +568,12 @@ generate (options const& ops,
// Copy prologue.
//
- {
- bool p (ops.ixx_prologue ().count (db) != 0);
- bool pf (ops.ixx_prologue_file ().count (db) != 0);
-
- if (p || pf)
- {
- ixx << "// Begin prologue." << endl
- << "//" << endl;
- if (p)
- append (ixx, ops.ixx_prologue ()[db]);
- if (pf)
- append (ixx, ops.ixx_prologue_file ()[db]);
- ixx << "//" << endl
- << "// End prologue." << endl
- << endl;
- }
- }
+ append_logue (ixx,
+ db,
+ ops.ixx_prologue (),
+ ops.ixx_prologue_file (),
+ "// Begin prologue.\n//",
+ "//\n// End prologue.");
{
// We don't want to indent prologues/epilogues.
@@ -557,23 +604,12 @@ generate (options const& ops,
// Copy epilogue.
//
- {
- bool e (ops.ixx_epilogue ().count (db) != 0);
- bool ef (ops.ixx_epilogue_file ().count (db) != 0);
-
- if (e || ef)
- {
- ixx << "// Begin epilogue." << endl
- << "//" << endl;
- if (e)
- append (ixx, ops.ixx_epilogue ()[db]);
- if (ef)
- append (ixx, ops.ixx_epilogue_file ()[db]);
- ixx << "//" << endl
- << "// End epilogue." << endl
- << endl;
- }
- }
+ append_logue (ixx,
+ db,
+ ops.ixx_epilogue (),
+ ops.ixx_epilogue_file (),
+ "// Begin epilogue.\n//",
+ "//\n// End epilogue.");
if (ops.show_sloc ())
cerr << ixx_name << ": " << sloc.stream ().count () << endl;
@@ -595,23 +631,12 @@ generate (options const& ops,
// Copy prologue.
//
- {
- bool p (ops.cxx_prologue ().count (db) != 0);
- bool pf (ops.cxx_prologue_file ().count (db) != 0);
-
- if (p || pf)
- {
- cxx << "// Begin prologue." << endl
- << "//" << endl;
- if (p)
- append (cxx, ops.cxx_prologue ()[db]);
- if (pf)
- append (cxx, ops.cxx_prologue_file ()[db]);
- cxx << "//" << endl
- << "// End prologue." << endl
- << endl;
- }
- }
+ append_logue (cxx,
+ db,
+ ops.cxx_prologue (),
+ ops.cxx_prologue_file (),
+ "// Begin prologue.\n//",
+ "//\n// End prologue.");
// Include query columns implementations for explicit instantiations.
//
@@ -665,23 +690,12 @@ generate (options const& ops,
// Copy epilogue.
//
- {
- bool e (ops.cxx_epilogue ().count (db) != 0);
- bool ef (ops.cxx_epilogue_file ().count (db) != 0);
-
- if (e || ef)
- {
- cxx << "// Begin epilogue." << endl
- << "//" << endl;
- if (e)
- append (cxx, ops.cxx_epilogue ()[db]);
- if (ef)
- append (cxx, ops.cxx_epilogue_file ()[db]);
- cxx << "//" << endl
- << "// End epilogue." << endl
- << endl;
- }
- }
+ append_logue (cxx,
+ db,
+ ops.cxx_epilogue (),
+ ops.cxx_epilogue_file (),
+ "// Begin epilogue.\n//",
+ "//\n// End epilogue.");
cxx << "#include <odb/post.hxx>" << endl;
@@ -705,23 +719,12 @@ generate (options const& ops,
// Copy prologue.
//
- {
- bool p (ops.schema_prologue ().count (db) != 0);
- bool pf (ops.schema_prologue_file ().count (db) != 0);
-
- if (p || pf)
- {
- sch << "// Begin prologue." << endl
- << "//" << endl;
- if (p)
- append (sch, ops.schema_prologue ()[db]);
- if (pf)
- append (sch, ops.schema_prologue_file ()[db]);
- sch << "//" << endl
- << "// End prologue." << endl
- << endl;
- }
- }
+ append_logue (sch,
+ db,
+ ops.schema_prologue (),
+ ops.schema_prologue_file (),
+ "// Begin prologue.\n//",
+ "//\n// End prologue.");
sch << "#include " << ctx->process_include_path (hxx_name) << endl
<< endl;
@@ -733,10 +736,6 @@ generate (options const& ops,
switch (db)
{
- case database::common:
- {
- assert (false);
- }
case database::mssql:
case database::mysql:
case database::oracle:
@@ -746,28 +745,19 @@ generate (options const& ops,
relational::schema_source::generate ();
break;
}
+ case database::common:
+ assert (false);
}
}
// Copy epilogue.
//
- {
- bool e (ops.schema_epilogue ().count (db) != 0);
- bool ef (ops.schema_epilogue_file ().count (db) != 0);
-
- if (e || ef)
- {
- sch << "// Begin epilogue." << endl
- << "//" << endl;
- if (e)
- append (sch, ops.schema_epilogue ()[db]);
- if (ef)
- append (sch, ops.schema_epilogue_file ()[db]);
- sch << "//" << endl
- << "// End epilogue." << endl
- << endl;
- }
- }
+ append_logue (sch,
+ db,
+ ops.schema_epilogue (),
+ ops.schema_epilogue_file (),
+ "// Begin epilogue.\n//",
+ "//\n// End epilogue.");
sch << "#include <odb/post.hxx>" << endl;
@@ -786,93 +776,154 @@ generate (options const& ops,
switch (db)
{
- case database::common:
- {
- assert (false);
- }
case database::mssql:
case database::mysql:
case database::oracle:
case database::pgsql:
case database::sqlite:
{
- relational::schema::generate_prologue ();
-
- // Copy prologue.
+ // Prologue.
//
- {
- bool p (ops.sql_prologue ().count (db) != 0);
- bool pf (ops.sql_prologue_file ().count (db) != 0);
-
- if (p || pf)
- {
- sql << "/* Begin prologue." << endl
- << " */" << endl;
- if (p)
- append (sql, ops.sql_prologue ()[db]);
- if (pf)
- append (sql, ops.sql_prologue_file ()[db]);
- sql << "/*" << endl
- << " * End prologue. */" << endl
- << endl;
- }
- }
+ relational::schema::generate_prologue ();
+ append_logue (sql,
+ db,
+ ops.sql_prologue (),
+ ops.sql_prologue_file (),
+ "/* Begin prologue.\n */",
+ "/*\n * End prologue. */");
if (!ops.omit_drop ())
relational::schema::generate_drop ();
- // Copy interlude.
+ // Interlude.
//
- {
- bool i (ops.sql_interlude ().count (db) != 0);
- bool ifl (ops.sql_interlude_file ().count (db) != 0);
-
- if (i || ifl)
- {
- sql << "/* Begin interlude." << endl
- << " */" << endl;
- if (i)
- append (sql, ops.sql_interlude ()[db]);
- if (ifl)
- append (sql, ops.sql_interlude_file ()[db]);
- sql << "/*" << endl
- << " * End interlude. */" << endl
- << endl;
- }
- }
+ append_logue (sql,
+ db,
+ ops.sql_interlude (),
+ ops.sql_interlude_file (),
+ "/* Begin interlude.\n */",
+ "/*\n * End interlude. */");
if (!ops.omit_create ())
relational::schema::generate_create ();
- // Copy epilogue.
+ // Epilogue.
//
- {
- bool e (ops.sql_epilogue ().count (db) != 0);
- bool ef (ops.sql_epilogue_file ().count (db) != 0);
+ append_logue (sql,
+ db,
+ ops.sql_epilogue (),
+ ops.sql_epilogue_file (),
+ "/* Begin epilogue.\n */",
+ "/*\n * End epilogue. */");
+ relational::schema::generate_epilogue ();
+
+ break;
+ }
+ case database::common:
+ assert (false);
+ }
+ }
+
+ // MIG
+ //
+ if (gen_sql_migration)
+ {
+ for (ofstreams::size_type i (0); i < mig_pre.size (); ++i)
+ {
+ sema_rel::changeset& cs (
+ changelog->contains_changeset_at (i).changeset ());
+
+ // pre
+ //
+ {
+ ofstream& mig (*mig_pre[i]);
+ auto_ptr<context> ctx (create_context (mig, unit, ops, fts, 0));
- if (e || ef)
+ switch (db)
+ {
+ case database::mssql:
+ case database::mysql:
+ case database::oracle:
+ case database::pgsql:
+ case database::sqlite:
{
- sql << "/* Begin epilogue." << endl
- << " */" << endl;
- if (e)
- append (sql, ops.sql_epilogue ()[db]);
- if (ef)
- append (sql, ops.sql_epilogue_file ()[db]);
- sql << "/*" << endl
- << " * End epilogue. */" << endl
- << endl;
+ // Prologue.
+ //
+ relational::schema::generate_prologue ();
+ append_logue (mig,
+ db,
+ ops.migration_prologue (),
+ ops.migration_prologue_file (),
+ "/* Begin prologue.\n */",
+ "/*\n * End prologue. */");
+
+ relational::schema::generate_migrate_pre (cs);
+
+ // Epilogue.
+ //
+ append_logue (mig,
+ db,
+ ops.migration_epilogue (),
+ ops.migration_epilogue_file (),
+ "/* Begin epilogue.\n */",
+ "/*\n * End epilogue. */");
+ relational::schema::generate_epilogue ();
+
+ break;
}
+ case database::common:
+ assert (false);
}
+ }
- relational::schema::generate_epilogue ();
- break;
+ // post
+ //
+ {
+ ofstream& mig (*mig_post[i]);
+ auto_ptr<context> ctx (create_context (mig, unit, ops, fts, 0));
+
+ switch (db)
+ {
+ case database::mssql:
+ case database::mysql:
+ case database::oracle:
+ case database::pgsql:
+ case database::sqlite:
+ {
+ // Prologue.
+ //
+ relational::schema::generate_prologue ();
+ append_logue (mig,
+ db,
+ ops.migration_prologue (),
+ ops.migration_prologue_file (),
+ "/* Begin prologue.\n */",
+ "/*\n * End prologue. */");
+
+ relational::schema::generate_migrate_post (cs);
+
+ // Epilogue.
+ //
+ append_logue (mig,
+ db,
+ ops.migration_epilogue (),
+ ops.migration_epilogue_file (),
+ "/* Begin epilogue.\n */",
+ "/*\n * End epilogue. */");
+ relational::schema::generate_epilogue ();
+
+ break;
+ }
+ case database::common:
+ assert (false);
+ }
}
}
}
- // Save the changelog if it changed.
+ // Save the changelog if it has changed.
//
- if (gen_log)
+ if (gen_changelog)
{
try
{
@@ -884,15 +935,8 @@ generate (options const& ops,
if (changelog_xml != old_changelog_xml)
{
- ofstream log (out_log_path.string ().c_str (),
- ios_base::out | ios_base::binary);
-
- if (!log.is_open ())
- {
- cerr << "error: unable to open '" << out_log_path << "' in " <<
- "write mode" << endl;
- throw failed ();
- }
+ ofstream log;
+ open (log, out_log_path, ios_base::binary);
if (old_changelog == 0)
auto_rm.add (out_log_path);
diff --git a/odb/options.cli b/odb/options.cli
index b086011..457c06f 100644
--- a/odb/options.cli
+++ b/odb/options.cli
@@ -112,7 +112,14 @@ class options
the generated C++ code. By default the SQL file is generated for
the MySQL, PostgreSQL, Oracle, and Microsoft SQL Server databases
and the schema is embedded into the C++ code for the SQLite database.
- Use the \cb{--schema-format} option to alter the default schema format."
+ Use the \cb{--schema-format} option to alter the default schema format.
+
+ If database schema evolution support is enabled (that is, the object
+ model version is specified), then this option also triggers the
+ generation of database schema migration statements, again either as
+ standalong SQL files or embedded into the generated C++ code. You can
+ suppress the generation of schema migration statements by specifying
+ the \cb{--suppress-migration} option."
};
bool --generate-schema-only
@@ -122,6 +129,11 @@ class options
for details)."
};
+ bool --suppress-migration
+ {
+ "Suppress the generation of database schema migration statements."
+ };
+
database_map<std::set< ::schema_format> > --schema-format
{
"<format>",
@@ -323,6 +335,13 @@ class options
options unless they are absolute paths."
};
+ bool --init-changelog
+ {
+ "Force re-initialization of the changelog even if one exists (all the
+ existing change history will be lost). This option is primarily useful
+ for automated testing."
+ };
+
database_map<std::string> --odb-file-suffix
{
"<suffix>",
@@ -428,6 +447,12 @@ class options
"Insert <text> at the beginning of the generated database schema file."
};
+ database_map<std::vector<std::string> > --migration-prologue
+ {
+ "<text>",
+ "Insert <text> at the beginning of the generated database migration file."
+ };
+
// Interludes.
//
database_map<std::vector<std::string> > --sql-interlude
@@ -469,6 +494,12 @@ class options
"Insert <text> at the end of the generated database schema file."
};
+ database_map<std::vector<std::string> > --migration-epilogue
+ {
+ "<text>",
+ "Insert <text> at the end of the generated database migration file."
+ };
+
// Prologue files.
//
database_map<std::string> --hxx-prologue-file
@@ -506,6 +537,13 @@ class options
database schema file."
};
+ database_map<std::string> --migration-prologue-file
+ {
+ "<f>",
+ "Insert the content of file <f> at the beginning of the generated database
+ migration file."
+ };
+
// Interlude files.
//
database_map<std::string> --sql-interlude-file
@@ -552,6 +590,13 @@ class options
schema file."
};
+ database_map<std::string> --migration-epilogue-file
+ {
+ "<f>",
+ "Insert the content of file <f> at the end of the generated database
+ migration file."
+ };
+
// ODB compilation prologue/epilogue.
//
database_map<std::vector<std::string> > --odb-prologue
diff --git a/odb/relational/changelog.cxx b/odb/relational/changelog.cxx
index 1d536fc..f576c79 100644
--- a/odb/relational/changelog.cxx
+++ b/odb/relational/changelog.cxx
@@ -654,7 +654,8 @@ namespace relational
model_version const& mv,
changelog* old,
std::string const& in_name,
- std::string const& out_name)
+ std::string const& out_name,
+ bool force_init)
{
cutl::shared_ptr<changelog> cl (new (shared) changelog);
graph& g (*cl);
@@ -672,8 +673,9 @@ namespace relational
throw operation_failed ();
}
- cerr << out_name << ": info: initializing changelog with base " <<
- "version " << mv.base << endl;
+ if (!force_init)
+ cerr << out_name << ": info: initializing changelog with base " <<
+ "version " << mv.base << endl;
if (mv.base == mv.current)
g.new_edge<contains_model> (*cl, g.new_node<model> (m, g));
@@ -687,7 +689,10 @@ namespace relational
changeset& c (diff (nm, m, *cl));
if (!c.names_empty ())
+ {
+ g.new_edge<alters_model> (c, nm);
g.new_edge<contains_changeset> (*cl, c);
+ }
}
return cl;
@@ -725,12 +730,11 @@ namespace relational
// changeset.
//
//
- model* base (bver == mv.base ? &g.new_node<model> (oldm, g) : 0);
+ model* last (&g.new_node<model> (oldm, g));
+ model* base (bver == mv.base ? last : 0);
if (base != 0)
g.new_edge<contains_model> (*cl, *base);
- model* last (&oldm);
-
for (changelog::contains_changeset_iterator i (
old->contains_changeset_begin ());
i != old->contains_changeset_end (); ++i)
@@ -743,7 +747,8 @@ namespace relational
if (cs.version () == mv.current)
break;
- last = &patch (*last, cs, g);
+ model& prev (*last);
+ last = &patch (prev, cs, g);
if (base == 0)
{
@@ -762,7 +767,10 @@ namespace relational
changeset& c (diff (*base, *last, *cl));
if (!c.names_empty ())
+ {
+ g.new_edge<alters_model> (c, *base);
g.new_edge<contains_changeset> (*cl, c);
+ }
continue;
}
@@ -780,6 +788,8 @@ namespace relational
? static_cast<qscope&> (*base) // Cannot be NULL.
: cl->contains_changeset_back ().changeset (),
g));
+
+ g.new_edge<alters_model> (c, prev);
g.new_edge<contains_changeset> (*cl, c);
}
@@ -794,7 +804,7 @@ namespace relational
{
// Fast-forward the latest model to the new base.
//
- base = last != &oldm ? last : &g.new_node<model> (oldm, g);
+ base = last;
base->version (mv.base);
}
@@ -835,10 +845,13 @@ namespace relational
//
if (mv.base != mv.current)
{
- changeset& cs (diff (*last, m, *cl));
+ changeset& c (diff (*last, m, *cl));
- if (!cs.names_empty ())
- g.new_edge<contains_changeset> (*cl, cs);
+ if (!c.names_empty ())
+ {
+ g.new_edge<alters_model> (c, *last);
+ g.new_edge<contains_changeset> (*cl, c);
+ }
}
return cl;
diff --git a/odb/relational/generate.hxx b/odb/relational/generate.hxx
index a50b999..5bd795e 100644
--- a/odb/relational/generate.hxx
+++ b/odb/relational/generate.hxx
@@ -10,6 +10,7 @@
#include <odb/context.hxx>
#include <odb/semantics/relational/model.hxx>
+#include <odb/semantics/relational/changeset.hxx>
#include <odb/semantics/relational/changelog.hxx>
namespace relational
@@ -53,7 +54,8 @@ namespace relational
model_version const&,
semantics::relational::changelog* old, // Can be NULL.
std::string const& in_name,
- std::string const& out_name);
+ std::string const& out_name,
+ bool force_init);
}
namespace schema
@@ -62,13 +64,19 @@ namespace relational
generate_prologue ();
void
+ generate_epilogue ();
+
+ void
generate_drop ();
void
generate_create ();
void
- generate_epilogue ();
+ generate_migrate_pre (semantics::relational::changeset&);
+
+ void
+ generate_migrate_post (semantics::relational::changeset&);
}
}
diff --git a/odb/relational/mssql/schema.cxx b/odb/relational/mssql/schema.cxx
index ca89d68..11467b3 100644
--- a/odb/relational/mssql/schema.cxx
+++ b/odb/relational/mssql/schema.cxx
@@ -45,7 +45,7 @@ namespace relational
drop_table (base const& x): base (x) {}
virtual void
- traverse (sema_rel::table&);
+ traverse (sema_rel::table&, bool);
private:
friend class drop_foreign_key;
@@ -55,8 +55,8 @@ namespace relational
struct drop_foreign_key: trav_rel::foreign_key, relational::common
{
- drop_foreign_key (drop_table& dt)
- : common (dt.emitter (), dt.stream ()), dt_ (dt)
+ drop_foreign_key (drop_table& dt, bool m)
+ : common (dt.emitter (), dt.stream ()), dt_ (dt), migration_ (m)
{
}
@@ -71,39 +71,52 @@ namespace relational
// If the table which we reference is droped before us, then
// we need to drop the constraint first. Similarly, if the
// referenced table is not part if this model, then assume
- // it is dropped before us.
+ // it is dropped before us. In migration we always do this
+ // first.
//
- sema_rel::qname const& rt (fk.referenced_table ());
sema_rel::table& t (dynamic_cast<sema_rel::table&> (fk.scope ()));
- sema_rel::model& m (dynamic_cast<sema_rel::model&> (t.scope ()));
- if (dt_.tables_.find (rt) != dt_.tables_.end () ||
- m.find (rt) == m.names_end ())
+ if (!migration_)
{
- pre_statement ();
+ sema_rel::qname const& rt (fk.referenced_table ());
+ sema_rel::model& m (dynamic_cast<sema_rel::model&> (t.scope ()));
+
+ if (dt_.tables_.find (rt) == dt_.tables_.end () &&
+ m.find (rt) != m.names_end ())
+ return;
+ }
+
+ pre_statement ();
+
+ if (!migration_)
os << "IF OBJECT_ID(" << quote_string (fk.name ()) << ", " <<
quote_string ("F") << ") IS NOT NULL" << endl
- << " ALTER TABLE " << quote_id (t.name ()) << " DROP" << endl
- << " CONSTRAINT " << quote_id (fk.name ()) << endl;
- post_statement ();
- }
+ << " ";
+
+ os << "ALTER TABLE " << quote_id (t.name ()) << " DROP" << endl
+ << (!migration_ ? " " : "") << " CONSTRAINT " <<
+ quote_id (fk.name ()) << endl;
+
+ post_statement ();
}
private:
drop_table& dt_;
+ bool migration_;
};
void drop_table::
- traverse (sema_rel::table& t)
+ traverse (sema_rel::table& t, bool migration)
{
qname const& table (t.name ());
if (pass_ == 1)
{
- // Drop constraints.
+ // Drop constraints. In migration this is always done on pass 1.
//
- tables_.insert (table); // Add it before to cover self-refs.
- drop_foreign_key fk (*this);
+ if (!migration)
+ tables_.insert (table); // Add it before to cover self-refs.
+ drop_foreign_key fk (*this, migration);
trav_rel::unames n (fk);
names (t, n);
}
@@ -114,9 +127,14 @@ namespace relational
// drop a table if it exists.
//
pre_statement ();
- os << "IF OBJECT_ID(" << quote_string (table.string ()) <<
- ", " << quote_string ("U") << ") IS NOT NULL" << endl
- << " DROP TABLE " << quote_id (table) << endl;
+
+ if (!migration)
+ os << "IF OBJECT_ID(" << quote_string (table.string ()) <<
+ ", " << quote_string ("U") << ") IS NOT NULL" << endl
+ << " ";
+
+ os << "DROP TABLE " << quote_id (table) << endl;
+
post_statement ();
}
}
@@ -258,7 +276,10 @@ namespace relational
{
if (pass_ == 1)
{
- tables_.insert (t.name ()); // Add it before to cover self-refs.
+ // In migration we always add foreign keys on pass 2.
+ //
+ if (!t.is_a<sema_rel::add_table> ())
+ tables_.insert (t.name ()); // Add it before to cover self-refs.
base::traverse (t);
return;
}
diff --git a/odb/relational/mysql/schema.cxx b/odb/relational/mysql/schema.cxx
index 3784c31..e216918 100644
--- a/odb/relational/mysql/schema.cxx
+++ b/odb/relational/mysql/schema.cxx
@@ -22,13 +22,12 @@ namespace relational
//
// Drop.
//
- /*
struct drop_table: relational::drop_table, context
{
drop_table (base const& x): base (x) {}
virtual void
- traverse (sema_rel::table&);
+ traverse (sema_rel::table&, bool migration);
private:
friend class drop_foreign_key;
@@ -38,8 +37,8 @@ namespace relational
struct drop_foreign_key: trav_rel::foreign_key, relational::common
{
- drop_foreign_key (drop_table& dt)
- : common (dt.emitter (), dt.stream ()), dt_ (dt)
+ drop_foreign_key (drop_table& dt, bool m)
+ : common (dt.emitter (), dt.stream ()), dt_ (dt), migration_ (m)
{
}
@@ -54,58 +53,86 @@ namespace relational
// If the table which we reference is droped before us, then
// we need to drop the constraint first. Similarly, if the
// referenced table is not part if this model, then assume
- // it is dropped before us.
+ // it is dropped before us. In migration we always do this
+ // first.
//
- sema_rel::qname const& rt (fk.referenced_table ());
sema_rel::table& t (dynamic_cast<sema_rel::table&> (fk.scope ()));
- sema_rel::model& m (dynamic_cast<sema_rel::model&> (t.scope ()));
- if (dt_.tables_.find (rt) != dt_.tables_.end () ||
- m.find (rt) == m.names_end ())
+ if (!migration_)
{
- pre_statement ();
+ sema_rel::qname const& rt (fk.referenced_table ());
+ sema_rel::model& m (dynamic_cast<sema_rel::model&> (t.scope ()));
- // @@ This does not work: in MySQL control statements can only
- // be used in stored procedures. It seems the only way to
- // implement this is to define, execute, and drop a stored
- // procedure, which is just too ugly.
- //
- os << "IF EXISTS (SELECT NULL FROM information_schema.TABLE_CONSTRAINTS" << endl
- << " WHERE CONSTRAINT_TYPE = " << quote_string ("FOREIGN KEY") << "AND" << endl
- << " CONSTRAINT_SCHEMA = DATABASE() AND" << endl
- << " CONSTRAINT_NAME = " << quote_string (fk.name ()) << ") THEN" << endl
- << " ALTER TABLE " << quote_id (t.name ()) << " DROP FOREIGN KEY " << quote_id (fk.name ()) << ";" << endl
- << "END IF;" << endl;
- post_statement ();
+ if (dt_.tables_.find (rt) == dt_.tables_.end () &&
+ m.find (rt) != m.names_end ())
+ return;
}
+
+ pre_statement ();
+
+ /*
+ // @@ This does not work: in MySQL control statements can only
+ // be used in stored procedures. It seems the only way to
+ // implement this is to define, execute, and drop a stored
+ // procedure, which is just too ugly.
+ //
+ // Another option would be to use CREATE TABLE IF NOT EXISTS
+ // to create a dummy table with a dummy constraint that makes
+ // the following DROP succeed. Note, however, that MySQL issues
+ // a notice if the table already exist so would need to suppress
+ // that as well. Still not sure that the utility of this support
+ // justifies this kind of a hack.
+ //
+ os << "IF EXISTS (SELECT NULL FROM information_schema.TABLE_CONSTRAINTS" << endl
+ << " WHERE CONSTRAINT_TYPE = " << quote_string ("FOREIGN KEY") << "AND" << endl
+ << " CONSTRAINT_SCHEMA = DATABASE() AND" << endl
+ << " CONSTRAINT_NAME = " << quote_string (fk.name ()) << ") THEN" << endl
+ << " ALTER TABLE " << quote_id (t.name ()) << " DROP FOREIGN KEY " << quote_id (fk.name ()) << ";" << endl
+ << "END IF;" << endl;
+ */
+
+ os << "ALTER TABLE " << quote_id (t.name ()) << " DROP FOREIGN " <<
+ "KEY " << quote_id (fk.name ()) << endl;
+
+ post_statement ();
}
private:
drop_table& dt_;
+ bool migration_;
};
void drop_table::
- traverse (sema_rel::table& t)
+ traverse (sema_rel::table& t, bool migration)
{
+ // Only enabled for migration support for now (see above).
+ //
+ if (!migration)
+ {
+ base::traverse (t, migration);
+ return;
+ }
+
qname const& table (t.name ());
if (pass_ == 1)
{
- // Drop constraints.
+ // Drop constraints. In migration this is always done on pass 1.
//
- tables_.insert (table); // Add it before to cover self-refs.
- drop_foreign_key fk (*this);
+ if (!migration)
+ tables_.insert (table); // Add it before to cover self-refs.
+ drop_foreign_key fk (*this, migration);
trav_rel::unames n (fk);
names (t, n);
}
else if (pass_ == 2)
{
pre_statement ();
- os << "DROP TABLE IF EXISTS " << quote_id (table) << endl;
+ os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") <<
+ quote_id (table) << endl;
post_statement ();
}
}
- */
//
// Create.
@@ -281,7 +308,10 @@ namespace relational
{
if (pass_ == 1)
{
- tables_.insert (t.name ()); // Add it before to cover self-refs.
+ // In migration we always add foreign keys on pass 2.
+ //
+ if (!t.is_a<sema_rel::add_table> ())
+ tables_.insert (t.name ()); // Add it before to cover self-refs.
base::traverse (t);
return;
}
diff --git a/odb/relational/oracle/schema.cxx b/odb/relational/oracle/schema.cxx
index 28fd4fd..bd0acda 100644
--- a/odb/relational/oracle/schema.cxx
+++ b/odb/relational/oracle/schema.cxx
@@ -93,49 +93,72 @@ namespace relational
drop_table (base const& x): base (x) {}
virtual void
- traverse (sema_rel::table& t)
+ traverse (sema_rel::table& t, bool migration)
{
if (pass_ != 1)
return;
- qname const& table (t.name ());
-
- // Oracle has no IF EXISTS conditional for dropping objects. The
- // PL/SQL approach below seems to be the least error-prone and the
- // most widely used of the alternatives.
- //
- pre_statement ();
- os << "BEGIN" << endl
- << " BEGIN" << endl
- << " EXECUTE IMMEDIATE 'DROP TABLE " << quote_id (table) <<
- " CASCADE CONSTRAINTS';" << endl
- << " EXCEPTION" << endl
- << " WHEN OTHERS THEN" << endl
- << " IF SQLCODE != -942 THEN RAISE; END IF;" << endl
- << " END;" << endl;
-
- // Drop the sequence if we have auto primary key.
- //
using sema_rel::primary_key;
+ qname const& table (t.name ());
+
sema_rel::table::names_iterator i (t.find ("")); // Special name.
primary_key* pk (i != t.names_end ()
? &dynamic_cast<primary_key&> (i->nameable ())
: 0);
- if (pk != 0 && pk->auto_ ())
+ string qt (quote_id (table));
+ string qs (pk != 0 && pk->auto_ ()
+ ? quote_id (sequence_name (table))
+ : "");
+
+ if (migration)
+ {
+ pre_statement ();
+ os << "DROP TABLE " << qt << " CASCADE CONSTRAINTS" << endl;
+ post_statement ();
+
+ // Drop the sequence if we have auto primary key.
+ //
+ if (!qs.empty ())
+ {
+ pre_statement ();
+ os << "DROP SEQUENCE " << qs << endl;
+ post_statement ();
+ }
+ }
+ else
{
- os << " BEGIN" << endl
- << " EXECUTE IMMEDIATE 'DROP SEQUENCE " <<
- quote_id (sequence_name (table)) << "';" << endl
+ // Oracle has no IF EXISTS conditional for dropping objects. The
+ // PL/SQL approach below seems to be the least error-prone and the
+ // most widely used of the alternatives.
+ //
+ pre_statement ();
+ os << "BEGIN" << endl
+ << " BEGIN" << endl
+ << " EXECUTE IMMEDIATE 'DROP TABLE " << qt << " CASCADE " <<
+ "CONSTRAINTS';" << endl
<< " EXCEPTION" << endl
<< " WHEN OTHERS THEN" << endl
- << " IF SQLCODE != -2289 THEN RAISE; END IF;" << endl
+ << " IF SQLCODE != -942 THEN RAISE; END IF;" << endl
<< " END;" << endl;
- }
- os << "END;" << endl;
- post_statement ();
+ // Drop the sequence if we have auto primary key.
+ //
+ if (!qs.empty ())
+ {
+ os << " BEGIN" << endl
+ << " EXECUTE IMMEDIATE 'DROP SEQUENCE " << qs <<
+ "';" << endl
+ << " EXCEPTION" << endl
+ << " WHEN OTHERS THEN" << endl
+ << " IF SQLCODE != -2289 THEN RAISE; END IF;" << endl
+ << " END;" << endl;
+ }
+
+ os << "END;" << endl;
+ post_statement ();
+ }
}
};
entry<drop_table> drop_table_;
@@ -217,7 +240,10 @@ namespace relational
{
if (pass_ == 1)
{
- tables_.insert (t.name ()); // Add it before to cover self-refs.
+ // In migration we always add foreign keys on pass 2.
+ //
+ if (!t.is_a<sema_rel::add_table> ())
+ tables_.insert (t.name ()); // Add it before to cover self-refs.
base::traverse (t);
// Create the sequence if we have auto primary key.
diff --git a/odb/relational/pgsql/schema.cxx b/odb/relational/pgsql/schema.cxx
index 0a2b82d..efe9b40 100644
--- a/odb/relational/pgsql/schema.cxx
+++ b/odb/relational/pgsql/schema.cxx
@@ -28,10 +28,10 @@ namespace relational
drop_table (base const& x): base (x) {}
virtual void
- drop (sema_rel::qname const& table)
+ drop (sema_rel::qname const& table, bool migration)
{
- os << "DROP TABLE IF EXISTS " << quote_id (table) <<
- " CASCADE" << endl;
+ os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") <<
+ quote_id (table) << " CASCADE" << endl;
}
};
entry<drop_table> drop_table_;
@@ -146,7 +146,10 @@ namespace relational
{
if (pass_ == 1)
{
- tables_.insert (t.name ()); // Add it before to cover self-refs.
+ // In migration we always add foreign keys on pass 2.
+ //
+ if (!t.is_a<sema_rel::add_table> ())
+ tables_.insert (t.name ()); // Add it before to cover self-refs.
base::traverse (t);
return;
}
diff --git a/odb/relational/schema.cxx b/odb/relational/schema.cxx
index 492c3b7..e764790 100644
--- a/odb/relational/schema.cxx
+++ b/odb/relational/schema.cxx
@@ -86,5 +86,57 @@ namespace relational
model->traverse (*ctx.model);
}
}
+
+ void
+ generate_migrate_pre (sema_rel::changeset& cs)
+ {
+ context ctx;
+ instance<sql_emitter> em;
+ emitter_ostream emos (*em);
+
+ schema_format f (schema_format::sql);
+
+ instance<migrate_pre_changeset> changeset (*em, emos, f);
+ instance<create_table> ctable (*em, emos, f);
+ trav_rel::qnames names;
+
+ changeset >> names >> ctable;
+
+ // Pass 1 and 2.
+ //
+ for (unsigned short pass (1); pass < 3; ++pass)
+ {
+ changeset->pass (pass);
+ ctable->pass (pass);
+
+ changeset->traverse (cs);
+ }
+ }
+
+ void
+ generate_migrate_post (sema_rel::changeset& cs)
+ {
+ context ctx;
+ instance<sql_emitter> em;
+ emitter_ostream emos (*em);
+
+ schema_format f (schema_format::sql);
+
+ instance<migrate_post_changeset> changeset (*em, emos, f);
+ instance<drop_table> dtable (*em, emos, f);
+ trav_rel::qnames names;
+
+ changeset >> names >> dtable;
+
+ // Pass 1 and 2.
+ //
+ for (unsigned short pass (1); pass < 3; ++pass)
+ {
+ changeset->pass (pass);
+ dtable->pass (pass);
+
+ changeset->traverse (cs);
+ }
+ }
}
}
diff --git a/odb/relational/schema.hxx b/odb/relational/schema.hxx
index 86b7419..8ce9f59 100644
--- a/odb/relational/schema.hxx
+++ b/odb/relational/schema.hxx
@@ -58,84 +58,69 @@ namespace relational
// Drop.
//
- struct drop_index: trav_rel::index, common
+ struct drop_table: trav_rel::table,
+ trav_rel::drop_table,
+ trav_rel::add_table, // override
+ trav_rel::alter_table, // override
+ common
{
- typedef drop_index base;
+ typedef drop_table base;
- drop_index (emitter_type& e, ostream& os, schema_format f)
+ drop_table (emitter_type& e, ostream& os, schema_format f)
: common (e, os), format_ (f)
{
}
- virtual string
- name (sema_rel::index& in)
- {
- return quote_id (in.name ());
- }
-
- virtual string
- table_name (sema_rel::index& in)
+ virtual void
+ drop (sema_rel::qname const& table, bool migration)
{
- return quote_id (static_cast<sema_rel::table&> (in.scope ()).name ());
+ os << "DROP TABLE " << (migration ? "" : "IF EXISTS ") <<
+ quote_id (table) << endl;
}
virtual void
- drop (string const& /*name*/, string const& /*table*/)
+ traverse (sema_rel::table& t, bool migration)
{
- // 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.
//
- // os << "DROP INDEX IF EXISTS " << quote_id (name) << " ON " <<
- // table << endl;
- }
+ if (pass_ > 1)
+ return;
- virtual void
- traverse (sema_rel::index& in)
- {
pre_statement ();
- drop (name (in), table_name (in));
+ drop (t.name (), migration);
post_statement ();
}
- protected:
- schema_format format_;
- };
-
- 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
+ traverse (sema_rel::table& t)
{
+ traverse (t, false);
}
virtual void
- drop (sema_rel::qname const& table)
+ traverse (sema_rel::drop_table& dt)
{
- os << "DROP TABLE IF EXISTS " << quote_id (table) << endl;
+ using sema_rel::model;
+ using sema_rel::changeset;
+ using sema_rel::table;
+
+ // Find the table we are dropping in the base model.
+ //
+ changeset& cs (dynamic_cast<changeset&> (dt.scope ()));
+ model& bm (cs.base_model ());
+ table* t (bm.find<table> (dt.name ()));
+ assert (t != 0);
+ traverse (*t, true);
}
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;
+ traverse (sema_rel::add_table&) {}
- // Drop indexes.
- //
- {
- instance<drop_index> in (emitter (), stream (), format_);
- trav_rel::unames n (*in);
- names (t, n);
- }
+ virtual void
+ traverse (sema_rel::alter_table&) {}
- pre_statement ();
- drop (t.name ());
- post_statement ();
- }
+ using table::names;
void
pass (unsigned short p)
@@ -646,6 +631,78 @@ namespace relational
unsigned short pass_;
};
+ struct migrate_pre_changeset: trav_rel::changeset, common
+ {
+ typedef migrate_pre_changeset base;
+
+ migrate_pre_changeset (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os), format_ (f)
+ {
+ }
+
+ // This version is only called for file migration.
+ //
+ virtual void
+ traverse (sema_rel::changeset& m)
+ {
+ traverse (m.names_begin (), m.names_end ());
+ }
+
+ virtual void
+ traverse (sema_rel::changeset::names_iterator begin,
+ sema_rel::changeset::names_iterator end)
+ {
+ for (; begin != end; ++begin)
+ dispatch (*begin);
+ }
+
+ void
+ pass (unsigned short p)
+ {
+ pass_ = p;
+ }
+
+ protected:
+ schema_format format_;
+ unsigned short pass_;
+ };
+
+ struct migrate_post_changeset: trav_rel::changeset, common
+ {
+ typedef migrate_post_changeset base;
+
+ migrate_post_changeset (emitter_type& e, ostream& os, schema_format f)
+ : common (e, os), format_ (f)
+ {
+ }
+
+ // This version is only called for file migration.
+ //
+ virtual void
+ traverse (sema_rel::changeset& m)
+ {
+ traverse (m.names_begin (), m.names_end ());
+ }
+
+ virtual void
+ traverse (sema_rel::changeset::names_iterator begin,
+ sema_rel::changeset::names_iterator end)
+ {
+ for (; begin != end; ++begin)
+ dispatch (*begin);
+ }
+
+ void
+ pass (unsigned short p)
+ {
+ pass_ = p;
+ }
+
+ protected:
+ schema_format format_;
+ unsigned short pass_;
+ };
+
//
// SQL output.
//
diff --git a/odb/semantics/relational/changelog.hxx b/odb/semantics/relational/changelog.hxx
index 93b327d..d9e220c 100644
--- a/odb/semantics/relational/changelog.hxx
+++ b/odb/semantics/relational/changelog.hxx
@@ -88,6 +88,12 @@ namespace semantics
return *contains_changeset_.back ();
}
+ contains_changeset const&
+ contains_changeset_at (contains_changeset_list::size_type i) const
+ {
+ return *contains_changeset_[i];
+ }
+
contains_changeset_iterator
contains_changeset_begin () const
{
diff --git a/odb/semantics/relational/changeset.cxx b/odb/semantics/relational/changeset.cxx
index fd5a17f..669394f 100644
--- a/odb/semantics/relational/changeset.cxx
+++ b/odb/semantics/relational/changeset.cxx
@@ -13,14 +13,16 @@ namespace semantics
changeset::
changeset (changeset const& c, qscope& b, graph& g)
: qscope (c, &b, g),
- version_ (c.version_)
+ version_ (c.version_),
+ alters_model_ (0)
{
}
changeset::
changeset (xml::parser& p, qscope& b, graph& g)
: qscope (p, &b, g),
- version_ (p.attribute<version_type> ("version"))
+ version_ (p.attribute<version_type> ("version")),
+ alters_model_ (0)
{
}
diff --git a/odb/semantics/relational/changeset.hxx b/odb/semantics/relational/changeset.hxx
index 76fd683..27c80fe 100644
--- a/odb/semantics/relational/changeset.hxx
+++ b/odb/semantics/relational/changeset.hxx
@@ -11,6 +11,43 @@ namespace semantics
{
namespace relational
{
+ class model;
+ class changeset;
+
+ class alters_model: public edge
+ {
+ public:
+ typedef relational::model model_type;
+ typedef relational::changeset changeset_type;
+
+ model_type&
+ model () const {return *model_;}
+
+ changeset_type&
+ changeset () const {return *changeset_;}
+
+ public:
+ alters_model () : model_ (0), changeset_ (0) {}
+
+ void
+ set_left_node (changeset_type& c)
+ {
+ assert (changeset_ == 0);
+ changeset_ = &c;
+ }
+
+ void
+ set_right_node (model_type& m)
+ {
+ assert (model_ == 0);
+ model_ = &m;
+ }
+
+ protected:
+ model_type* model_;
+ changeset_type* changeset_;
+ };
+
class changeset: public qscope
{
public:
@@ -19,8 +56,14 @@ namespace semantics
version_type
version () const {return version_;}
+ // Returns model to which this changeset applies (as opposed to the
+ // ultimate base model). Note that it may not be set.
+ //
+ model&
+ base_model () const {return alters_model_->model ();}
+
public:
- changeset (version_type v): version_ (v) {}
+ changeset (version_type v): version_ (v), alters_model_ (0) {}
changeset (changeset const&, qscope& base, graph&);
changeset (xml::parser&, qscope& base, graph&);
@@ -31,11 +74,19 @@ namespace semantics
serialize (xml::serializer&) const;
public:
+ virtual void
+ add_edge_left (alters_model& am)
+ {
+ assert (alters_model_ == 0);
+ alters_model_ = &am;
+ }
+
using qscope::add_edge_left;
using qscope::add_edge_right;
private:
version_type version_;
+ alters_model* alters_model_;
};
}
}