From b0391e168b489811708ca7ba5f71a0eb67b46ffe Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 3 Apr 2013 11:22:42 +0200 Subject: Generate add/drop table migration statements --- odb/generator.cxx | 672 ++++++++++++++++++--------------- odb/options.cli | 47 ++- odb/relational/changelog.cxx | 35 +- odb/relational/generate.hxx | 12 +- odb/relational/mssql/schema.cxx | 63 ++-- odb/relational/mysql/schema.cxx | 88 +++-- odb/relational/oracle/schema.cxx | 82 ++-- odb/relational/pgsql/schema.cxx | 11 +- odb/relational/schema.cxx | 52 +++ odb/relational/schema.hxx | 159 +++++--- odb/semantics/relational/changelog.hxx | 6 + odb/semantics/relational/changeset.cxx | 6 +- odb/semantics/relational/changeset.hxx | 53 ++- 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 // std::toupper, std::is{alpha,upper,lower} #include #include // std::auto_ptr +#include #include #include #include @@ -23,6 +24,7 @@ #include #include +#include #include #include @@ -34,6 +36,7 @@ using namespace cutl; using semantics::path; typedef vector strings; typedef vector paths; +typedef vector > 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 > const& text, + database_map 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 model; + + shared_ptr 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 changelog; - cutl::shared_ptr old_changelog; + bool gen_changelog (gen_schema && unit.count ("model-version") != 0); + cutl::shared_ptr changelog; + cutl::shared_ptr 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"), 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 pre (new (shared) ofstream); + shared_ptr 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 ind_filter; typedef compiler::ostream_filter 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 " << 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 " << 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 " << 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 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 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 > --schema-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 --odb-file-suffix { "", @@ -428,6 +447,12 @@ class options "Insert at the beginning of the generated database schema file." }; + database_map > --migration-prologue + { + "", + "Insert at the beginning of the generated database migration file." + }; + // Interludes. // database_map > --sql-interlude @@ -469,6 +494,12 @@ class options "Insert at the end of the generated database schema file." }; + database_map > --migration-epilogue + { + "", + "Insert at the end of the generated database migration file." + }; + // Prologue files. // database_map --hxx-prologue-file @@ -506,6 +537,13 @@ class options database schema file." }; + database_map --migration-prologue-file + { + "", + "Insert the content of file at the beginning of the generated database + migration file." + }; + // Interlude files. // database_map --sql-interlude-file @@ -552,6 +590,13 @@ class options schema file." }; + database_map --migration-epilogue-file + { + "", + "Insert the content of file at the end of the generated database + migration file." + }; + // ODB compilation prologue/epilogue. // database_map > --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 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 (*cl, g.new_node (m, g)); @@ -687,7 +689,10 @@ namespace relational changeset& c (diff (nm, m, *cl)); if (!c.names_empty ()) + { + g.new_edge (c, nm); g.new_edge (*cl, c); + } } return cl; @@ -725,12 +730,11 @@ namespace relational // changeset. // // - model* base (bver == mv.base ? &g.new_node (oldm, g) : 0); + model* last (&g.new_node (oldm, g)); + model* base (bver == mv.base ? last : 0); if (base != 0) g.new_edge (*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 (c, *base); g.new_edge (*cl, c); + } continue; } @@ -780,6 +788,8 @@ namespace relational ? static_cast (*base) // Cannot be NULL. : cl->contains_changeset_back ().changeset (), g)); + + g.new_edge (c, prev); g.new_edge (*cl, c); } @@ -794,7 +804,7 @@ namespace relational { // Fast-forward the latest model to the new base. // - base = last != &oldm ? last : &g.new_node (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 (*cl, cs); + if (!c.names_empty ()) + { + g.new_edge (c, *last); + g.new_edge (*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 #include +#include #include 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 (fk.scope ())); - sema_rel::model& m (dynamic_cast (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 (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 ()) + 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 (fk.scope ())); - sema_rel::model& m (dynamic_cast (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 (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 ()) + 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 (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_; @@ -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 ()) + 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_; @@ -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 ()) + 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 em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance changeset (*em, emos, f); + instance 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 em; + emitter_ostream emos (*em); + + schema_format f (schema_format::sql); + + instance changeset (*em, emos, f); + instance 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 (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 (dt.scope ())); + model& bm (cs.base_model ()); + table* t (bm.find (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 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")) + version_ (p.attribute ("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_; }; } } -- cgit v1.1