Skip to content
July 13, 2010 / cohodo

Automatically Creating Inverse Changesets and When They Don't Behave as Expected

The Talis Platform uses changesets as a mechanism for updating RDF. As the configuration of the Platform is itself stored as RDF, we also use changesets to modify its configuration. This can be as part of a release or to make requested changes to a customer’s store.

I recently needed to apply a large number of changesets to the Platform configuration. But before applying them, I wanted to create another set of changesets which would, if necessary, reverse all the changes – I wanted to be able to rollback if anything went wrong.

So my changesets looked something like this:

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:cs="http://purl.org/vocab/changeset/schema#">
   <cs:ChangeSet rdf:about="http://example.com/changesets#change-1">
    <cs:subjectOfChange rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
    <cs:removal>
      <rdf:Statement>
        <rdf:subject rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
        <rdf:predicate rdf:resource="http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty"/>
        <rdf:object rdf:resource="http://api.talis.com/stores/mystore/exampleconfig/old"/>
      </rdf:Statement>
    </cs:removal>
    <cs:addition>
      <rdf:Statement>
        <rdf:subject rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
        <rdf:predicate rdf:resource="http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty"/>
        <rdf:object rdf:resource="http://api.talis.com/stores/mystore/exampleconfig/new"/>
      </rdf:Statement>
    </cs:addition>
  </cs:ChangeSet>
</rdf:RDF>

This changeset can be reversed by changing the removals to additions and changing the additions to removals. This is easy to achieve with sed:

for f in changesetdirectory/* ; do
  sed -e 's/cs:addition/TOBEAREMOVAL/' -e 's/cs:removal/TOBEANADDITION/' \
    -e 's/TOBEAREMOVAL/cs:removal/'  -e 's/TOBEANADDITION/cs:additon/' $f > rollback/$f
done

The above script creates an inverse of every changeset in the specified changesetdirectory and places them in the rollback directory. The inverse of the example changeset above is created as below:

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:cs="http://purl.org/vocab/changeset/schema#">
   <cs:ChangeSet rdf:about="http://example.com/changesets#change-1">
    <cs:subjectOfChange rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
    <cs:addition>
      <rdf:Statement>
        <rdf:subject rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
        <rdf:predicate rdf:resource="http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty"/>
        <rdf:object rdf:resource="http://api.talis.com/stores/mystore/exampleconfig/old"/>
      </rdf:Statement>
    </cs:addition>
    <cs:removal>
      <rdf:Statement>
        <rdf:subject rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
        <rdf:predicate rdf:resource="http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty"/>
        <rdf:object rdf:resource="http://api.talis.com/stores/mystore/exampleconfig/new"/>
      </rdf:Statement>
    </cs:removal>
  </cs:ChangeSet>
</rdf:RDF>

So the original changeset removes the triple:

http://api.talis.com/stores/mystore/exampleconfig
http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty
http://api.talis.com/stores/mystore/exampleconfig/old

and replaces it with:

http://api.talis.com/stores/mystore/exampleconfig
http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty
http://api.talis.com/stores/mystore/exampleconfig/new

The inverse changeset removes the triple:

http://api.talis.com/stores/mystore/exampleconfig
http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty
http://api.talis.com/stores/mystore/exampleconfig/new

and replaces the original:

http://api.talis.com/stores/mystore/exampleconfig
http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty
http://api.talis.com/stores/mystore/exampleconfig/old

Using this technique, I successfully created inverse changesets which, if I had needed to, would have rolled back the changes to the configuration.

However, there is a caveat. The set semantics of a triplestore can be a gotcha.

Suppose the following triple already exists:

http://api.talis.com/stores/mystore/exampleconfig
http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty
http://api.talis.com/stores/mystore/exampleconfig/alreadyexists

The following changeset could be applied:

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:cs="http://purl.org/vocab/changeset/schema#">
   <cs:ChangeSet rdf:about="http://example.com/changesets#change-1">
    <cs:subjectOfChange rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
    <cs:addition>
      <rdf:Statement>
        <rdf:subject rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
        <rdf:predicate rdf:resource="http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty"/>
        <rdf:object rdf:resource="http://api.talis.com/stores/mystore/exampleconfig/alreadyexists"/>
      </rdf:Statement>
    </cs:addition>
  </cs:ChangeSet>
</rdf:RDF>

This changeset is accepted but doesn’t actually modify the triples as the triple it adds already existed. Creating an inverse of this changeset gives us:

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:cs="http://purl.org/vocab/changeset/schema#">
   <cs:ChangeSet rdf:about="http://example.com/changesets#change-1">
    <cs:subjectOfChange rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
    <cs:removal>
      <rdf:Statement>
        <rdf:subject rdf:resource="http://api.talis.com/stores/mystore/exampleconfig"/>
        <rdf:predicate rdf:resource="http://schemas.talis.com/2006/bigfoot/configuration#exampleproperty"/>
        <rdf:object rdf:resource="http://api.talis.com/stores/mystore/exampleconfig/alreadyexists"/>
      </rdf:Statement>
    </cs:removal>
  </cs:ChangeSet>
</rdf:RDF>

However, applying the inverse changeset removes the triple. As the triple existed before applying the first changeset the inverse of the changeset did not have the result we were looking for. It ended up deleting the triple which existed before we started.

So creating inverse changesets in this way can be useful, but only when you know with certainty that any triples added in the original changeset did not already exist.

Advertisements

2 Comments

Leave a Comment
  1. Mhermans / Jul 14 2010 6:27 pm

    Reminds me: what do you think about these proposed extentions to the changeset mechanism?

  2. Uldis Bojars / Aug 9 2010 8:05 pm

    A possible solution: make the RDF store to generate and return you a “rollback changeset” which contains updates needed to reverse the changeset you are giving it. Since the RDF store knows exactly what triples will be changed, it can give you a correct rollback changeset.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: