Database Migration with structsy.

Structsy as any other database has to provide a way to migrate data from old version of the software to new version, being structsy an embedded database that do not have a query language it cannot provide migration scripts, so in the same way of the other parts of structsy, we need to exploit some feature of rust to do data migration, but let's start from the beginning let's define an example struct that represent the first version of our software:

use structsy_defive::Persistent;

type Person = PersonV0;

#[derive(Persistent)] 
struct PersonV0 {
    name: String,
    surname: String,
}

This is a simple example of struct persisted with structsy, as you can see from this example I'm already naming the structy V0 and define a type alias, this is a good practice to keep the name clean and do easy evolution without massive refactors. One important note is also that changing the name of a struct is also a compatibility issue, so call the first version just Person than force us to come up with a different name for the second version, doing a type alias hide all this name complexity just at the struct definition.

At the moment we have to do a new version of the Person struct, let's say because we want to add an email field, we can just define a new struct with the additional fields and implement a from the old struct, here the complete new code for the new version.

use structsy_defive::Persistent;

type Person = PersonV1;

#[derive(Persistent)] 
struct PersonV0 {
    name: String,
    surname: String,
}

#[derive(Persistent)] 
struct PersonV1 {
    name: String,
    surname: String,
    email: Option<String>, 
}

impl From<PersonV0> for PersonV1 {
    fn from(old:PersonV0) -> Self {
        PersonV1 {
            name:old.name,
            surname: old.surname,
            email : None
        }
    }
}

at this point we have a new version of our Person struct, we changed the alias to point to the new version so most of the code do not need to be changed, now is missing only the last step to handle data migration on the open of a database, if is needed, so starting from a usual:

use structsy::{Structsy,SRes};

fn open_database(path:&str) -> SRes<Structsy> {
    let db = Structsy::open(path)?;
    db.define::<Person>()?;
    Ok(db)
}

we just change this code to add the migration logic:

use structsy::{Structsy,SRes};

fn open_database(path:&str) -> SRes<Structsy> {
    let prepare = Structsy::prepare_open(path)?;
    prepare.migrate::<PersonV0,PersonV1>()?;
    let db = prepare.open()?;
    db.define::<Person>()?;
    Ok(db)
}

In here before to open a structsy instance we get a 'prepare open' instance that allows us to do all the migrations needed. This migration are run in two steps internally and are transactional, in the first step are collected all the record to be migrated, in the second steps the record are migrated one by one to the new version. In case of crash while a migration is running when the application is restarted the migration will restart as well, if the crash is during the first step the migration is started from scratch, if the crash happen during the second step, the migration will restart from the record were left at, before the crash. If the migration is finished successfully the subsequent call to the migrate in subsequent restarts do nothing.

As today the migration is handled only forward automatically, anyway if you need a backward migration it is possible to write an application that execute the migration backward.

That's all folks