I recently discovered a mildly annoying bug in Sqitch, the Git-inspired database schema change management app I’ve been working on for the past year. One of its key features is the ability to “rework” changes. For example, if you have a change that defines a function
change_password(), and discover sometime after release that it has a bug (maybe the hashing algorithm is too weak), you can “rework” it – essentially modify it in place – and save some headaches. Check out the “In Place Changes” section of the (PostgreSQL, SQLite, Oracle, or MySQL (coming soon) tutorials for detailed examples of how it works.
The bug was about what happens when one adds a new change that depends on a reworked change, but just specifies it by name, such as
sqitch add meow --requires change_password
This added the change fine, but at deploy time, Sqitch complained that there were multiple instances of a change in the database. Of course, that’s true, because
change_password will have been deployed twice: once for the original version, and the second time for the reworked version. This was inconsistent with how it looked up changes in the plan, where it would just return the first instance of a change in the plan. So I changed it so that dependency lookups in the database also return the first instance of the change. I believe this makes sense, because if you require
change_password, without specifying which instance you want, you probably want any instance, starting with the earliest.
But what if you actually need to require a specific instance of a reworked change? Let’s say your plan looks like this:
users widgets change_pass sleep @v1.0 work_stuff change_pass [firstname.lastname@example.org]
The third change is
change_pass, and it has been reworked in the sixth change (requiring the previous version, as of the
@v1.0 tag). If you want to require any instance of
change_pass, you specify it as in the previous example. But what if there were changes in the reworked version that you require? You might try to require it as-of the symbolic tag
sqitch add meow --requires change_password@HEAD
This means, “Require the last instance of
change_password in the plan.” And that would work…until you reworked it again, then it would be updated to point at the newer instance. Sqitch will choke on that, because you can’t require changes that appear later in the plan.
So what we have to do instead is add a new tag after the second instance of
sqitch tag rehash
Now the plan will look like this:
users widgets change_pass sleep @v1.0 work_stuff change_pass [email@example.com] @rehash
Now we can identify exactly the instance we need by specifying that tag:
sqitch add meow --requires change_password@rehash
Meaning “The instance of
change_password as of
@rehash.” If what you really needed was the first version, you can specify the tag that follows it:
sqitch add meow --requires firstname.lastname@example.org
Which, since it is the first instance is the same as specifying no tag at all. But if there were, say, four instances of
change_pass, you can see how it might be important to use tags to specify specific instances for dependencies.
For what it’s worth, this is how to get around the original bug referenced above: just specify which instance of the change to require by using a tag that follows that instance, and the error should go away.