1 from django.db import connection
2 from exceptions import *
6 cursor = connection.cursor()
10 def _execute_in_transaction(*sql):
11 cursor = connection.cursor()
12 cursor.execute("BEGIN")
14 cursor.execute("COMMIT")
16 def table_present(table_name):
17 cursor = _execute("SHOW TABLES LIKE %s", [table_name])
18 return bool(cursor.fetchone())
21 return [(m, 'up') for m in migrations]
23 def _down(migrations):
24 return [(m, 'down') for m in migrations]
26 class MigrationState(object):
28 def __init__(self, dev=None, migration_db=None):
29 self.migration_db = migration_db
32 def migration_table_present(self):
33 return table_present('dmigrations')
35 def log(self, action, migration_name, status='success'):
36 from migration_log import log_action
37 log_action(action, migration_name, status)
39 def applied_but_not_in_db(self):
40 migrations_in_db = set(self.migration_db.list())
41 applied_migrations = [
42 m[0] for m in _execute(
43 "SELECT migration FROM dmigrations"
46 return self.migration_db.sort_migrations(
47 [m for m in applied_migrations if m not in migrations_in_db]
50 def apply(self, name):
52 migration = self.migration_db.load_migration_object(name)
54 self.mark_as_applied(name, log=False)
55 self.log('apply', name)
57 self.log('apply', name, str(e))
60 def unapply(self, name):
62 migration = self.migration_db.load_migration_object(name)
64 self.mark_as_unapplied(name, log=False)
65 self.log('unapply', name)
67 self.log('unapply', name, str(e))
70 def mark_as_applied(self, name, log=True):
71 if not self.is_applied(name):
72 _execute_in_transaction(
73 "INSERT INTO dmigrations (migration) VALUES (%s)", [name]
76 self.log('mark_as_applied', name)
78 def mark_as_unapplied(self, name, log=True):
79 if self.is_applied(name):
80 _execute_in_transaction(
81 "DELETE FROM dmigrations WHERE migration = %s", [name]
84 self.log('mark_as_unapplied', name)
86 def is_applied(self, name):
88 "SELECT * FROM dmigrations WHERE migration = %s", [name]
90 return bool(cursor.fetchone())
92 def all_migrations_applied(self):
93 cursor = _execute("SELECT migration FROM dmigrations")
94 return self.migration_db.sort_migrations(
95 [row[0] for row in cursor.fetchall()]
98 def create_migration_table(self):
100 CREATE TABLE `dmigrations` (
101 `id` int(11) NOT NULL auto_increment,
102 `migration` VARCHAR(255) NOT NULL,
104 ) ENGINE=InnoDB DEFAULT CHARSET=utf8
106 _execute_in_transaction(create_new)
109 "Create the dmigration table, if necessary"
110 if not self.migration_table_present():
111 self.create_migration_table()
112 from migration_log import init as log_init
115 def resolve_name(self, name):
117 Resolve user-friendly migration name to real full migration name.
118 So both "5" and "005_foo" become "005_foo.py" etc.
120 Raises exception if ambiguous, returns None if not found.
123 if re.search('^\d+$', name):
124 name = self.migration_db.find_unique_migration_by_number(
129 def force_resolve_name(self, name):
131 Resolve name or raise an exception if not possible.
133 Raises exception if ambiguous or not found.
135 resolved_name = self.resolve_name(name)
136 if resolved_name not in self.migration_db.list():
137 raise NoSuchMigrationError(name)
140 def list_considering_dev(self):
142 Return list of migrations considering value of dev flag.
145 m for m in self.migration_db.list()
146 if self.dev or not self.migration_db.is_dev_migration(m)
149 def plan_to(self, point):
151 Point can be a migration name or a number.
152 Number can resolve to an unique migration name,
153 or to a point between migrations,
154 but it cannot resolve to an ambiguous migration.
156 Return plan to migrate to such point.
158 migrations = self.list_considering_dev()
160 if point in migrations:
161 i = migrations.index(point) + 1
162 elif re.search(r'^\d+$', point):
164 # NOTE: It only checks that the point is not a duplicate
165 self.migration_db.find_unique_migration_by_number(point)
167 while i < len(migrations) and \
168 self.migration_db.migration_number(migrations[i]) <= point:
171 raise NoSuchMigrationError(point)
174 self.applied_only(reversed(migrations[i:]))
176 self.unapplied_only(migrations[:i])
179 def applied_only(self, migrations):
180 return [m for m in migrations if self.is_applied(m)]
182 def unapplied_only(self, migrations):
183 return [m for m in migrations if not self.is_applied(m)]
185 def plan(self, action, *args):
186 if action in ['all', 'up', 'down'] and len(args) > 0:
187 raise Exception(u"Too many arguments")
189 if action in ['upto', 'downto', 'to'] and len(args) != 1:
190 raise Exception(u"Action %s requires exactly 1 argument" % action)
192 if action == 'apply':
193 return _up(self.unapplied_only(
194 [self.force_resolve_name(arg) for arg in args]
197 if action == 'unapply':
198 return _down(self.applied_only(
199 [self.force_resolve_name(arg) for arg in args]
203 return _up(self.unapplied_only(self.list_considering_dev()))
206 return _up(self.unapplied_only(self.list_considering_dev()))[:1]
209 return _down(reversed(
210 self.applied_only(self.list_considering_dev())
214 return self.plan_to(*args)
216 if action == 'downto':
217 return [(m,a) for (m,a) in self.plan_to(*args) if a == 'down']
220 return [(m,a) for (m,a) in self.plan_to(*args) if a == 'up']
223 u"Unknown action %s" % " ".join([action] + list(args))