Changes between Initial Version and Version 1 of SchemaEvolutionDocumentation


Ignore:
Timestamp:
Jul 31, 2007, 11:28:02 AM (17 years ago)
Author:
public@…
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • SchemaEvolutionDocumentation

    v1 v1  
     1= Schema Evolution Documentation =
     2
     3== Introduction ==
     4
     5Schema evolution is the function of updating an existing Django generated database schema to a newer/modified version based upon a newer/modified set of Django models.
     6
     7=== Limitations ===
     8
     9I feel it important to note that is an automated implementation designed to handle schema ''evolution'', not ''revolution''.  No tool, other than storing DBA written SQL scripts and auto-applying them via schema versioning or DB fingerprinting (which is a trivial solution - I have a Java implementation if anyone wants it), can handle the full scope of possible database changes.  Once you accept this fact, the following becomes self-evident:
     10
     11 * There is a trade off between ease of use and the scope of coverable problems.
     12
     13Combine that with:
     14
     15 * The vast majority of database changes are minor, evolutionary tweaks. (*)
     16 * Very few people are DBAs.
     17
     18And I believe the ideal solution is in easing the life of common Django developer, not in appeasing the DBA's or power-developer's desire for an all-in-one-comprehensive solution.  Massive schema changes (w/ data retention) are always going to require someone with database skill, but we can empower the people to do the simple things for themselves.
     19
     20(*) By this I mean adding/removing/renaming tables and adding/removing/renaming/changing-attributes-of columns.
     21
     22== Downloading / Installing ==
     23
     24This functionality is not yet in Django/trunk, but in a separate schema-evolution branch.  To download this branch, run the following:
     25
     26{{{
     27svn co http://code.djangoproject.com/svn/django/schema-evolution/ django_se_src
     28ln -s `pwd`/django_se_src/django SITE-PACKAGES-DIR/django
     29}}}
     30
     31Or, if you're currently running Django v0.96, run the following:
     32
     33{{{
     34cd /<path_to_python_dir>/site-packages/django/
     35wget http://kered.org/blog/wp-content/uploads/2007/07/django_schema_evolution-v096patch.txt
     36patch -p1 < django_schema_evolution-v096patch.txt
     37}}}
     38
     39The last command will produce the following output:
     40
     41{{{
     42patching file core/management.py
     43patching file db/backends/mysql/base.py
     44patching file db/backends/mysql/introspection.py
     45patching file db/backends/postgresql/base.py
     46patching file db/backends/postgresql/introspection.py
     47patching file db/backends/sqlite3/base.py
     48patching file db/backends/sqlite3/introspection.py
     49patching file db/models/fields/__init__.py
     50patching file db/models/options.py}}}
     51}}}
     52
     53== How To Use ==
     54
     55For the most part, schema evolution is designed to be automagic via introspection.  Make changes to your models, run syncdb, and you're done.  But like all schema changes, it's wise to preview what is going to be run.  To do this, run the following:
     56
     57{{{
     58./manage sqlevolve app_name
     59}}}
     60
     61This will output to the command line the SQL to be run to bring your database schema up to date with your model structure.
     62
     63However not everything can be handled through introspection.  A small amount of metadata is used in the cases of model or field renames, so that the introspection code can match up the old field to the new field. (therefore preserving your data)
     64
     65For renaming a column, use an "aka" attribute:
     66
     67{{{
     68    # this field used to be called pub_date
     69    publish_date = models.DateTimeField('date published', aka='pub_date')
     70}}}
     71
     72If you have renamed this twice and still wish to support migration from both older schemas, "aka"s can be tuples:
     73
     74{{{
     75    # this field used to be called pub_date
     76    publish_date = models.DateTimeField('date published', aka=('pub_date','other_old_field_name'))
     77}}}
     78
     79For renaming a model, add an "aka" field to the Meta section:
     80
     81{{{
     82# the original name for this model was 'Choice'
     83class Option(models.Model):
     84    [...]
     85    class Meta:
     86        aka = 'Choice'
     87}}}
     88
     89For further examples...
     90
     91== Usage Examples ==
     92
     93The following documentation will take you through several common model changes and show you how Django's schema evolution handles them. Each example provides the pre and post model source code, as well as the SQL output.
     94
     95=== Adding / Removing Fields ===
     96
     97Model: version 1
     98
     99{{{
     100    from django.db import models
     101   
     102    class Poll(models.Model):
     103        question = models.CharField(maxlength=200)
     104        pub_date = models.DateTimeField('date published')
     105        author = models.CharField(maxlength=200)
     106        def __str__(self):
     107            return self.question
     108       
     109    class Choice(models.Model):
     110        poll = models.ForeignKey(Poll)
     111        choice = models.CharField(maxlength=200)
     112        votes = models.IntegerField()
     113        def __str__(self):
     114            return self.choice
     115}}}
     116
     117Model: version 2
     118
     119{{{     
     120    from django.db import models
     121   
     122    class Poll(models.Model):
     123        question = models.CharField(maxlength=200)
     124        pub_date = models.DateTimeField('date published')
     125        author = models.CharField(maxlength=200)
     126        def __str__(self):
     127            return self.question
     128   
     129        # new fields
     130        pub_date2 = models.DateTimeField('date published')
     131
     132    class Choice(models.Model):
     133        poll = models.ForeignKey(Poll)
     134        choice = models.CharField(maxlength=200)
     135        votes = models.IntegerField()
     136        def __str__(self):
     137            return self.choice
     138   
     139        # new fields
     140        votes2 = models.IntegerField()
     141        hasSomething = models.BooleanField()
     142        creatorIp = models.IPAddressField()
     143}}}
     144
     145Output: v1⇒v2   
     146
     147{{{
     148    BEGIN;
     149    ALTER TABLE `case01_add_field_poll` ADD COLUMN `pub_date2` datetime NOT NULL;
     150    ALTER TABLE `case01_add_field_choice` ADD COLUMN `votes2` integer NOT NULL;
     151    ALTER TABLE `case01_add_field_choice` ADD COLUMN `hasSomething` bool NOT NULL;
     152    ALTER TABLE `case01_add_field_choice` ADD COLUMN `creatorIp` char(15) NOT NULL;
     153    COMMIT;
     154}}}
     155
     156Output: v2⇒v1   
     157
     158{{{
     159    -- warning: as the following may cause data loss, it/they must be run manually
     160    -- ALTER TABLE `case01_add_field_poll` DROP COLUMN `pub_date2`;
     161    -- end warning
     162    -- warning: as the following may cause data loss, it/they must be run manually
     163    -- ALTER TABLE `case01_add_field_choice` DROP COLUMN `votes2`;
     164    -- ALTER TABLE `case01_add_field_choice` DROP COLUMN `creatorIp`;
     165    -- ALTER TABLE `case01_add_field_choice` DROP COLUMN `hasSomething`;
     166    -- end warning
     167}}}
     168
     169=== Renaming Fields ===
     170
     171Model: version 1
     172
     173{{{
     174    from django.db import models
     175
     176    class Poll(models.Model):
     177        """this model originally had fields named pub_date and the_author.  you can use
     178        either a str or a tuple for the aka value.  (tuples are used if you have changed
     179        its name more than once)"""
     180        question = models.CharField(maxlength=200)
     181        pub_date = models.DateTimeField('date published', aka='publish_date')
     182        the_author = models.CharField(maxlength=200, aka='the_author')
     183        def __str__(self):
     184            return self.question
     185       
     186    class Choice(models.Model):
     187        poll = models.ForeignKey(Poll)
     188        choice = models.CharField(maxlength=200)
     189        votes = models.IntegerField(aka='votes')
     190        def __str__(self):
     191            return self.choice
     192}}}
     193
     194Model: version 2
     195
     196{{{
     197    from django.db import models
     198   
     199    class Poll(models.Model):
     200        """this model originally had fields named pub_date and the_author.  you can use
     201        either a str or a tuple for the aka value.  (tuples are used if you have changed
     202        its name more than once)"""
     203        question = models.CharField(maxlength=200)
     204        published_date = models.DateTimeField('date published', aka=('pub_date', 'publish_date'))
     205        author = models.CharField(maxlength=200, aka='the_author')
     206        def __str__(self):
     207            return self.question
     208       
     209    class Choice(models.Model):
     210        poll = models.ForeignKey(Poll)
     211        choice = models.CharField(maxlength=200)
     212        number_of_votes = models.IntegerField(aka='votes')
     213        def __str__(self):
     214            return self.choice
     215}}}
     216
     217Output: v1⇒v2   
     218
     219{{{
     220    BEGIN;
     221    ALTER TABLE `case02_rename_field_poll` CHANGE COLUMN `pub_date` `published_date` datetime NOT NULL;
     222    ALTER TABLE `case02_rename_field_poll` CHANGE COLUMN `the_author` `author` varchar(200) NOT NULL;
     223    ALTER TABLE `case02_rename_field_choice` CHANGE COLUMN `votes` `number_of_votes` integer NOT NULL;
     224    COMMIT;
     225}}}
     226
     227=== Renaming Models ===
     228
     229Model: version 1
     230
     231{{{
     232    from django.db import models
     233   
     234    class Poll(models.Model):
     235        question = models.CharField(maxlength=200)
     236        pub_date = models.DateTimeField('date published')
     237        author = models.CharField(maxlength=200)
     238        def __str__(self):
     239            return self.question
     240       
     241    class Choice(models.Model):
     242        "the original name for this model was 'Choice'"
     243        poll = models.ForeignKey(Poll)
     244        choice = models.CharField(maxlength=200)
     245        number_of_votes = models.IntegerField()
     246        def __str__(self):
     247            return self.choice
     248        class Meta:
     249            aka = ('Choice', 'OtherBadName')
     250}}}
     251
     252Model: version 2
     253
     254{{{
     255    from django.db import models
     256   
     257    class Poll(models.Model):
     258        question = models.CharField(maxlength=200)
     259        pub_date = models.DateTimeField('date published')
     260        author = models.CharField(maxlength=200)
     261        def __str__(self):
     262            return self.question
     263       
     264    class Option(models.Model):
     265        "the original name for this model was 'Choice'"
     266        poll = models.ForeignKey(Poll)
     267        choice = models.CharField(maxlength=200)
     268        # show that field name changes work too
     269        votes = models.IntegerField(aka='number_of_votes')
     270        def __str__(self):
     271            return self.choice
     272        class Meta:
     273            aka = ('Choice', 'BadName')
     274}}}
     275   
     276Output: v1⇒v2   
     277
     278{{{
     279    BEGIN;
     280    ALTER TABLE `case03_rename_model_choice` RENAME TO `case03_rename_model_option`;
     281    ALTER TABLE `case03_rename_model_option` CHANGE COLUMN `number_of_votes` `votes` integer NOT NULL;
     282    COMMIT;
     283}}}
     284
     285=== Changing Flags ===
     286
     287Model: version 1
     288
     289{{{
     290    from django.db import models
     291   
     292    class Poll(models.Model):
     293        question = models.CharField(maxlength=200)
     294        pub_date = models.DateTimeField('date published')
     295        author = models.CharField(maxlength=200)
     296        def __str__(self):
     297            return self.question
     298       
     299    class Choice(models.Model):
     300        "the original name for this model was 'Choice'"
     301        poll = models.ForeignKey(Poll)
     302        choice = models.CharField(maxlength=200)
     303        votes = models.IntegerField()
     304        def __str__(self):
     305            return self.choice
     306
     307    class Foo(models.Model):
     308        GENDER_CHOICES = (
     309            ('M', 'Male'),
     310            ('F', 'Female'),
     311        )
     312        gender = models.CharField(maxlength=1, choices=GENDER_CHOICES)
     313}}}
     314   
     315Model: version 2
     316
     317{{{
     318    from django.db import models
     319   
     320    class Poll(models.Model):
     321        question = models.CharField(maxlength=100)
     322        pub_date = models.DateTimeField('date published')
     323        author = models.CharField(maxlength=200)
     324        def __str__(self):
     325            return self.question
     326       
     327    class Choice(models.Model):
     328        "the original name for this model was 'Choice'"
     329        poll = models.ForeignKey(Poll)
     330        # make sure aka still works with a flag change
     331        option = models.CharField(maxlength=400, aka='choice')
     332        votes = models.IntegerField()
     333        votes2 = models.IntegerField() # make sure column adds still work
     334        def __str__(self):
     335            return self.choice
     336   
     337    class Foo(models.Model):
     338        GENDER_CHOICES = (
     339            ('M', 'Male'),
     340            ('F', 'Female'),
     341        )
     342        gender = models.CharField(maxlength=1, choices=GENDER_CHOICES, db_index=True)
     343        gender2 = models.CharField(maxlength=1, null=True, unique=True)
     344           
     345}}}
     346
     347Output: v1⇒v2   
     348
     349{{{
     350    BEGIN;
     351    ALTER TABLE `case04_change_flag_poll` MODIFY COLUMN `question` varchar(100) NOT NULL;
     352    ALTER TABLE `case04_change_flag_foo` ADD COLUMN `gender2` varchar(1) NULL UNIQUE;
     353    ALTER TABLE `case04_change_flag_choice` MODIFY COLUMN `choice` varchar(400) NOT NULL;
     354    ALTER TABLE `case04_change_flag_choice` CHANGE COLUMN `choice` `option` varchar(400) NOT NULL;
     355    ALTER TABLE `case04_change_flag_choice` ADD COLUMN `votes2` integer NOT NULL;
     356    COMMIT;
     357}}}
     358
     359== Conclusion ==
     360
     361That's pretty much it. If you can suggest additional examples or test cases you
     362think would be of value, please email me at public@kered.org.
Back to Top