~ubuntu-branches/ubuntu/saucy/migrate/saucy-proposed

« back to all changes in this revision

Viewing changes to docs/versioning.rst

  • Committer: Bazaar Package Importer
  • Author(s): Jan Dittberner
  • Date: 2010-07-12 00:24:57 UTC
  • mfrom: (1.1.5 upstream) (2.1.8 sid)
  • Revision ID: james.westby@ubuntu.com-20100712002457-4j2fdmco4u9kqzm5
Upload to unstable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
.. _versioning-system:
 
2
.. currentmodule:: migrate.versioning
 
3
.. highlight:: console
2
4
 
3
 
**************************
4
 
Database schema versioning
5
 
**************************
 
5
***********************************
 
6
Database schema versioning workflow
 
7
***********************************
6
8
 
7
9
SQLAlchemy migrate provides the :mod:`migrate.versioning` API that is
8
 
also available as the :command:`migrate` command.
9
 
 
10
 
.. program:: migrate
11
 
 
12
 
Project Setup
 
10
also available as the :ref:`migrate <command-line-usage>` command.
 
11
 
 
12
Purpose of this package is frontend for migrations. It provides commands
 
13
to manage migrate repository and database selection aswell as script versioning.
 
14
 
 
15
 
 
16
Project setup
13
17
=============
14
18
 
15
19
.. _create_change_repository:
18
22
--------------------------
19
23
 
20
24
To begin, we'll need to create a *repository* for our
21
 
project. Repositories are associated with a single database schema,
22
 
and store collections of change scripts to manage that schema. The
23
 
scripts in a repository may be applied to any number of databases.
24
 
 
25
 
Repositories each have a name. This name is used to identify the
26
 
repository we're working with.
27
 
 
28
 
All work with repositories is done using the migrate command. Let's
 
25
project.
 
26
 
 
27
All work with repositories is done using the :ref:`migrate <command-line-usage>` command. Let's
29
28
create our project's repository::
30
29
 
31
 
 % migrate create my_repository "Example project"
32
 
 
33
 
This creates an initially empty repository in the current directory at
34
 
my_repository/ named Example project. The repository directory
35
 
contains a sub directory versions that will store the schema versions,
 
30
 $ migrate create my_repository "Example project"
 
31
 
 
32
This creates an initially empty repository relative to current directory at
 
33
my_repository/ named `Example project`.
 
34
 
 
35
The repository directory
 
36
contains a sub directory :file:`versions` that will store the :ref:`schema versions <changeset-system>`,
36
37
a configuration file :file:`migrate.cfg` that contains
37
 
:ref:`repository configuration <repository_configuration>`, a
38
 
:file:`README` file containing information that the directory is an
39
 
sqlalchemy-migrate repository and a script :file:`manage.py` that has
40
 
the same functionality as the :command:`migrate` command but is
41
 
preconfigured with the repository.
42
 
 
43
 
Version-control a database
 
38
:ref:`repository configuration <repository_configuration>` and a script :ref:`manage.py <project_management_script>`
 
39
that has the same functionality as the :ref:`migrate <command-line-usage>` command but is
 
40
preconfigured with repository specific parameters.
 
41
 
 
42
.. note::
 
43
 
 
44
    Repositories are associated with a single database schema,
 
45
    and store collections of change scripts to manage that schema. The
 
46
    scripts in a repository may be applied to any number of databases.
 
47
    Each repository has an unique name. This name is used to identify the
 
48
    repository we're working with.
 
49
 
 
50
 
 
51
Version control a database
44
52
--------------------------
45
53
 
46
 
Next, we need to create a database and declare it to be under version
47
 
control. Information on a database's version is stored in the database
 
54
Next we need to declare database to be under version control.
 
55
Information on a database's version is stored in the database
48
56
itself; declaring a database to be under version control creates a
49
 
table, named 'migrate_version' by default, and associates it with your
50
 
repository.
 
57
table named **migrate_version** and associates it with your repository.
51
58
 
52
59
The database is specified as a `SQLAlchemy database url`_.
53
60
 
56
63
 
57
64
::
58
65
 
59
 
 % python my_repository/manage.py version_control sqlite:///project.db
 
66
 $ python my_repository/manage.py version_control sqlite:///project.db
60
67
 
61
68
We can have any number of databases under this repository's version
62
69
control.
65
72
script applied to the database increments this version number. You can
66
73
see a database's current version::
67
74
 
68
 
 % python my_repository/manage.py db_version sqlite:///project.db
 
75
 $ python my_repository/manage.py db_version sqlite:///project.db
69
76
 0 
70
77
 
71
78
A freshly versioned database begins at version 0 by default. This
77
84
Similarly, we can also see the latest version available in a
78
85
repository with the command::
79
86
 
80
 
 % python my_repository/manage.py version
 
87
 $ python my_repository/manage.py version
81
88
 0
82
89
 
83
90
We've entered no changes so far, so our repository cannot upgrade a
93
100
our project that remembers the database and repository we're using,
94
101
and use it to perform commands::
95
102
 
96
 
 % migrate manage manage.py --repository=my_repository --url=sqlite:///project.db
97
 
 % python manage.py db_version
 
103
 $ migrate manage manage.py --repository=my_repository --url=sqlite:///project.db
 
104
 $ python manage.py db_version
98
105
 0
99
106
 
100
107
The script manage.py was created. All commands we perform with it are
101
 
the same as those performed with the 'migrate' tool, using the
 
108
the same as those performed with the :ref:`migrate <command-line-usage>` tool, using the
102
109
repository and database connection entered above. The difference
103
110
between the script :file:`manage.py` in the current directory and the
104
111
script inside the repository is, that the one in the current directory
105
112
has the database URL preconfigured.
106
113
 
 
114
.. note::
 
115
 
 
116
   Parameters specified in manage.py should be the same as in :ref:`versioning api <versioning-api>`.
 
117
   Preconfigured parameter should just be omitted from :ref:`migrate <command-line-usage>` command.
 
118
 
107
119
 
108
120
Making schema changes
109
121
=====================
117
129
Create a change script
118
130
----------------------
119
131
 
120
 
Our first change script will create a simple table::
121
 
 
122
 
 account = Table('account',meta,
123
 
     Column('id',Integer,primary_key=True),
124
 
     Column('login',String(40)),
125
 
     Column('passwd',String(40)),
126
 
 )
 
132
Our first change script will create a simple table
 
133
 
 
134
.. code-block:: python
 
135
 
 
136
         account = Table('account', meta,
 
137
                         Column('id', Integer, primary_key=True),
 
138
                         Column('login', String(40)),
 
139
                         Column('passwd', String(40)),
 
140
         )
127
141
 
128
142
This table should be created in a change script. Let's create one::
129
143
 
130
 
 % python manage.py script "Add account table"
 
144
 $ python manage.py script "Add account table"
131
145
 
132
146
This creates an empty change script at
133
147
:file:`my_repository/versions/001_Add_account_table.py`. Next, we'll
134
148
edit this script to create our table.
135
149
 
 
150
 
136
151
Edit the change script
137
152
----------------------
138
153
 
139
 
Our change script defines two functions, currently empty:
140
 
``upgrade()`` and ``downgrade()``. We'll fill those in::
141
 
 
142
 
  from sqlalchemy import *
143
 
  from migrate import *
144
 
  
145
 
  meta = MetaData(migrate_engine)
146
 
  account = Table('account', meta,
147
 
                  Column('id', Integer, primary_key=True),
148
 
                  Column('login', String(40)),
149
 
                  Column('passwd', String(40)),
150
 
                  )
151
 
  
152
 
  def upgrade():
153
 
      account.create()
154
 
  
155
 
  def downgrade():
156
 
      account.drop()
157
 
 
158
 
As you might have guessed, upgrade() upgrades the database to the next
159
 
version. This function should contain the changes we want to perform;
160
 
here, we're creating a table. downgrade() should reverse changes made
161
 
by upgrade(). You'll need to write both functions for every change
162
 
script. (Well, you don't *have* to write downgrade(), but you won't be
 
154
Our change script predefines two functions, currently empty:
 
155
:func:`upgrade` and :func:`downgrade`. We'll fill those in
 
156
 
 
157
.. code-block:: python
 
158
 
 
159
    from sqlalchemy import *
 
160
    from migrate import *
 
161
 
 
162
    meta = MetaData()
 
163
 
 
164
    account = Table('account', meta,
 
165
        Column('id', Integer, primary_key=True),
 
166
        Column('login', String(40)),
 
167
        Column('passwd', String(40)),
 
168
    )
 
169
  
 
170
    def upgrade(migrate_engine):
 
171
        meta.bind = migrate_engine
 
172
        account.create()
 
173
 
 
174
    def downgrade(migrate_engine):
 
175
        meta.bind = migrate_engine
 
176
        account.drop()
 
177
 
 
178
As you might have guessed, :func:`upgrade` upgrades the database to the next
 
179
version. This function should contain the :ref:`schema changes<changeset-system>` we want to perform
 
180
(in our example we're creating a table).
 
181
 
 
182
:func:`downgrade` should reverse changes made
 
183
by :func:`upgrade`. You'll need to write both functions for every change
 
184
script. (Well, you don't *have* to write downgrade, but you won't be
163
185
able to revert to an older version of the database or test your
164
186
scripts without it.)
165
187
 
166
 
``from migrate import *`` imports a special SQLAlchemy engine named
167
 
'migrate_engine'. You should use this in your change scripts, rather
168
 
than creating your own engine.
169
 
 
170
 
You should be very careful about importing files from the rest of your
171
 
application, as your change scripts might break when your application
172
 
changes. More about `writing scripts with consistent behavior`_.
 
188
 
 
189
.. note::
 
190
 
 
191
    As you can see, **migrate_engine** is passed to both functions.
 
192
    You should use this in your change scripts, rather
 
193
    than creating your own engine.
 
194
 
 
195
.. warning::
 
196
 
 
197
    You should be very careful about importing files from the rest of your
 
198
    application, as your change scripts might break when your application
 
199
    changes. More about `writing scripts with consistent behavior`_.
 
200
 
173
201
 
174
202
Test the change script
175
203
------------------------
176
204
 
177
205
Change scripts should be tested before they are committed. Testing a
178
 
script will run its upgrade() and downgrade() functions on a specified
 
206
script will run its :func:`upgrade` and :func:`downgrade` functions on a specified
179
207
database; you can ensure the script runs without error. You should be
180
208
testing on a test database - if something goes wrong here, you'll need
181
209
to correct it by hand. If the test is successful, the database should
182
 
appear unchanged after upgrade() and downgrade() run.
183
 
 
184
 
To test the script:
185
 
 
186
 
.. code-block:: none
187
 
 
188
 
 % python manage.py test
 
210
appear unchanged after :func:`upgrade` and :func:`downgrade` run.
 
211
 
 
212
To test the script::
 
213
 
 
214
 $ python manage.py test
189
215
 Upgrading... done
190
216
 Downgrading... done
191
217
 Success
193
219
Our script runs on our database (``sqlite:///project.db``, as
194
220
specified in manage.py) without any errors.
195
221
 
196
 
Our repository's version now is::
 
222
Our repository's version is::
197
223
 
198
 
 % python manage.py version
 
224
 $ python manage.py version
199
225
 1
200
226
 
 
227
.. warning::
 
228
 
 
229
         test command executes actual script, be sure you are NOT doing this on production database.
 
230
 
 
231
 
201
232
Upgrade the database
202
233
--------------------
203
234
 
204
235
Now, we can apply this change script to our database::
205
236
 
206
 
 % python manage.py upgrade
 
237
 $ python manage.py upgrade
207
238
 0 -> 1... done
208
239
 
209
240
This upgrades the database (``sqlite:///project.db``, as specified
210
241
when we created manage.py above) to the latest available version. (We
211
 
could also specify a version number if we wished, using the --version
 
242
could also specify a version number if we wished, using the ``--version``
212
243
option.) We can see the database's version number has changed, and our
213
 
table has been created:
214
 
 
215
 
.. code-block:: none
216
 
 
217
 
 % python manage.py db_version
 
244
table has been created::
 
245
 
 
246
 $ python manage.py db_version
218
247
 1
219
 
 % sqlite3 project.db
 
248
 $ sqlite3 project.db
220
249
 sqlite> .tables
221
250
 account migrate_version
222
251
 
243
272
want your change scripts' behavior changing when your source code
244
273
does.
245
274
 
246
 
Consider the following example of what can go wrong (i.e. what NOT to
247
 
do):
248
 
 
249
 
Your application defines a table in the model.py file:
250
 
 
251
 
::
252
 
 
253
 
 from sqlalchemy import *
254
 
 
255
 
 meta = MetaData()
256
 
 table = Table('mytable',meta,
257
 
     Column('id',Integer,primary_key=True),
258
 
 )
259
 
 
260
 
...and uses this file to create a table in a change script:
261
 
 
262
 
::
263
 
 
264
 
 from sqlalchemy import *
265
 
 from migrate import *
266
 
 import model
267
 
 model.meta.connect(migrate_engine)
268
 
 
269
 
 def upgrade():
 
275
.. warning:: 
 
276
 
 
277
    **Consider the following example of what NOT to do**
 
278
 
 
279
Let's say your application defines a table in the :file:`model.py` file:
 
280
 
 
281
.. code-block:: python
 
282
 
 
283
 from sqlalchemy import *
 
284
 
 
285
 meta = MetaData()
 
286
 table = Table('mytable', meta,
 
287
     Column('id', Integer, primary_key=True),
 
288
 )
 
289
 
 
290
... and uses this file to create a table in a change script:
 
291
 
 
292
.. code-block:: python
 
293
 
 
294
 from sqlalchemy import *
 
295
 from migrate import *
 
296
 import model
 
297
 
 
298
 def upgrade(migrate_engine):
 
299
     model.meta.bind = migrate_engine
 
300
 
 
301
 def downgrade(migrate_engine):
 
302
     model.meta.bind = migrate_engine 
 
303
     model.table.drop()
 
304
 
 
305
This runs successfully the first time. But what happens if we change
 
306
the table definition in :file:`model.py`?
 
307
 
 
308
.. code-block:: python
 
309
 
 
310
 from sqlalchemy import *
 
311
 
 
312
 meta = MetaData()
 
313
 table = Table('mytable', meta,
 
314
     Column('id', Integer, primary_key=True),
 
315
     Column('data', String(42)),
 
316
 )
 
317
 
 
318
We'll create a new column with a matching change script
 
319
 
 
320
.. code-block:: python
 
321
 
 
322
 from sqlalchemy import *
 
323
 from migrate import *
 
324
 import model
 
325
 
 
326
 def upgrade(migrate_engine):
 
327
     model.meta.bind = migrate_engine
270
328
     model.table.create()
271
 
 def downgrade():
 
329
 
 
330
 def downgrade(migrate_engine):
 
331
     model.meta.bind = migrate_engine
272
332
     model.table.drop()
273
333
 
274
 
This runs successfully the first time. But what happens if we change
275
 
the table definition?
276
 
 
277
 
::
278
 
 
279
 
 table = Table('mytable',meta,
280
 
     Column('id',Integer,primary_key=True),
281
 
     Column('data',String(42)),
282
 
 )
283
 
 
284
 
We'll create a new column with a matching change script::
285
 
 
286
 
 from sqlalchemy import *
287
 
 from migrate import *
288
 
 import model
289
 
 model.meta.connect(migrate_engine)
290
 
 
291
 
 def upgrade():
292
 
     model.table.data.create()
293
 
 def downgrade():
294
 
     model.table.data.drop()
295
334
 
296
335
This appears to run fine when upgrading an existing database - but the
297
336
first script's behavior changed! Running all our change scripts on a
303
342
definition into each change script rather than importing parts of your
304
343
application.
305
344
 
 
345
.. note:: Sometimes it is enough to just reflect tables with SQLAlchemy instead of copy-pasting - but remember, explicit is better than implicit!
 
346
 
 
347
 
306
348
Writing for a specific database
307
349
-------------------------------
308
350
 
309
351
Sometimes you need to write code for a specific database. Migrate
310
352
scripts can run under any database, however - the engine you're given
311
353
might belong to any database. Use engine.name to get the name of the
312
 
database you're working with::
 
354
database you're working with
 
355
 
 
356
.. code-block:: python
313
357
 
314
358
 >>> from sqlalchemy import *
315
359
 >>> from migrate import *
324
368
You might prefer to write your change scripts in SQL, as .sql files,
325
369
rather than as Python scripts. SQLAlchemy-migrate can work with that::
326
370
 
327
 
 % python manage.py version
 
371
 $ python manage.py version
328
372
 1
329
 
 % python manage.py script_sql postgres
 
373
 $ python manage.py script_sql postgres
330
374
 
331
375
This creates two scripts
332
376
:file:`my_repository/versions/002_postgresql_upgrade.sql` and
338
382
postgres, oracle, mysql...
339
383
 
340
384
 
 
385
.. _command-line-usage:
 
386
 
341
387
Command line usage
342
388
==================
343
389
 
344
390
.. currentmodule:: migrate.versioning.shell
345
391
 
346
 
:command:`migrate` command is used for API interface. For list of commands and help use
347
 
 
348
 
::
349
 
 
350
 
        migrate --help
351
 
 
352
 
:program:`migrate` command uses :func:`migrate.versioning.shell.main` function.
 
392
:command:`migrate` command is used for API interface. For list of commands and help use::
 
393
 
 
394
        $ migrate --help
 
395
 
 
396
:program:`migrate` command exectues :func:`main` function.
353
397
For ease of usage, generate your own :ref:`project management script <project_management_script>`,
354
 
which calls :func:`shell.main` function with keywords arguments.
355
 
You may want to specify `url` and `repository` arguments which almost all API functions require as positional arguments.
 
398
which calls :func:`main` function with keywords arguments.
 
399
You may want to specify `url` and `repository` arguments which almost all API functions require.
356
400
 
357
401
If api command looks like::
358
402
 
359
 
        migrate downgrade URL REPOSITORY VERSION [--preview_sql|--preview_py]
360
 
 
361
 
and you have a project management script that looks like::
 
403
        $ migrate downgrade URL REPOSITORY VERSION [--preview_sql|--preview_py]
 
404
 
 
405
and you have a project management script that looks like
 
406
 
 
407
.. code-block:: python
362
408
 
363
409
        from migrate.versioning.shell import main
364
410
 
366
412
 
367
413
you have first two slots filed, and command line usage would look like::
368
414
 
369
 
        # downgrade to version 2 and preview Python file
370
 
        migrate downgrade 2 --preview_py
 
415
        # preview Python script
 
416
        $ migrate downgrade 2 --preview_py
371
417
 
372
418
        # downgrade to version 2
373
 
        migrate downgrade 2
 
419
        $ migrate downgrade 2
374
420
 
375
421
.. versionchanged:: 0.5.4
376
422
        Command line parsing refactored: positional parameters usage
377
423
 
378
424
Whole command line parsing was rewriten from scratch with use of OptionParser.
379
 
Options passed as kwargs to :func:`migrate.versioning.shell.main` are now parsed correctly.
 
425
Options passed as kwargs to :func:`~migrate.versioning.shell.main` are now parsed correctly.
380
426
Options are passed to commands in the following priority (starting from highest):
381
427
 
382
428
- optional (given by ``--some_option`` in commandline)
387
433
Python API
388
434
==========
389
435
 
390
 
.. currentmodule:: migrate.versioning
 
436
.. currentmodule:: migrate.versioning.api
391
437
 
392
438
All commands available from the command line are also available for
393
 
your Python scripts by importing :mod:`migrate.versioning`. See the
394
 
:mod:`migrate.versioning` documentation for a list of functions;
 
439
your Python scripts by importing :mod:`migrate.versioning.api`. See the
 
440
:mod:`migrate.versioning.api` documentation for a list of functions;
395
441
function names match equivalent shell commands. You can use this to
396
442
help integrate SQLAlchemy Migrate with your existing update process.
397
443
 
399
445
 
400
446
*From the command line*::
401
447
 
402
 
 % migrate help help
 
448
 $ migrate help help
403
449
 /usr/bin/migrate help COMMAND
404
450
 
405
451
     Displays help on a given command.
406
452
 
407
 
*From Python*::
 
453
*From Python*
 
454
 
 
455
.. code-block:: python
408
456
 
409
457
 import migrate.versioning.api
410
458
 migrate.versioning.api.help('help')
418
466
 
419
467
.. _repository_configuration:
420
468
 
 
469
 
421
470
Experimental commands
422
471
=====================
423
472
 
446
495
the necessary arguments to the commands from the output of ``migrate
447
496
help <command>``.
448
497
 
 
498
 
449
499
Repository configuration
450
500
========================
451
501
 
469
519
  successfully during a commit, or the entire commit will fail. List
470
520
  the databases your application will actually be using to ensure your
471
521
  updates to that database work properly. This must be a list;
472
 
  example: `['postgres','sqlite']`
 
522
  example: `['postgres', 'sqlite']`
 
523
 
 
524
 
 
525
.. _custom-templates:
 
526
 
 
527
Customize templates
 
528
===================
 
529
 
 
530
Users can pass ``templates_path`` to API functions to provide customized templates path.
 
531
Path should be a collection of templates, like ``migrate.versioning.templates`` package directory.
 
532
 
 
533
One may also want to specify custom themes. API functions accept ``templates_theme`` for this purpose (which defaults to `default`)
 
534
 
 
535
Example::
 
536
        
 
537
        /home/user/templates/manage $ ls
 
538
        default.py_tmpl
 
539
        pylons.py_tmpl
 
540
 
 
541
        /home/user/templates/manage $ migrate manage manage.py --templates_path=/home/user/templates --templates_theme=pylons
 
542
 
 
543
 
 
544
.. versionadded:: 0.6.0