~raj-abhilash1/mailman/sqlalchemy

« back to all changes in this revision

Viewing changes to src/mailman/database/docs/migration.rst-skip

  • Committer: Barry Warsaw
  • Date: 2014-09-22 23:32:58 UTC
  • mto: This revision was merged to the branch mainline in revision 7267.
  • Revision ID: barry@list.org-20140922233258-fvg9e3j3equ2y915
Migrations will be replaced with Alchemy.

We don't need the raw SQL schema stuff any more.

We don't need the Version table any more.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
=================
2
 
Schema migrations
3
 
=================
4
 
 
5
 
The SQL database schema will over time require upgrading to support new
6
 
features.  This is supported via schema migration.
7
 
 
8
 
Migrations are embodied in individual Python classes, which themselves may
9
 
load SQL into the database.  The naming scheme for migration files is:
10
 
 
11
 
    mm_YYYYMMDDHHMMSS_comment.py
12
 
 
13
 
where `YYYYMMDDHHMMSS` is a required numeric year, month, day, hour, minute,
14
 
and second specifier providing unique ordering for processing.  Only this
15
 
component of the file name is used to determine the ordering.  The prefix is
16
 
required due to Python module naming requirements, but it is actually
17
 
ignored.  `mm_` is reserved for Mailman's own use.
18
 
 
19
 
The optional `comment` part of the file name can be used as a short
20
 
description for the migration, although comments and docstrings in the
21
 
migration files should be used for more detailed descriptions.
22
 
 
23
 
Migrations are applied automatically when Mailman starts up, but can also be
24
 
applied at any time by calling in the API directly.  Once applied, a
25
 
migration's version string is registered so it will not be applied again.
26
 
 
27
 
We see that the base migration, as well as subsequent standard migrations, are
28
 
already applied.
29
 
 
30
 
    >>> from mailman.model.version import Version
31
 
    >>> results = config.db.store.find(Version, component='schema')
32
 
    >>> results.count()
33
 
    4
34
 
    >>> versions = sorted(result.version for result in results)
35
 
    >>> for version in versions:
36
 
    ...     print(version)
37
 
    00000000000000
38
 
    20120407000000
39
 
    20121015000000
40
 
    20130406000000
41
 
 
42
 
 
43
 
Migrations
44
 
==========
45
 
 
46
 
Migrations can be loaded at any time, and can be found in the migrations path
47
 
specified in the configuration file.
48
 
 
49
 
.. Create a temporary directory for the migrations::
50
 
 
51
 
    >>> import os, sys, tempfile
52
 
    >>> tempdir = tempfile.mkdtemp()
53
 
    >>> path = os.path.join(tempdir, 'migrations')
54
 
    >>> os.makedirs(path)
55
 
    >>> sys.path.append(tempdir)
56
 
    >>> config.push('migrations', """
57
 
    ... [database]
58
 
    ... migrations_path: migrations
59
 
    ... """)
60
 
 
61
 
.. Clean this up at the end of the doctest.
62
 
    >>> def cleanup():
63
 
    ...     import shutil
64
 
    ...     from mailman.config import config
65
 
    ...     config.pop('migrations')
66
 
    ...     shutil.rmtree(tempdir)
67
 
    >>> cleanups.append(cleanup)
68
 
 
69
 
Here is an example migrations module.  The key part of this interface is the
70
 
``upgrade()`` method, which takes four arguments:
71
 
 
72
 
 * `database` - The database class, as derived from `StormBaseDatabase`
73
 
 * `store` - The Storm `Store` object.
74
 
 * `version` - The version string as derived from the migrations module's file
75
 
   name.  This will include only the `YYYYMMDDHHMMSS` string.
76
 
 * `module_path` - The dotted module path to the migrations module, suitable
77
 
   for lookup in `sys.modules`.
78
 
 
79
 
This migration module just adds a marker to the `version` table.
80
 
 
81
 
    >>> with open(os.path.join(path, '__init__.py'), 'w') as fp:
82
 
    ...     pass
83
 
    >>> with open(os.path.join(path, 'mm_20159999000000.py'), 'w') as fp:
84
 
    ...     print("""
85
 
    ... from __future__ import unicode_literals
86
 
    ... from mailman.model.version import Version
87
 
    ... def upgrade(database, store, version, module_path):
88
 
    ...     v = Version(component='test', version=version)
89
 
    ...     store.add(v)
90
 
    ...     database.load_schema(store, version, None, module_path)
91
 
    ... """, file=fp)
92
 
 
93
 
This will load the new migration, since it hasn't been loaded before.
94
 
 
95
 
    >>> config.db.load_migrations()
96
 
    >>> results = config.db.store.find(Version, component='schema')
97
 
    >>> for result in sorted(result.version for result in results):
98
 
    ...     print(result)
99
 
    00000000000000
100
 
    20120407000000
101
 
    20121015000000
102
 
    20130406000000
103
 
    20159999000000
104
 
    >>> test = config.db.store.find(Version, component='test').one()
105
 
    >>> print(test.version)
106
 
    20159999000000
107
 
 
108
 
Migrations will only be loaded once.
109
 
 
110
 
    >>> with open(os.path.join(path, 'mm_20159999000001.py'), 'w') as fp:
111
 
    ...     print("""
112
 
    ... from __future__ import unicode_literals
113
 
    ... from mailman.model.version import Version
114
 
    ... _marker = 801
115
 
    ... def upgrade(database, store, version, module_path):
116
 
    ...     global _marker
117
 
    ...     # Pad enough zeros on the left to reach 14 characters wide.
118
 
    ...     marker = '{0:=#014d}'.format(_marker)
119
 
    ...     _marker += 1
120
 
    ...     v = Version(component='test', version=marker)
121
 
    ...     store.add(v)
122
 
    ...     database.load_schema(store, version, None, module_path)
123
 
    ... """, file=fp)
124
 
 
125
 
The first time we load this new migration, we'll get the 801 marker.
126
 
 
127
 
    >>> config.db.load_migrations()
128
 
    >>> results = config.db.store.find(Version, component='schema')
129
 
    >>> for result in sorted(result.version for result in results):
130
 
    ...     print(result)
131
 
    00000000000000
132
 
    20120407000000
133
 
    20121015000000
134
 
    20130406000000
135
 
    20159999000000
136
 
    20159999000001
137
 
    >>> test = config.db.store.find(Version, component='test')
138
 
    >>> for marker in sorted(marker.version for marker in test):
139
 
    ...     print(marker)
140
 
    00000000000801
141
 
    20159999000000
142
 
 
143
 
We do not get an 802 marker because the migration has already been loaded.
144
 
 
145
 
    >>> config.db.load_migrations()
146
 
    >>> results = config.db.store.find(Version, component='schema')
147
 
    >>> for result in sorted(result.version for result in results):
148
 
    ...     print(result)
149
 
    00000000000000
150
 
    20120407000000
151
 
    20121015000000
152
 
    20130406000000
153
 
    20159999000000
154
 
    20159999000001
155
 
    >>> test = config.db.store.find(Version, component='test')
156
 
    >>> for marker in sorted(marker.version for marker in test):
157
 
    ...     print(marker)
158
 
    00000000000801
159
 
    20159999000000
160
 
 
161
 
 
162
 
Partial upgrades
163
 
================
164
 
 
165
 
It's possible (mostly for testing purposes) to only do a partial upgrade, by
166
 
providing a timestamp to `load_migrations()`.  To demonstrate this, we add two
167
 
additional migrations, intended to be applied in sequential order.
168
 
 
169
 
    >>> from shutil import copyfile
170
 
    >>> from mailman.testing.helpers import chdir
171
 
    >>> with chdir(path):
172
 
    ...     copyfile('mm_20159999000000.py', 'mm_20159999000002.py')
173
 
    ...     copyfile('mm_20159999000000.py', 'mm_20159999000003.py')
174
 
    ...     copyfile('mm_20159999000000.py', 'mm_20159999000004.py')
175
 
 
176
 
Now, only migrate to the ...03 timestamp.
177
 
 
178
 
    >>> config.db.load_migrations('20159999000003')
179
 
 
180
 
You'll notice that the ...04 version is not present.
181
 
 
182
 
    >>> results = config.db.store.find(Version, component='schema')
183
 
    >>> for result in sorted(result.version for result in results):
184
 
    ...     print(result)
185
 
    00000000000000
186
 
    20120407000000
187
 
    20121015000000
188
 
    20130406000000
189
 
    20159999000000
190
 
    20159999000001
191
 
    20159999000002
192
 
    20159999000003
193
 
 
194
 
 
195
 
.. cleanup:
196
 
    Because the Version table holds schema migration data, it will not be
197
 
    cleaned up by the standard test suite.  This is generally not a problem
198
 
    for SQLite since each test gets a new database file, but for PostgreSQL,
199
 
    this will cause migration.rst to fail on subsequent runs.  So let's just
200
 
    clean up the database explicitly.
201
 
 
202
 
    >>> if config.db.TAG != 'sqlite':
203
 
    ...     results = config.db.store.execute("""
204
 
    ...         DELETE FROM version WHERE version.version >= '201299990000'
205
 
    ...                                OR version.component = 'test';
206
 
    ...         """)
207
 
    ...     config.db.commit()