Probably the version & revision plan above can take us quite far. But DB updates are still strictly linear, which is a bit of a pain because it restricts branched development. This in turn limits
- How much we can do in a STABLE branch
- How much revision we can do of code before it hits HEAD
It also forces a bit of dirty work on ddllib.php, which has to do a lot of magic to figure out if a DDL change has already been applied. Eloy's DDL magic is curiosly strong and saves us from endless problems, but there are cases that it cannot handle. The problem is fundamentally hard, and by the time ddllib gets called, there are things we no longer know, so it has to guess. It goes something like this:
Does the table/column/index I am asked to create
exist already? Does it look roughly like what
I'm being asked to create?
There is a lot of magic to do this reliably cross-DBs. But as you can imagine, a simple "create table and then modify it" scenario can throw this magic off, unless we attach a mind-reader extension to the code.
So here's my 2.0 plan to overcome this. It also helps people doing custom branches (which of course is what I do ).
First I'll describe the workflow for developers.
Workflow
In addition to db/install.xml and db/upgrade.php, each pluggable thing that has DB files (and core, naturally) have a db/upgrades/ directory.
As a developer, when you create a new DB upgrade "block", you
- Give it a slightly longer name, following the current convention but adding some description to it, like : 2008021700-webdavlocks
- Create a new file lib/db/upgrades/2008021700-webdavlocks.php
- In the file, the if() that controls the block looks almost like the current one
We'll replace the if block like this
- if ($oldversion < 2008021700) {
+ if (has_dbupdate("2008021700-webdavblocks") {
The workflow above covers development on CVS HEAD, a STABLE branch, or a custom client branch you'll never show to anyone else. For code in CVS HEAD only, there's an additional couple of steps we take when we are about to branch (the "BETA" branchpoint.
And we are done! That's all the change.
DB upgrade blocks that were applied during the STABLE branch do not get re-attempted if they are also in the upgrade to the new major release. The upgrade to the new major release can ask if a particular upgrade from STABLE was ever applied (to undo possible damage, or to skip unneeded steps).
How it works
You probably guessed it already: we keep a simple table were we save every db upgrade that completes successfully. Call the table db_upgrades -- it has
- id
- pluginname (we can use 'core' or 'moodle' for lib/db
- upgradename
So if your install is tracking STABLE or HEAD, new records get added to that table. When a new major version comes out, and the upgrades move to the upgrade.php file, the subtle change I mentioned above of doing
- if (has_dbupdate("2008021700-webdavblocks") {
+ if (has_dbupdate("2008021700-webdavblocks", "MOODLE_20_STABLE") {
allows us to say "ah, we are on MOODLE_20_STABLE, so we have this upgrade. If it's there is a record for it in the db_upgrades table, remove it, as it's no longer needed. That way the list of upgrades we track individually stays short.
To trigger upgrades we read the lib/db/upgrades/ directories and the db_upgrades tables and trigger an update if there are files with filenames that we dont have in the db_upgrades table. With the "cleanup" strategy I outlined directory contents and the db_upgrades table will stay relatively short -- specially in production environments, not so much for developers on HEAD. We may want to keep using $CFG->version bumps for other stuff as it's generally useful, but we will have broken with the linearity of DB schema upgrades.
What do people think?
Edit: There's an alternative to the 2nd parameter in has_dbupdate() - we could cleanup the entries in the db_upgrades table if the file is gone. This has its downsides: if you upgrade to a temporary install without a specific lib/db/upgrades/XX file, and the row will be removed even though the DB schema change hasn't been undone.