[ZODB-Dev] Support for graceful ZODB Class renaming
holger krekel
pyth@devel.trillke.net
Sun, 19 Jan 2003 14:30:24 +0100
Pieter Nagel wrote:
> On Sun, Jan 19, 2003 at 11:50:09AM +0000, Toby Dickenson wrote:
>
> > I assume that other classes would have self.my_portfolio attributes that you
> > would also want to rename? How do you cope with this?
>
> By renaming the attributes :-)
>
> I have a migration framework that copes with class renamings, class
> deletions, and attribute changes quite nicely all in one. It works like this:
>
> The work to be done to migrate a database is specified declaratively,
> stored on a Migrator object.
>
> The Migrator stores two things:
> 1) A mapping of "old class name" to "new class".
> 2) A list of migration phases. A migration phases consists of two
> items: a specification of all objects to operate on, and the function to
> be invoked on these.
>
> When the Migrator migrates a database, it first sets up a classFactory on
> the database that will instantiate any pickles of classes under an old
> name in the mapping as instances of the new class. It sweeps throught the
> database, activating all the objects in it - which ensures, as side effect,
> that all instances of renamed classes are re-pickled under their new name.
>
> Then it proceeds through all the migration phases, in order, and invokes
> migration methods on all the objects to be migrated.
>
> Lastly, it calls 'checkIntegrity' on all objects in the database that
> implement it. This is to test that the migration did, indeed, result in
> what one expected.
>
> Deleted classes are handled by renaming instances of the deleted class to
> a new stub class that contains a checkIntegrity() method that always
> barfs. This is so one can be certain one actually got rid of them.
>
> So the work the end-user needs to do to migrate a database looks like
> this:
>
> def migrateNewPerson(person):
> person.fullname = person.firstname + ' ' + person.surname
> if not (person.telephone.strip()):
> #OOPS! Fix old data that treated whitespace numbers as being 'real'
> person.telephone = None
>
> import NewPerson, Migrator, RenameAttribute
>
> migrator = Migrator()
> migrator.renameClasses('SomeModule.OldPerson', NewPerson)
> migrator.addPhase(Instances(NewPerson), migrateNewPerson)
> migrator.addPhase(Instances(NewPerson), RenameAttribute('portfolio', 'fundHoldings'))
>
> migrator.migrate(aDatabaseConnection)
>
> In practice this has served me well throughout many renamings, add-hoc
> data fixes, and computed migrations.
>
> It still needs to be ported to ZODB4 HEAD (to cope with the disapperance
> of setClassFactory - metaclass.__new__ seems the way to go), and can still
> be optimized to not sweep through the entire database gratuitously.
>
> But the approach of refactoring data in a production database to a sane
> state at each deployment is definitely feasible. I've done it in ZODB and
> GemStone/Smalltalk for years. It works.
interesting approach. Do you also cope with exported data (say to
xml) which gets reimported later when the scheme has already changed?
Do you have a history list of Migrators that would get applied?
holger