modular_db ~master
Factorization of a relational database into a set of modules.
To use this package, run the following command in your project's root directory:
Manual usage
Put the following dependency into your project's dependences section:
modular_db
This library allows to compose your database from several modules, possibly written by independent
parties. The only DBMS currently supported is SQLite via d2sqlite3
— please, refer to its
documentation.
<!-- EXAMPLE -->
import moddb = modular_db: ModuleQualification, format;
@system:
// Module type, which should conform to `modular_db.isModule` structural interface.
struct CountriesMod {
// The easiest way to implement it is to mix required members in.
mixin moddb.moduleFields;
}
struct CountriesModLoader {
// Note: No member of this struct is required to be compile-time-accessible or static,
// though it certainly won't harm.
// A string to uniquely identify this module. The library does not fetch the resource it
// points to nor even checks that it is a valid URI. It serves the same purpose as, e.g., URI
// in an XML schema. However, it is a good idea to host some documentation at that address.
enum url = "https://sirnickolas.github.io/modular_db/examples/basic/countries";
// Module's version as a positive integral number.
enum version_ = 3L;
// The database (attached to the connection with schema `q.schema`) already has our module,
// upgraded to the latest version, and it has ID `q.id` there. We need to construct and return
// a _module object_.
static CountriesMod load(moddb.Database db, ModuleQualification q) {
return CountriesMod(db, q);
}
// Our module is not present in the database. We are asked to add it there. We should use
// ID `q.id` for it.
static CountriesMod setup(moddb.Database db, ModuleQualification q) {
db.run(q.format!`
-- If you enclose an identifier in square brackets, schema name and module ID will be
-- prepended to it. E.g., [countries] might produce "main"."1countries".
CREATE TABLE [countries](
-- Each your table must have an INTEGER PRIMARY KEY (i.e., alias for the rowid).
oid INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
area REAL CHECK(area > 0)
);
-- [-.identifier] syntax prepends only module ID but not schema name.
CREATE INDEX [countries_area_idx] ON [-.countries](area);
`);
return CountriesMod(db, q);
}
// The database has our module (its ID is `q.id`), but it is outdated. We must do whatever
// is needed to update it to our current `version_`.
static CountriesMod migrate(moddb.Database db, ModuleQualification q, long fromVersion) {
final switch (fromVersion) {
case 1L:
db.execute(q.format!`ALTER TABLE [countries] ADD COLUMN area REAL CHECK(area > 0)`);
goto case;
case 2L:
db.execute(q.format!`CREATE INDEX [countries_area_idx] ON [-.countries](area)`);
}
return CountriesMod(db, q);
}
}
struct CitiesMod {
mixin moddb.moduleFields;
}
struct CitiesModLoader {
enum url = "https://sirnickolas.github.io/modular_db/examples/basic/cities";
enum version_ = 1L;
static CitiesMod load(moddb.Database db, ModuleQualification q) {
return CitiesMod(db, q);
}
static CitiesMod setup(moddb.Database db, ModuleQualification q) {
// Get ID of the module we depend on.
const long cntId = moddb.getModuleId(db, q, CountriesModLoader.url);
db.run(q.format!`
CREATE TABLE [cities](
oid INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
-- [1|countries] prepends schema name and uses the value of the 1st argument
-- as a module ID. [-.1|countries] does not prepend schema name.
country_id INTEGER NOT NULL REFERENCES [-.1|countries] ON UPDATE CASCADE
);
CREATE INDEX [cities_country_idx] ON [-.cities](country_id);
CREATE TABLE [capitals](
-- Every foreign key definition must have "ON UPDATE CASCADE" clause.
city_id INTEGER PRIMARY KEY REFERENCES [-.cities] ON UPDATE CASCADE
);
`(cntId)); // Pass its ID so that we can make references to its tables.
return CitiesMod(db, q);
}
static CitiesMod migrate(moddb.Database, ModuleQualification, long) {
assert(false, "There are no previous versions");
}
}
void main() {
import std.stdio;
static import d2sqlite3;
auto db = moddb.Database(d2sqlite3.Database("db.sqlite3"));
db.execute("PRAGMA foreign_keys = ON"); // They are disabled by default in SQLite.
// A call to `modular_db.initialize` is required to, you guessed it, initialize the module
// system for the given database. In particular, "module system" is just a regular module -
// `modular_db.module_module.ModuleModule` - that gets installed into your database. It always
// has ID of 0. You can rely on this fact if you need to interact with its tables.
//
// If you ATTACH more DATABASEs to your connection, you have to initialize each one, passing
// schema name as the third parameter.
//
// There are three loading modes available:
// * `modular_db.Mode.load` (default) - try to load the module if it is present in the DB, throw
// an exception if it is not or has a wrong version.
// * `modular_db.Mode.setup` - try to load the module; if it is not present, install it; if it
// has a wrong version, throw an exception.
// * `modular_db.Mode.migrate` - try to load the module, install it, or upgrade to current
// version. It can still throw an exception if the stored module happens to have a higher
// version than we consider the latest.
moddb.initialize(db, moddb.Mode.migrate); // `migrate` affects only the system, not our modules.
// Because `initialize` just loads a particular module, everything said before is applicable
// to `modular_db.loadModule` as well.
CountriesMod cnt = moddb.loadModule!CountriesModLoader(db, moddb.Mode.setup);
// More verbose way, which supports stateful loaders:
CitiesModLoader bLoader;
CitiesMod ct = moddb.loadModule(db, bLoader, moddb.Mode.setup);
enum countryName = "Great Britain";
d2sqlite3.ResultRange results = ct.database.execute(
ct.qualification.format!`
SELECT ct.name
FROM [cities] ct
JOIN [capitals] cap ON cap.city_id = ct.oid
JOIN [1|countries] cnt ON cnt.oid = ct.country_id
WHERE cnt.name = ?
`(cnt.qualification.id),
countryName,
);
if (!results.empty)
writef!"%s is the capital of %s.\n"(results.oneValue!string(), countryName);
}
<!-- END -->
- ~master released 4 years ago
- SirNickolas/modular_db
- MIT
- Copyright © 2020, Nickolay Bukreyev
- Authors:
- Dependencies:
- d2sqlite3
- Versions:
-
0.1.1 2020-Jul-26 0.1.0 2020-Jul-23 ~master 2020-Jul-26 - Download Stats:
-
-
0 downloads today
-
0 downloads this week
-
0 downloads this month
-
10 downloads total
-
- Score:
- 0.5
- Short URL:
- modular_db.dub.pm