diff options
Diffstat (limited to 'libodb/odb/schema-catalog.cxx')
-rw-r--r-- | libodb/odb/schema-catalog.cxx | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/libodb/odb/schema-catalog.cxx b/libodb/odb/schema-catalog.cxx new file mode 100644 index 0000000..1bdc112 --- /dev/null +++ b/libodb/odb/schema-catalog.cxx @@ -0,0 +1,387 @@ +// file : odb/schema-catalog.cxx +// license : GNU GPL v2; see accompanying LICENSE file + +#include <map> +#include <vector> +#include <cassert> + +#include <odb/exceptions.hxx> +#include <odb/schema-catalog.hxx> +#include <odb/schema-catalog-impl.hxx> + +using namespace std; + +namespace odb +{ + // Schema. + // + typedef bool (*create_function) (database&, unsigned short pass, bool drop); + typedef bool (*migrate_function) (database&, unsigned short pass, bool pre); + + typedef pair<database_id, string> key; + typedef vector<create_function> create_functions; + typedef vector<migrate_function> migrate_functions; + typedef map<schema_version, migrate_functions> version_map; + + struct schema_functions + { + create_functions create; + version_map migrate; + }; + typedef map<key, schema_functions> schema_map; + + // Data. Normally the code would be database-independent, though there + // could be database-specific migration steps. + // + typedef pair<string, schema_version> data_key; + + struct data_function + { + typedef schema_catalog::data_migration_function_wrapper + function_wrapper_type; + + data_function () {} + data_function (database_id i, function_wrapper_type m) + : id (i), migrate (m) {} + + database_id id; + function_wrapper_type migrate; + }; + typedef vector<data_function> data_functions; + typedef map<data_key, data_functions> data_map; + + struct schema_catalog_impl + { + schema_map schema; + data_map data; + }; + + // Static initialization. + // + schema_catalog_impl* schema_catalog_init::catalog = 0; + size_t schema_catalog_init::count = 0; + + struct schema_catalog_init_extra + { + bool initialized; + + schema_catalog_init_extra (): initialized (false) {} + ~schema_catalog_init_extra () + { + if (initialized && --schema_catalog_init::count == 0) + delete schema_catalog_init::catalog; + } + }; + + static schema_catalog_init_extra schema_catalog_init_extra_; + + bool schema_catalog:: + exists (database_id id, const string& name) + { + const schema_catalog_impl& c (*schema_catalog_init::catalog); + return c.schema.find (key (id, name)) != c.schema.end (); + } + + void schema_catalog:: + create_schema (database& db, const string& name, bool drop) + { + const schema_catalog_impl& c (*schema_catalog_init::catalog); + schema_map::const_iterator i (c.schema.find (key (db.id (), name))); + + if (i == c.schema.end ()) + throw unknown_schema (name); + + const create_functions& fs (i->second.create); + + if (drop) + drop_schema (db, name); + + // Run the passes until we ran them all or all the functions + // return false, which means no more passes necessary. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + bool done (true); + + for (create_functions::const_iterator j (fs.begin ()), e (fs.end ()); + j != e; ++j) + { + if ((*j) (db, pass, false)) + done = false; + } + + if (done) + break; + } + } + + void schema_catalog:: + drop_schema (database& db, const string& name) + { + const schema_catalog_impl& c (*schema_catalog_init::catalog); + schema_map::const_iterator i (c.schema.find (key (db.id (), name))); + + if (i == c.schema.end ()) + throw unknown_schema (name); + + const create_functions& fs (i->second.create); + + // Run the passes until we ran them all or all the functions + // return false, which means no more passes necessary. + // + for (unsigned short pass (1); pass < 3; ++pass) + { + bool done (true); + + for (create_functions::const_iterator j (fs.begin ()), e (fs.end ()); + j != e; ++j) + { + if ((*j) (db, pass, true)) + done = false; + } + + if (done) + break; + } + } + + void schema_catalog:: + migrate_schema_impl (database& db, + schema_version v, + const string& name, + migrate_mode m) + { + const schema_catalog_impl& c (*schema_catalog_init::catalog); + schema_map::const_iterator i (c.schema.find (key (db.id (), name))); + + if (i == c.schema.end ()) + throw unknown_schema (name); + + const version_map& vm (i->second.migrate); + version_map::const_iterator j (vm.find (v)); + + if (j == vm.end ()) + throw unknown_schema_version (v); + + const migrate_functions& fs (j->second); + + // Run the passes until we ran them all or all the functions + // return false, which means no more passes necessary. + // + for (bool pre (m != migrate_post);; pre = false) + { + for (unsigned short pass (1); pass < 3; ++pass) + { + bool done (true); + + for (migrate_functions::const_iterator i (fs.begin ()), e (fs.end ()); + i != e; ++i) + { + if ((*i) (db, pass, pre)) + done = false; + } + + if (done) + break; + } + + if (!pre || m != migrate_both) + break; + } + + // Update the schema version on the database instance. + // + db.schema_version_migration (v, m == migrate_pre, name); + } + + size_t schema_catalog:: + migrate_data (database& db, schema_version v, const string& name) + { + if (v == 0) + { + if (!db.schema_migration ()) + return 0; + + v = db.schema_version (); + } + + const schema_catalog_impl& c (*schema_catalog_init::catalog); + data_map::const_iterator i (c.data.find (data_key (name, v))); + + if (i == c.data.end ()) + return 0; // No data migration for this schema/version. + + size_t r (0); + + const data_functions& df (i->second); + for (data_functions::const_iterator i (df.begin ()), e (df.end ()); + i != e; ++i) + { + if (i->id == id_common || i->id == db.id ()) + { + const data_migration_function_wrapper &m = i->migrate; + + if (m.std_function == 0) + m.function (db); + else + { + typedef void (*caller) (const void*, database&); + m.cast<caller> () (m.std_function, db); + } + r++; + } + } + + return r; + } + + void schema_catalog:: + data_migration_function (database_id id, + schema_version v, + data_migration_function_wrapper f, + const string& name) + { + // This function can be called from a static initializer in which + // case the catalog might not have yet been created. + // + if (schema_catalog_init::count == 0) + { + schema_catalog_init::catalog = new schema_catalog_impl; + ++schema_catalog_init::count; + schema_catalog_init_extra_.initialized = true; + } + + schema_catalog_impl& c (*schema_catalog_init::catalog); + c.data[data_key (name, v)].push_back (data_function (id, f)); + } + + void schema_catalog:: + migrate (database& db, schema_version v, const string& name) + { + schema_version cur (current_version (db, name)); + + if (v == 0) + v = cur; + else if (v > cur) + throw unknown_schema_version (v); + + schema_version i (db.schema_version (name)); + + if (i > v) + throw unknown_schema_version (i); // Database too new. + + // If there is no schema, then "migrate" by creating it. + // + if (i == 0) + { + // Schema creation can only "migrate" straight to current. + // + if (v != cur) + throw unknown_schema_version (v); + + create_schema (db, name, false); + return; + } + + for (i = next_version (db, i, name); + i <= v; + i = next_version (db, i, name)) + { + migrate_schema_pre (db, i, name); + migrate_data (db, i, name); + migrate_schema_post (db, i, name); + } + } + + schema_version schema_catalog:: + base_version (database_id id, const string& name) + { + const schema_catalog_impl& c (*schema_catalog_init::catalog); + schema_map::const_iterator i (c.schema.find (key (id, name))); + + if (i == c.schema.end ()) + throw unknown_schema (name); + + const version_map& vm (i->second.migrate); + assert (!vm.empty ()); + return vm.begin ()->first; + } + + schema_version schema_catalog:: + current_version (database_id id, const string& name) + { + const schema_catalog_impl& c (*schema_catalog_init::catalog); + schema_map::const_iterator i (c.schema.find (key (id, name))); + + if (i == c.schema.end ()) + throw unknown_schema (name); + + const version_map& vm (i->second.migrate); + assert (!vm.empty ()); + return vm.rbegin ()->first; + } + + schema_version schema_catalog:: + next_version (database_id id, schema_version v, const string& name) + { + const schema_catalog_impl& sc (*schema_catalog_init::catalog); + schema_map::const_iterator i (sc.schema.find (key (id, name))); + + if (i == sc.schema.end ()) + throw unknown_schema (name); + + const version_map& vm (i->second.migrate); // Cannot be empty. + + schema_version b (vm.begin ()->first); + schema_version c (vm.rbegin ()->first); + + if (v == 0) + return c; // "Migration" to the current via schema creation. + else if (v < b) + throw unknown_schema_version (v); // Unsupported migration. + + version_map::const_iterator j (vm.upper_bound (v)); + return j != vm.end () ? j->first : c + 1; + } + + // schema_catalog_init + // + schema_catalog_init:: + schema_catalog_init () + { + if (count == 0) + catalog = new schema_catalog_impl; + + ++count; + } + + schema_catalog_init:: + ~schema_catalog_init () + { + if (--count == 0) + delete catalog; + } + + // schema_catalog_create_entry + // + schema_catalog_create_entry:: + schema_catalog_create_entry (database_id id, + const char* name, + create_function cf) + { + schema_catalog_impl& c (*schema_catalog_init::catalog); + c.schema[key(id, name)].create.push_back (cf); + } + + // schema_catalog_migrate_entry + // + schema_catalog_migrate_entry:: + schema_catalog_migrate_entry (database_id id, + const char* name, + schema_version v, + migrate_function mf) + { + schema_catalog_impl& c (*schema_catalog_init::catalog); + c.schema[key(id, name)].migrate[v].push_back (mf); + } +} |