Purpose

MIGRATEdb will parse an XML file of database changes and load them to the targeted database if they are not already there.

A database change consists of tests to determine whether the change has already been applied and a set of change actions.

Download it here.

Based on blog entries from Warren Mayocchi.

Pramod Sadalage's short tutorial.

Features

Feature
Supported by MIGRATEdb
Comments
Allows construction of a database at a particular version
Yes
Allows migration from an existing database to a later versionYes
Allows migration from an existing database to an earlier versionNo
Human readable format for releasesYes
It is in XML which could be called ugly or readable depending on your opinion
Release ‘action’ available on multiple environments (ie various operating systems) allowing development on a different platform than productionYes

Provides a complete history of changes for each database objectYes
Though you would probably want some type of filtering ability over the top of the file to filter for changes to the object of interest.
Provides a current creation script for each database objectNo
If this is required, it can usually be generated.
The source code can be branched and mergedYes
Most complex when the same object is being modified in the branch and trunk, even in this case the 'actions' are small functional units
Allows multiple developers to work with/on the same database source code, at the same timeYes
The process can be re-run infinitely, only installing new changes each time
Supports an ‘automated build’ / ‘continuous integration’ enviromentYes

Automatically records database changes by the developer in their development areaNo

Supports use of parameters to configure variable parts of the release (eg the password part of “CREATE USER warren IDENTIFIED BY xyzzy”)Yes

An Example

A simple example could be an existing table with a 'name' column that contains first and last name. You want to create two new columns 'first_name' and 'last_name', migrate the data from the 'name' column, then remove the 'name' column. (From here)

 To start off the database changes XML file we add a project:

<project> </project>

If I wanted I could add the targeted user to the project tag:

<project user="wjm"> </project>

This will issue a warning if the targeted user is different to the actual install user.

Now, I need to add the change that creates the example table. It will include a simple test for the table's existence and two statements for the table and primary key creation (note the 'exists' parameter - it informs the test when it should proceed. In this case it needs to proceed when the object does not exist. The 'exists' value is true if a row is returned by the test SQL, and false when no row is returned.):

<project user="wjm">
  <change>
    <sqltest exists="false">
      SELECT NULL FROM USER_OBJECTS
      WHERE OBJECT_NAME = 'PERSONS'
    </sqltest>
    <sqlaction>
      CREATE TABLE persons (
        person_id INTEGER
       ,full_name VARCHAR2(300)
      )
    </sqlaction>
    <sqlaction>
      ALTER TABLE persons ADD CONSTRAINT person_pk (
      PRIMARY KEY (person_id)
      USING INDEX
    </sqlaction>
  </change>
</project>

So...after running this into production, it has been in use for a number of years. Many people have been added to the table. We now want to store first and last names separately. Luckily for us our interface has formatted all inserts as "FIRST_NAME space LAST_NAME".

To the end of the database script we add a change to accomodate the new columns, the data migration and the removal of the old column:

<project user="wjm">
  <change>
    <sqltest exists="false">
      SELECT NULL FROM USER_OBJECTS
      WHERE OBJECT_NAME = 'PERSONS'
    </sqltest>
    <sqlaction>
      CREATE TABLE persons (
        person_id INTEGER
       ,full_name VARCHAR2(300)
      )
    </sqlaction>
    <sqlaction>
      ALTER TABLE persons ADD CONSTRAINT person_pk (
      PRIMARY KEY (person_id)
      USING INDEX
    </sqlaction>
  </change>
  <change>
    <sqltest exists="true">
      SELECT NULL FROM USER_TAB_COLUMNS
      WHERE TABLE_NAME = 'PERSONS' AND COLUMN_NAME = 'FULL_NAME'
    </sqltest>
    <sqlaction>
      ALTER TABLE persons ADD (
      first_name VARCHAR2(150))
    </sqlaction>
    <sqlaction>
      ALTER TABLE persons ADD (
      last_name VARCHAR2(150))
    </sqlaction>
    <sqlaction>
      UPDATE persons
      SET first_name = SUBSTR(full_name, 1, INSTR(full_name, ' ') - 1)
    </sqlaction>
    <sqlaction>
      UPDATE persons
      SET last_name = SUBSTR(full_name, INSTR(full_name, ' ') + 1)
    </sqlaction>
    <sqlaction>
      ALTER TABLE persons
      DROP COLUMN full_name
    </sqlaction>
  </change>
</project>

That's it! Whenever that script is run it will now update existing installations with the change. The next question is how to run it?

Note: just as the example displays several actions in one change, it is also possible to specify any number of tests.

Using External SQL Files and Custom Tests. The xml could be:

<project user="wjm">
  <change>
    <tabletest exists="false" table="persons" />
    <sqlaction file="${db.src.dir}/persons.tab" />
    <sqlaction file="${db.src.dir}/person_pk.con" />
  </change>
  <change>
    <columntest exists="true" table="persons" column="full_name" />
    <sqlaction file="${db.src.dir}/persons_0001.tab" />
  </change>
</project>

Using a version table (similar to ideas discussed in Generic Migrations). The xml could be:

<project user="wjm">
  <change>
    <versiontest exists="false" version="1.0.0.0" />
    <sqlaction file="${db.src.dir}/persons.tab" />
    <sqlaction file="${db.src.dir}/person_pk.con" />
    <versionupdate version="1.0.0.0" />
  </change>
  <change>
    <versiontest exists="false" version="1.0.1.0" />
    <sqlaction file="${db.src.dir}/persons_0001.tab" />
    <versionupdate version="1.0.1.0" />
  </change>
</project>

Installation

You should have the Java Runtime (1.5/5 +) installed.

Download the install file and unzip the contents.

Ensure that you have the JDBC driver for your database in your CLASSPATH.

Running MIGRATEdb

From the command line

You can execute MIGRATEdb from the command line by adding the migratedb.jar to your classpath and entering "java net.sf.migratedb.migration.Main". It requires a number of parameters:

C:\migratedb\bin>java net.sf.migratedb.migration.Main

Error: Parameter 'user' is required.
Error: Parameter 'password' is required.
Error: Parameter 'url' is required.
Error: Parameter 'driver' is required.
Error: Parameter 'file' is required.

Usage: java net.sf.migratedb.migration.Main
                -u <user> -p <password> -l <url> -d <driver> -f <file> [-x] [-v] [-s <name=value>] [-m <custom>] [-o <output>]


  -u <user>
        The database user name.

  -p <password>
        The database user password.

  -l <url>
        The connection url, for example jdbc:oracle:thin:@host:port:dbsid

  -d <driver>
        The database JDBC driver (remember to provide classpath access to the JAR file)

  -f <file>
        The DDL definition file.

  [-x]
        Apply the changes to the database - otherwise, only output what would happen to the console

  [-v]
        Output detailed progress messages

  [-s <name=value>]
        A substitution parameter - there may be as many of these as you need

  [-m <custom>]
        The custom migrations file.

  [-o <output>]
        The SQL output file.

As an ANT target

  1. First create an ANT task definition:
  2. <taskdef name="dbrelease" classname="net.sf.dbmigrate.ant.DbMigrateTask" classpath="lib/dbmigrate.jar" />
  3. Then create your target:
  4. <target name="db-migrate" description="Migrate database objects to latest version">
      <dbrelease
        driver="oracle.jdbc.OracleDriver"
        url="jdbc:oracle:thin:@host:port:sid"
        userid="user"
        password="password"
        apply="true"
        file="db.xml" />
    </target>

To turn on verbose output from the ANT target add the following:

    verbose="true"

To use a custom migrations file (see Advanced topics):

    customfile="oracle.xml"

To output the SQL that is executed to a file - each SQL statement will be finished with a ';' and '/' on a new line:

    outputfile="script.sql"

Advanced topics

Substitution Parameters

There are times where string substitution may be required. For, example for the password part of a CREATE USER statement.

Substitutions can be specified with Ampersand or Dollar formats:

CREATE USER warren IDENTIFIED BY ${db.password}
CREATE USER warren IDENTIFIED BY &db.password

The user.xml file would look like:

<project>
  <change>
    <sqltest exists="false">
      SELECT NULL FROM ALL_USERS
      WHERE USERNAME = 'WARREN'
    </sqltest>
    <sqlaction>
      CREATE USER warren IDENTIFIED BY ${db.password}
    </sqlaction>
  </change>
</project>

There are two ways of specifying a value for the parameter based on how you run the migration.

From the Command Line

java net.sf.migratedb.migration.Main -u user -p password -d oracle.jdbc.OracleDriver -l jdbc:oracle:thin:@host:port:sid -f user.xml -x -s db.password=xyzzy

Note that the -s flagged parameter can be specified for as many parameters as needed.

From an ANT target

<target name="db-user" description="Create the user">
  <dbrelease
    driver="oracle.jdbc.OracleDriver"
    url="jdbc:oracle:thin:@host:port:sid"
    userid="user"
    password="password"
    apply="true"
    file="user.xml">
      <property name="db.password" value="xyzzy" />
  </dbrelease
</target>

Note:

External SQL Files

It is possible to use external SQL files rather than statements inside the XML.

Using user.xml from the previous section as an example:

<project>
  <change>
    <sqltest exists="false" file="user-test.sql" />
    <sqlaction path="${sql.path}" file="create-user.sql" />
  </change>
</project>

The create-user.sql file could contain the following (demonstrating different valid ways for delimiting the SQL statement) - Note that the path parameter is optional:

CREATE USER ${db.user}
IDENTIFIED BY ${db.password} ;
GRANT CREATE TABLE TO ${db.user}
/
GRANT CREATE SESSION TO $db.user};
/

Custom tests

MIGRATEdb will handle a custom XML file with configured tests and/or actions. (MIGRATEdb is already packaged with some custom migration files, currently for the Derby and Oracle databases).

To make use of the custom migrations use code similar to:

<change>
  <tabletest exists="false" table="new_table" />
...
</change>

An example of the format for the content of the migrations file is listed below.

The only considerations to be aware of when adding a custom migration are:

<migrations database="oracle">
  <test>
    <tabletest>
      SELECT TABLE_NAME FROM ALL_TABLES WHERE TABLE_NAME = UPPER('${table}')
    </tabletest>
  </test>
  <test>
    <sequencetest>
      SELECT SEQUENCE_NAME FROM ALL_SEQUENCES WHERE SEQUENCE_NAME = UPPER('${sequence}')
  </sequencetest>
  </test>
  <test>
    <usertest>
      SELECT USERNAME FROM ALL_USERS WHERE USERNAME = UPPER('${user}')
    </usertest>
  </test>
  <test>
    <privilegetest>
      SELECT PRIVILEGE FROM DBA_SYS_PRIVS WHERE GRANTEE = UPPER('${grantee}') AND PRIVILEGE = UPPER('${privilege}')
    </privilegetest>
  </test>
  <action>
    <tabledrop>
      DROP TABLE ${table}
    </tabledrop>
  </action>
</migrations>

Generic Migrations

What if the database in use has no data dictionary that can be tested for the existence of objects via SQL? For example, using MYSQL before v5.0 (MYSQL 5.0 has a data dictionary).

If you install a MIGRATIONS table that records at least a migration identifier...

CREATE TABLE migrations (
  migration varchar(100),
  migration_date timestamp)

You can then setup tests that query the MIGRATIONS table. The migrations demonstrated below are used by setting the custom migrations file to be 'generic-migrations.xml'. That file is included in the release. Note the 'migrationtest' test - it queries the migrations table for the existence of the migration. Also note the 'migration' action - it registers the migration in the migrations table.

<project>
  <change>
    <migrationtest exists="false" migration="one" />
    <sqlaction>
      CREATE TABLE persons (
        person_id INTEGER
       ,full_name VARCHAR2(300)
      )
    </sqlaction>
    <sqlaction>
      ALTER TABLE persons ADD CONSTRAINT person_pk (
      PRIMARY KEY (person_id)
      USING INDEX
    </sqlaction>
    <migration migration="one" />
  </change>
</project>