Show More
@@ -0,0 +1,24 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | """ | |||
|
3 | rhodecode.model.db | |||
|
4 | ~~~~~~~~~~~~~~~~~~ | |||
|
5 | ||||
|
6 | Database Models for RhodeCode | |||
|
7 | ||||
|
8 | :created_on: Apr 08, 2010 | |||
|
9 | :author: marcink | |||
|
10 | :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com> | |||
|
11 | :license: GPLv3, see COPYING for more details. | |||
|
12 | """ | |||
|
13 | # This program is free software: you can redistribute it and/or modify | |||
|
14 | # it under the terms of the GNU General Public License as published by | |||
|
15 | # the Free Software Foundation, either version 3 of the License, or | |||
|
16 | # (at your option) any later version. | |||
|
17 | # | |||
|
18 | # This program is distributed in the hope that it will be useful, | |||
|
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
21 | # GNU General Public License for more details. | |||
|
22 | # | |||
|
23 | # You should have received a copy of the GNU General Public License | |||
|
24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. No newline at end of file |
@@ -0,0 +1,28 b'' | |||||
|
1 | import logging | |||
|
2 | import datetime | |||
|
3 | ||||
|
4 | from sqlalchemy import * | |||
|
5 | from sqlalchemy.exc import DatabaseError | |||
|
6 | from sqlalchemy.orm import relation, backref, class_mapper | |||
|
7 | from sqlalchemy.orm.session import Session | |||
|
8 | ||||
|
9 | from rhodecode.lib.dbmigrate.migrate import * | |||
|
10 | from rhodecode.lib.dbmigrate.migrate.changeset import * | |||
|
11 | ||||
|
12 | from rhodecode.model.meta import Base | |||
|
13 | ||||
|
14 | log = logging.getLogger(__name__) | |||
|
15 | ||||
|
16 | def upgrade(migrate_engine): | |||
|
17 | """ Upgrade operations go here. | |||
|
18 | Don't create your own engine; bind migrate_engine to your metadata | |||
|
19 | """ | |||
|
20 | ||||
|
21 | ||||
|
22 | ||||
|
23 | return | |||
|
24 | ||||
|
25 | ||||
|
26 | def downgrade(migrate_engine): | |||
|
27 | meta = MetaData() | |||
|
28 | meta.bind = migrate_engine |
@@ -1,11 +1,11 b'' | |||||
1 | """ |
|
1 | """ | |
2 | SQLAlchemy migrate provides two APIs :mod:`migrate.versioning` for |
|
2 | SQLAlchemy migrate provides two APIs :mod:`migrate.versioning` for | |
3 | database schema version and repository management and |
|
3 | database schema version and repository management and | |
4 | :mod:`migrate.changeset` that allows to define database schema changes |
|
4 | :mod:`migrate.changeset` that allows to define database schema changes | |
5 | using Python. |
|
5 | using Python. | |
6 | """ |
|
6 | """ | |
7 |
|
7 | |||
8 | from rhodecode.lib.dbmigrate.migrate.versioning import * |
|
8 | from rhodecode.lib.dbmigrate.migrate.versioning import * | |
9 | from rhodecode.lib.dbmigrate.migrate.changeset import * |
|
9 | from rhodecode.lib.dbmigrate.migrate.changeset import * | |
10 |
|
10 | |||
11 |
__version__ = '0.7. |
|
11 | __version__ = '0.7.3.dev' No newline at end of file |
@@ -1,155 +1,155 b'' | |||||
1 | """ |
|
1 | """ | |
2 | `SQLite`_ database specific implementations of changeset classes. |
|
2 | `SQLite`_ database specific implementations of changeset classes. | |
3 |
|
3 | |||
4 | .. _`SQLite`: http://www.sqlite.org/ |
|
4 | .. _`SQLite`: http://www.sqlite.org/ | |
5 | """ |
|
5 | """ | |
6 | from UserDict import DictMixin |
|
6 | from UserDict import DictMixin | |
7 | from copy import copy |
|
7 | from copy import copy | |
8 |
|
8 | |||
9 | from sqlalchemy.databases import sqlite as sa_base |
|
9 | from sqlalchemy.databases import sqlite as sa_base | |
10 |
|
10 | |||
11 | from rhodecode.lib.dbmigrate.migrate import exceptions |
|
11 | from rhodecode.lib.dbmigrate.migrate import exceptions | |
12 | from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06 |
|
12 | from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06 | |
13 |
|
13 | |||
14 |
|
14 | |||
15 | if not SQLA_06: |
|
15 | if not SQLA_06: | |
16 | SQLiteSchemaGenerator = sa_base.SQLiteSchemaGenerator |
|
16 | SQLiteSchemaGenerator = sa_base.SQLiteSchemaGenerator | |
17 | else: |
|
17 | else: | |
18 | SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler |
|
18 | SQLiteSchemaGenerator = sa_base.SQLiteDDLCompiler | |
19 |
|
19 | |||
20 | class SQLiteCommon(object): |
|
20 | class SQLiteCommon(object): | |
21 |
|
21 | |||
22 | def _not_supported(self, op): |
|
22 | def _not_supported(self, op): | |
23 | raise exceptions.NotSupportedError("SQLite does not support " |
|
23 | raise exceptions.NotSupportedError("SQLite does not support " | |
24 | "%s; see http://www.sqlite.org/lang_altertable.html" % op) |
|
24 | "%s; see http://www.sqlite.org/lang_altertable.html" % op) | |
25 |
|
25 | |||
26 |
|
26 | |||
27 | class SQLiteHelper(SQLiteCommon): |
|
27 | class SQLiteHelper(SQLiteCommon): | |
28 |
|
28 | |||
29 | def recreate_table(self,table,column=None,delta=None): |
|
29 | def recreate_table(self,table,column=None,delta=None): | |
30 | table_name = self.preparer.format_table(table) |
|
30 | table_name = self.preparer.format_table(table) | |
31 |
|
31 | |||
32 | # we remove all indexes so as not to have |
|
32 | # we remove all indexes so as not to have | |
33 | # problems during copy and re-create |
|
33 | # problems during copy and re-create | |
34 | for index in table.indexes: |
|
34 | for index in table.indexes: | |
35 | index.drop() |
|
35 | index.drop() | |
36 |
|
36 | |||
37 | self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name) |
|
37 | self.append('ALTER TABLE %s RENAME TO migration_tmp' % table_name) | |
38 | self.execute() |
|
38 | self.execute() | |
39 |
|
39 | |||
40 | insertion_string = self._modify_table(table, column, delta) |
|
40 | insertion_string = self._modify_table(table, column, delta) | |
41 |
|
41 | |||
42 | table.create() |
|
42 | table.create(bind=self.connection) | |
43 | self.append(insertion_string % {'table_name': table_name}) |
|
43 | self.append(insertion_string % {'table_name': table_name}) | |
44 | self.execute() |
|
44 | self.execute() | |
45 | self.append('DROP TABLE migration_tmp') |
|
45 | self.append('DROP TABLE migration_tmp') | |
46 | self.execute() |
|
46 | self.execute() | |
47 |
|
47 | |||
48 | def visit_column(self, delta): |
|
48 | def visit_column(self, delta): | |
49 | if isinstance(delta, DictMixin): |
|
49 | if isinstance(delta, DictMixin): | |
50 | column = delta.result_column |
|
50 | column = delta.result_column | |
51 | table = self._to_table(delta.table) |
|
51 | table = self._to_table(delta.table) | |
52 | else: |
|
52 | else: | |
53 | column = delta |
|
53 | column = delta | |
54 | table = self._to_table(column.table) |
|
54 | table = self._to_table(column.table) | |
55 | self.recreate_table(table,column,delta) |
|
55 | self.recreate_table(table,column,delta) | |
56 |
|
56 | |||
57 | class SQLiteColumnGenerator(SQLiteSchemaGenerator, |
|
57 | class SQLiteColumnGenerator(SQLiteSchemaGenerator, | |
58 | ansisql.ANSIColumnGenerator, |
|
58 | ansisql.ANSIColumnGenerator, | |
59 | # at the end so we get the normal |
|
59 | # at the end so we get the normal | |
60 | # visit_column by default |
|
60 | # visit_column by default | |
61 | SQLiteHelper, |
|
61 | SQLiteHelper, | |
62 | SQLiteCommon |
|
62 | SQLiteCommon | |
63 | ): |
|
63 | ): | |
64 | """SQLite ColumnGenerator""" |
|
64 | """SQLite ColumnGenerator""" | |
65 |
|
65 | |||
66 | def _modify_table(self, table, column, delta): |
|
66 | def _modify_table(self, table, column, delta): | |
67 | columns = ' ,'.join(map( |
|
67 | columns = ' ,'.join(map( | |
68 | self.preparer.format_column, |
|
68 | self.preparer.format_column, | |
69 | [c for c in table.columns if c.name!=column.name])) |
|
69 | [c for c in table.columns if c.name!=column.name])) | |
70 | return ('INSERT INTO %%(table_name)s (%(cols)s) ' |
|
70 | return ('INSERT INTO %%(table_name)s (%(cols)s) ' | |
71 | 'SELECT %(cols)s from migration_tmp')%{'cols':columns} |
|
71 | 'SELECT %(cols)s from migration_tmp')%{'cols':columns} | |
72 |
|
72 | |||
73 | def visit_column(self,column): |
|
73 | def visit_column(self,column): | |
74 | if column.foreign_keys: |
|
74 | if column.foreign_keys: | |
75 | SQLiteHelper.visit_column(self,column) |
|
75 | SQLiteHelper.visit_column(self,column) | |
76 | else: |
|
76 | else: | |
77 | super(SQLiteColumnGenerator,self).visit_column(column) |
|
77 | super(SQLiteColumnGenerator,self).visit_column(column) | |
78 |
|
78 | |||
79 | class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper): |
|
79 | class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper): | |
80 | """SQLite ColumnDropper""" |
|
80 | """SQLite ColumnDropper""" | |
81 |
|
81 | |||
82 | def _modify_table(self, table, column, delta): |
|
82 | def _modify_table(self, table, column, delta): | |
83 |
|
83 | |||
84 | columns = ' ,'.join(map(self.preparer.format_column, table.columns)) |
|
84 | columns = ' ,'.join(map(self.preparer.format_column, table.columns)) | |
85 | return 'INSERT INTO %(table_name)s SELECT ' + columns + \ |
|
85 | return 'INSERT INTO %(table_name)s SELECT ' + columns + \ | |
86 | ' from migration_tmp' |
|
86 | ' from migration_tmp' | |
87 |
|
87 | |||
88 | def visit_column(self,column): |
|
88 | def visit_column(self,column): | |
89 | # For SQLite, we *have* to remove the column here so the table |
|
89 | # For SQLite, we *have* to remove the column here so the table | |
90 | # is re-created properly. |
|
90 | # is re-created properly. | |
91 | column.remove_from_table(column.table,unset_table=False) |
|
91 | column.remove_from_table(column.table,unset_table=False) | |
92 | super(SQLiteColumnDropper,self).visit_column(column) |
|
92 | super(SQLiteColumnDropper,self).visit_column(column) | |
93 |
|
93 | |||
94 |
|
94 | |||
95 | class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger): |
|
95 | class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger): | |
96 | """SQLite SchemaChanger""" |
|
96 | """SQLite SchemaChanger""" | |
97 |
|
97 | |||
98 | def _modify_table(self, table, column, delta): |
|
98 | def _modify_table(self, table, column, delta): | |
99 | return 'INSERT INTO %(table_name)s SELECT * from migration_tmp' |
|
99 | return 'INSERT INTO %(table_name)s SELECT * from migration_tmp' | |
100 |
|
100 | |||
101 | def visit_index(self, index): |
|
101 | def visit_index(self, index): | |
102 | """Does not support ALTER INDEX""" |
|
102 | """Does not support ALTER INDEX""" | |
103 | self._not_supported('ALTER INDEX') |
|
103 | self._not_supported('ALTER INDEX') | |
104 |
|
104 | |||
105 |
|
105 | |||
106 | class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon): |
|
106 | class SQLiteConstraintGenerator(ansisql.ANSIConstraintGenerator, SQLiteHelper, SQLiteCommon): | |
107 |
|
107 | |||
108 | def visit_migrate_primary_key_constraint(self, constraint): |
|
108 | def visit_migrate_primary_key_constraint(self, constraint): | |
109 | tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )" |
|
109 | tmpl = "CREATE UNIQUE INDEX %s ON %s ( %s )" | |
110 | cols = ', '.join(map(self.preparer.format_column, constraint.columns)) |
|
110 | cols = ', '.join(map(self.preparer.format_column, constraint.columns)) | |
111 | tname = self.preparer.format_table(constraint.table) |
|
111 | tname = self.preparer.format_table(constraint.table) | |
112 | name = self.get_constraint_name(constraint) |
|
112 | name = self.get_constraint_name(constraint) | |
113 | msg = tmpl % (name, tname, cols) |
|
113 | msg = tmpl % (name, tname, cols) | |
114 | self.append(msg) |
|
114 | self.append(msg) | |
115 | self.execute() |
|
115 | self.execute() | |
116 |
|
116 | |||
117 | def _modify_table(self, table, column, delta): |
|
117 | def _modify_table(self, table, column, delta): | |
118 | return 'INSERT INTO %(table_name)s SELECT * from migration_tmp' |
|
118 | return 'INSERT INTO %(table_name)s SELECT * from migration_tmp' | |
119 |
|
119 | |||
120 | def visit_migrate_foreign_key_constraint(self, *p, **k): |
|
120 | def visit_migrate_foreign_key_constraint(self, *p, **k): | |
121 | self.recreate_table(p[0].table) |
|
121 | self.recreate_table(p[0].table) | |
122 |
|
122 | |||
123 | def visit_migrate_unique_constraint(self, *p, **k): |
|
123 | def visit_migrate_unique_constraint(self, *p, **k): | |
124 | self.recreate_table(p[0].table) |
|
124 | self.recreate_table(p[0].table) | |
125 |
|
125 | |||
126 |
|
126 | |||
127 | class SQLiteConstraintDropper(ansisql.ANSIColumnDropper, |
|
127 | class SQLiteConstraintDropper(ansisql.ANSIColumnDropper, | |
128 | SQLiteCommon, |
|
128 | SQLiteCommon, | |
129 | ansisql.ANSIConstraintCommon): |
|
129 | ansisql.ANSIConstraintCommon): | |
130 |
|
130 | |||
131 | def visit_migrate_primary_key_constraint(self, constraint): |
|
131 | def visit_migrate_primary_key_constraint(self, constraint): | |
132 | tmpl = "DROP INDEX %s " |
|
132 | tmpl = "DROP INDEX %s " | |
133 | name = self.get_constraint_name(constraint) |
|
133 | name = self.get_constraint_name(constraint) | |
134 | msg = tmpl % (name) |
|
134 | msg = tmpl % (name) | |
135 | self.append(msg) |
|
135 | self.append(msg) | |
136 | self.execute() |
|
136 | self.execute() | |
137 |
|
137 | |||
138 | def visit_migrate_foreign_key_constraint(self, *p, **k): |
|
138 | def visit_migrate_foreign_key_constraint(self, *p, **k): | |
139 | self._not_supported('ALTER TABLE DROP CONSTRAINT') |
|
139 | self._not_supported('ALTER TABLE DROP CONSTRAINT') | |
140 |
|
140 | |||
141 | def visit_migrate_check_constraint(self, *p, **k): |
|
141 | def visit_migrate_check_constraint(self, *p, **k): | |
142 | self._not_supported('ALTER TABLE DROP CONSTRAINT') |
|
142 | self._not_supported('ALTER TABLE DROP CONSTRAINT') | |
143 |
|
143 | |||
144 | def visit_migrate_unique_constraint(self, *p, **k): |
|
144 | def visit_migrate_unique_constraint(self, *p, **k): | |
145 | self._not_supported('ALTER TABLE DROP CONSTRAINT') |
|
145 | self._not_supported('ALTER TABLE DROP CONSTRAINT') | |
146 |
|
146 | |||
147 |
|
147 | |||
148 | # TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index |
|
148 | # TODO: technically primary key is a NOT NULL + UNIQUE constraint, should add NOT NULL to index | |
149 |
|
149 | |||
150 | class SQLiteDialect(ansisql.ANSIDialect): |
|
150 | class SQLiteDialect(ansisql.ANSIDialect): | |
151 | columngenerator = SQLiteColumnGenerator |
|
151 | columngenerator = SQLiteColumnGenerator | |
152 | columndropper = SQLiteColumnDropper |
|
152 | columndropper = SQLiteColumnDropper | |
153 | schemachanger = SQLiteSchemaChanger |
|
153 | schemachanger = SQLiteSchemaChanger | |
154 | constraintgenerator = SQLiteConstraintGenerator |
|
154 | constraintgenerator = SQLiteConstraintGenerator | |
155 | constraintdropper = SQLiteConstraintDropper |
|
155 | constraintdropper = SQLiteConstraintDropper |
@@ -1,88 +1,87 b'' | |||||
1 | """ |
|
1 | """ | |
2 | Provide exception classes for :mod:`migrate` |
|
2 | Provide exception classes for :mod:`migrate` | |
3 | """ |
|
3 | """ | |
4 |
|
4 | |||
5 |
|
5 | |||
6 | class Error(Exception): |
|
6 | class Error(Exception): | |
7 | """Error base class.""" |
|
7 | """Error base class.""" | |
8 |
|
8 | |||
9 |
|
9 | |||
10 | class ApiError(Error): |
|
10 | class ApiError(Error): | |
11 | """Base class for API errors.""" |
|
11 | """Base class for API errors.""" | |
12 |
|
12 | |||
13 |
|
13 | |||
14 | class KnownError(ApiError): |
|
14 | class KnownError(ApiError): | |
15 | """A known error condition.""" |
|
15 | """A known error condition.""" | |
16 |
|
16 | |||
17 |
|
17 | |||
18 | class UsageError(ApiError): |
|
18 | class UsageError(ApiError): | |
19 | """A known error condition where help should be displayed.""" |
|
19 | """A known error condition where help should be displayed.""" | |
20 |
|
20 | |||
21 |
|
21 | |||
22 | class ControlledSchemaError(Error): |
|
22 | class ControlledSchemaError(Error): | |
23 | """Base class for controlled schema errors.""" |
|
23 | """Base class for controlled schema errors.""" | |
24 |
|
24 | |||
25 |
|
25 | |||
26 | class InvalidVersionError(ControlledSchemaError): |
|
26 | class InvalidVersionError(ControlledSchemaError): | |
27 | """Invalid version number.""" |
|
27 | """Invalid version number.""" | |
28 |
|
28 | |||
29 |
|
29 | |||
30 | class DatabaseNotControlledError(ControlledSchemaError): |
|
30 | class DatabaseNotControlledError(ControlledSchemaError): | |
31 | """Database should be under version control, but it's not.""" |
|
31 | """Database should be under version control, but it's not.""" | |
32 |
|
32 | |||
33 |
|
33 | |||
34 | class DatabaseAlreadyControlledError(ControlledSchemaError): |
|
34 | class DatabaseAlreadyControlledError(ControlledSchemaError): | |
35 | """Database shouldn't be under version control, but it is""" |
|
35 | """Database shouldn't be under version control, but it is""" | |
36 |
|
36 | |||
37 |
|
37 | |||
38 | class WrongRepositoryError(ControlledSchemaError): |
|
38 | class WrongRepositoryError(ControlledSchemaError): | |
39 | """This database is under version control by another repository.""" |
|
39 | """This database is under version control by another repository.""" | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | class NoSuchTableError(ControlledSchemaError): |
|
42 | class NoSuchTableError(ControlledSchemaError): | |
43 | """The table does not exist.""" |
|
43 | """The table does not exist.""" | |
44 |
|
44 | |||
45 |
|
45 | |||
46 | class PathError(Error): |
|
46 | class PathError(Error): | |
47 | """Base class for path errors.""" |
|
47 | """Base class for path errors.""" | |
48 |
|
48 | |||
49 |
|
49 | |||
50 | class PathNotFoundError(PathError): |
|
50 | class PathNotFoundError(PathError): | |
51 | """A path with no file was required; found a file.""" |
|
51 | """A path with no file was required; found a file.""" | |
52 |
|
52 | |||
53 |
|
53 | |||
54 | class PathFoundError(PathError): |
|
54 | class PathFoundError(PathError): | |
55 | """A path with a file was required; found no file.""" |
|
55 | """A path with a file was required; found no file.""" | |
56 |
|
56 | |||
57 |
|
57 | |||
58 | class RepositoryError(Error): |
|
58 | class RepositoryError(Error): | |
59 | """Base class for repository errors.""" |
|
59 | """Base class for repository errors.""" | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | class InvalidRepositoryError(RepositoryError): |
|
62 | class InvalidRepositoryError(RepositoryError): | |
63 | """Invalid repository error.""" |
|
63 | """Invalid repository error.""" | |
64 |
|
64 | |||
65 |
|
65 | |||
66 | class ScriptError(Error): |
|
66 | class ScriptError(Error): | |
67 | """Base class for script errors.""" |
|
67 | """Base class for script errors.""" | |
68 |
|
68 | |||
69 |
|
69 | |||
70 | class InvalidScriptError(ScriptError): |
|
70 | class InvalidScriptError(ScriptError): | |
71 | """Invalid script error.""" |
|
71 | """Invalid script error.""" | |
72 |
|
72 | |||
73 |
|
73 | |||
74 | class InvalidVersionError(Error): |
|
74 | class InvalidVersionError(Error): | |
75 | """Invalid version error.""" |
|
75 | """Invalid version error.""" | |
76 |
|
76 | |||
77 | # migrate.changeset |
|
77 | # migrate.changeset | |
78 |
|
78 | |||
79 | class NotSupportedError(Error): |
|
79 | class NotSupportedError(Error): | |
80 | """Not supported error""" |
|
80 | """Not supported error""" | |
81 |
|
81 | |||
82 |
|
82 | |||
83 | class InvalidConstraintError(Error): |
|
83 | class InvalidConstraintError(Error): | |
84 | """Invalid constraint error""" |
|
84 | """Invalid constraint error""" | |
85 |
|
85 | |||
86 |
|
||||
87 | class MigrateDeprecationWarning(DeprecationWarning): |
|
86 | class MigrateDeprecationWarning(DeprecationWarning): | |
88 | """Warning for deprecated features in Migrate""" |
|
87 | """Warning for deprecated features in Migrate""" |
@@ -1,383 +1,384 b'' | |||||
1 | """ |
|
1 | """ | |
2 | This module provides an external API to the versioning system. |
|
2 | This module provides an external API to the versioning system. | |
3 |
|
3 | |||
4 | .. versionchanged:: 0.6.0 |
|
4 | .. versionchanged:: 0.6.0 | |
5 | :func:`migrate.versioning.api.test` and schema diff functions |
|
5 | :func:`migrate.versioning.api.test` and schema diff functions | |
6 | changed order of positional arguments so all accept `url` and `repository` |
|
6 | changed order of positional arguments so all accept `url` and `repository` | |
7 | as first arguments. |
|
7 | as first arguments. | |
8 |
|
8 | |||
9 | .. versionchanged:: 0.5.4 |
|
9 | .. versionchanged:: 0.5.4 | |
10 | ``--preview_sql`` displays source file when using SQL scripts. |
|
10 | ``--preview_sql`` displays source file when using SQL scripts. | |
11 | If Python script is used, it runs the action with mocked engine and |
|
11 | If Python script is used, it runs the action with mocked engine and | |
12 | returns captured SQL statements. |
|
12 | returns captured SQL statements. | |
13 |
|
13 | |||
14 | .. versionchanged:: 0.5.4 |
|
14 | .. versionchanged:: 0.5.4 | |
15 | Deprecated ``--echo`` parameter in favour of new |
|
15 | Deprecated ``--echo`` parameter in favour of new | |
16 | :func:`migrate.versioning.util.construct_engine` behavior. |
|
16 | :func:`migrate.versioning.util.construct_engine` behavior. | |
17 | """ |
|
17 | """ | |
18 |
|
18 | |||
19 | # Dear migrate developers, |
|
19 | # Dear migrate developers, | |
20 | # |
|
20 | # | |
21 | # please do not comment this module using sphinx syntax because its |
|
21 | # please do not comment this module using sphinx syntax because its | |
22 | # docstrings are presented as user help and most users cannot |
|
22 | # docstrings are presented as user help and most users cannot | |
23 | # interpret sphinx annotated ReStructuredText. |
|
23 | # interpret sphinx annotated ReStructuredText. | |
24 | # |
|
24 | # | |
25 | # Thanks, |
|
25 | # Thanks, | |
26 | # Jan Dittberner |
|
26 | # Jan Dittberner | |
27 |
|
27 | |||
28 | import sys |
|
28 | import sys | |
29 | import inspect |
|
29 | import inspect | |
30 | import logging |
|
30 | import logging | |
31 |
|
31 | |||
32 | from rhodecode.lib.dbmigrate.migrate import exceptions |
|
32 | from rhodecode.lib.dbmigrate.migrate import exceptions | |
33 | from rhodecode.lib.dbmigrate.migrate.versioning import repository, schema, version, \ |
|
33 | from rhodecode.lib.dbmigrate.migrate.versioning import repository, schema, version, \ | |
34 | script as script_ # command name conflict |
|
34 | script as script_ # command name conflict | |
35 | from rhodecode.lib.dbmigrate.migrate.versioning.util import catch_known_errors, with_engine |
|
35 | from rhodecode.lib.dbmigrate.migrate.versioning.util import catch_known_errors, with_engine | |
36 |
|
36 | |||
37 |
|
37 | |||
38 | log = logging.getLogger(__name__) |
|
38 | log = logging.getLogger(__name__) | |
39 | command_desc = { |
|
39 | command_desc = { | |
40 | 'help': 'displays help on a given command', |
|
40 | 'help': 'displays help on a given command', | |
41 | 'create': 'create an empty repository at the specified path', |
|
41 | 'create': 'create an empty repository at the specified path', | |
42 | 'script': 'create an empty change Python script', |
|
42 | 'script': 'create an empty change Python script', | |
43 | 'script_sql': 'create empty change SQL scripts for given database', |
|
43 | 'script_sql': 'create empty change SQL scripts for given database', | |
44 | 'version': 'display the latest version available in a repository', |
|
44 | 'version': 'display the latest version available in a repository', | |
45 | 'db_version': 'show the current version of the repository under version control', |
|
45 | 'db_version': 'show the current version of the repository under version control', | |
46 | 'source': 'display the Python code for a particular version in this repository', |
|
46 | 'source': 'display the Python code for a particular version in this repository', | |
47 | 'version_control': 'mark a database as under this repository\'s version control', |
|
47 | 'version_control': 'mark a database as under this repository\'s version control', | |
48 | 'upgrade': 'upgrade a database to a later version', |
|
48 | 'upgrade': 'upgrade a database to a later version', | |
49 | 'downgrade': 'downgrade a database to an earlier version', |
|
49 | 'downgrade': 'downgrade a database to an earlier version', | |
50 | 'drop_version_control': 'removes version control from a database', |
|
50 | 'drop_version_control': 'removes version control from a database', | |
51 | 'manage': 'creates a Python script that runs Migrate with a set of default values', |
|
51 | 'manage': 'creates a Python script that runs Migrate with a set of default values', | |
52 | 'test': 'performs the upgrade and downgrade command on the given database', |
|
52 | 'test': 'performs the upgrade and downgrade command on the given database', | |
53 | 'compare_model_to_db': 'compare MetaData against the current database state', |
|
53 | 'compare_model_to_db': 'compare MetaData against the current database state', | |
54 | 'create_model': 'dump the current database as a Python model to stdout', |
|
54 | 'create_model': 'dump the current database as a Python model to stdout', | |
55 | 'make_update_script_for_model': 'create a script changing the old MetaData to the new (current) MetaData', |
|
55 | 'make_update_script_for_model': 'create a script changing the old MetaData to the new (current) MetaData', | |
56 | 'update_db_from_model': 'modify the database to match the structure of the current MetaData', |
|
56 | 'update_db_from_model': 'modify the database to match the structure of the current MetaData', | |
57 | } |
|
57 | } | |
58 | __all__ = command_desc.keys() |
|
58 | __all__ = command_desc.keys() | |
59 |
|
59 | |||
60 | Repository = repository.Repository |
|
60 | Repository = repository.Repository | |
61 | ControlledSchema = schema.ControlledSchema |
|
61 | ControlledSchema = schema.ControlledSchema | |
62 | VerNum = version.VerNum |
|
62 | VerNum = version.VerNum | |
63 | PythonScript = script_.PythonScript |
|
63 | PythonScript = script_.PythonScript | |
64 | SqlScript = script_.SqlScript |
|
64 | SqlScript = script_.SqlScript | |
65 |
|
65 | |||
66 |
|
66 | |||
67 | # deprecated |
|
67 | # deprecated | |
68 | def help(cmd=None, **opts): |
|
68 | def help(cmd=None, **opts): | |
69 | """%prog help COMMAND |
|
69 | """%prog help COMMAND | |
70 |
|
70 | |||
71 | Displays help on a given command. |
|
71 | Displays help on a given command. | |
72 | """ |
|
72 | """ | |
73 | if cmd is None: |
|
73 | if cmd is None: | |
74 | raise exceptions.UsageError(None) |
|
74 | raise exceptions.UsageError(None) | |
75 | try: |
|
75 | try: | |
76 | func = globals()[cmd] |
|
76 | func = globals()[cmd] | |
77 | except: |
|
77 | except: | |
78 | raise exceptions.UsageError( |
|
78 | raise exceptions.UsageError( | |
79 | "'%s' isn't a valid command. Try 'help COMMAND'" % cmd) |
|
79 | "'%s' isn't a valid command. Try 'help COMMAND'" % cmd) | |
80 | ret = func.__doc__ |
|
80 | ret = func.__doc__ | |
81 | if sys.argv[0]: |
|
81 | if sys.argv[0]: | |
82 | ret = ret.replace('%prog', sys.argv[0]) |
|
82 | ret = ret.replace('%prog', sys.argv[0]) | |
83 | return ret |
|
83 | return ret | |
84 |
|
84 | |||
85 | @catch_known_errors |
|
85 | @catch_known_errors | |
86 | def create(repository, name, **opts): |
|
86 | def create(repository, name, **opts): | |
87 | """%prog create REPOSITORY_PATH NAME [--table=TABLE] |
|
87 | """%prog create REPOSITORY_PATH NAME [--table=TABLE] | |
88 |
|
88 | |||
89 | Create an empty repository at the specified path. |
|
89 | Create an empty repository at the specified path. | |
90 |
|
90 | |||
91 | You can specify the version_table to be used; by default, it is |
|
91 | You can specify the version_table to be used; by default, it is | |
92 | 'migrate_version'. This table is created in all version-controlled |
|
92 | 'migrate_version'. This table is created in all version-controlled | |
93 | databases. |
|
93 | databases. | |
94 | """ |
|
94 | """ | |
95 | repo_path = Repository.create(repository, name, **opts) |
|
95 | repo_path = Repository.create(repository, name, **opts) | |
96 |
|
96 | |||
97 |
|
97 | |||
98 | @catch_known_errors |
|
98 | @catch_known_errors | |
99 | def script(description, repository, **opts): |
|
99 | def script(description, repository, **opts): | |
100 | """%prog script DESCRIPTION REPOSITORY_PATH |
|
100 | """%prog script DESCRIPTION REPOSITORY_PATH | |
101 |
|
101 | |||
102 | Create an empty change script using the next unused version number |
|
102 | Create an empty change script using the next unused version number | |
103 | appended with the given description. |
|
103 | appended with the given description. | |
104 |
|
104 | |||
105 | For instance, manage.py script "Add initial tables" creates: |
|
105 | For instance, manage.py script "Add initial tables" creates: | |
106 | repository/versions/001_Add_initial_tables.py |
|
106 | repository/versions/001_Add_initial_tables.py | |
107 | """ |
|
107 | """ | |
108 | repo = Repository(repository) |
|
108 | repo = Repository(repository) | |
109 | repo.create_script(description, **opts) |
|
109 | repo.create_script(description, **opts) | |
110 |
|
110 | |||
111 |
|
111 | |||
112 | @catch_known_errors |
|
112 | @catch_known_errors | |
113 | def script_sql(database, description, repository, **opts): |
|
113 | def script_sql(database, description, repository, **opts): | |
114 | """%prog script_sql DATABASE DESCRIPTION REPOSITORY_PATH |
|
114 | """%prog script_sql DATABASE DESCRIPTION REPOSITORY_PATH | |
115 |
|
115 | |||
116 | Create empty change SQL scripts for given DATABASE, where DATABASE |
|
116 | Create empty change SQL scripts for given DATABASE, where DATABASE | |
117 | is either specific ('postgresql', 'mysql', 'oracle', 'sqlite', etc.) |
|
117 | is either specific ('postgresql', 'mysql', 'oracle', 'sqlite', etc.) | |
118 | or generic ('default'). |
|
118 | or generic ('default'). | |
119 |
|
119 | |||
120 | For instance, manage.py script_sql postgresql description creates: |
|
120 | For instance, manage.py script_sql postgresql description creates: | |
121 | repository/versions/001_description_postgresql_upgrade.sql and |
|
121 | repository/versions/001_description_postgresql_upgrade.sql and | |
122 |
repository/versions/001_description_postgresql_ |
|
122 | repository/versions/001_description_postgresql_downgrade.sql | |
123 | """ |
|
123 | """ | |
124 | repo = Repository(repository) |
|
124 | repo = Repository(repository) | |
125 | repo.create_script_sql(database, description, **opts) |
|
125 | repo.create_script_sql(database, description, **opts) | |
126 |
|
126 | |||
127 |
|
127 | |||
128 | def version(repository, **opts): |
|
128 | def version(repository, **opts): | |
129 | """%prog version REPOSITORY_PATH |
|
129 | """%prog version REPOSITORY_PATH | |
130 |
|
130 | |||
131 | Display the latest version available in a repository. |
|
131 | Display the latest version available in a repository. | |
132 | """ |
|
132 | """ | |
133 | repo = Repository(repository) |
|
133 | repo = Repository(repository) | |
134 | return repo.latest |
|
134 | return repo.latest | |
135 |
|
135 | |||
136 |
|
136 | |||
137 | @with_engine |
|
137 | @with_engine | |
138 | def db_version(url, repository, **opts): |
|
138 | def db_version(url, repository, **opts): | |
139 | """%prog db_version URL REPOSITORY_PATH |
|
139 | """%prog db_version URL REPOSITORY_PATH | |
140 |
|
140 | |||
141 | Show the current version of the repository with the given |
|
141 | Show the current version of the repository with the given | |
142 | connection string, under version control of the specified |
|
142 | connection string, under version control of the specified | |
143 | repository. |
|
143 | repository. | |
144 |
|
144 | |||
145 | The url should be any valid SQLAlchemy connection string. |
|
145 | The url should be any valid SQLAlchemy connection string. | |
146 | """ |
|
146 | """ | |
147 | engine = opts.pop('engine') |
|
147 | engine = opts.pop('engine') | |
148 | schema = ControlledSchema(engine, repository) |
|
148 | schema = ControlledSchema(engine, repository) | |
149 | return schema.version |
|
149 | return schema.version | |
150 |
|
150 | |||
151 |
|
151 | |||
152 | def source(version, dest=None, repository=None, **opts): |
|
152 | def source(version, dest=None, repository=None, **opts): | |
153 | """%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH |
|
153 | """%prog source VERSION [DESTINATION] --repository=REPOSITORY_PATH | |
154 |
|
154 | |||
155 | Display the Python code for a particular version in this |
|
155 | Display the Python code for a particular version in this | |
156 | repository. Save it to the file at DESTINATION or, if omitted, |
|
156 | repository. Save it to the file at DESTINATION or, if omitted, | |
157 | send to stdout. |
|
157 | send to stdout. | |
158 | """ |
|
158 | """ | |
159 | if repository is None: |
|
159 | if repository is None: | |
160 | raise exceptions.UsageError("A repository must be specified") |
|
160 | raise exceptions.UsageError("A repository must be specified") | |
161 | repo = Repository(repository) |
|
161 | repo = Repository(repository) | |
162 | ret = repo.version(version).script().source() |
|
162 | ret = repo.version(version).script().source() | |
163 | if dest is not None: |
|
163 | if dest is not None: | |
164 | dest = open(dest, 'w') |
|
164 | dest = open(dest, 'w') | |
165 | dest.write(ret) |
|
165 | dest.write(ret) | |
166 | dest.close() |
|
166 | dest.close() | |
167 | ret = None |
|
167 | ret = None | |
168 | return ret |
|
168 | return ret | |
169 |
|
169 | |||
170 |
|
170 | |||
171 | def upgrade(url, repository, version=None, **opts): |
|
171 | def upgrade(url, repository, version=None, **opts): | |
172 | """%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql] |
|
172 | """%prog upgrade URL REPOSITORY_PATH [VERSION] [--preview_py|--preview_sql] | |
173 |
|
173 | |||
174 | Upgrade a database to a later version. |
|
174 | Upgrade a database to a later version. | |
175 |
|
175 | |||
176 | This runs the upgrade() function defined in your change scripts. |
|
176 | This runs the upgrade() function defined in your change scripts. | |
177 |
|
177 | |||
178 | By default, the database is updated to the latest available |
|
178 | By default, the database is updated to the latest available | |
179 | version. You may specify a version instead, if you wish. |
|
179 | version. You may specify a version instead, if you wish. | |
180 |
|
180 | |||
181 | You may preview the Python or SQL code to be executed, rather than |
|
181 | You may preview the Python or SQL code to be executed, rather than | |
182 | actually executing it, using the appropriate 'preview' option. |
|
182 | actually executing it, using the appropriate 'preview' option. | |
183 | """ |
|
183 | """ | |
184 | err = "Cannot upgrade a database of version %s to version %s. "\ |
|
184 | err = "Cannot upgrade a database of version %s to version %s. "\ | |
185 | "Try 'downgrade' instead." |
|
185 | "Try 'downgrade' instead." | |
186 | return _migrate(url, repository, version, upgrade=True, err=err, **opts) |
|
186 | return _migrate(url, repository, version, upgrade=True, err=err, **opts) | |
187 |
|
187 | |||
188 |
|
188 | |||
189 | def downgrade(url, repository, version, **opts): |
|
189 | def downgrade(url, repository, version, **opts): | |
190 | """%prog downgrade URL REPOSITORY_PATH VERSION [--preview_py|--preview_sql] |
|
190 | """%prog downgrade URL REPOSITORY_PATH VERSION [--preview_py|--preview_sql] | |
191 |
|
191 | |||
192 | Downgrade a database to an earlier version. |
|
192 | Downgrade a database to an earlier version. | |
193 |
|
193 | |||
194 | This is the reverse of upgrade; this runs the downgrade() function |
|
194 | This is the reverse of upgrade; this runs the downgrade() function | |
195 | defined in your change scripts. |
|
195 | defined in your change scripts. | |
196 |
|
196 | |||
197 | You may preview the Python or SQL code to be executed, rather than |
|
197 | You may preview the Python or SQL code to be executed, rather than | |
198 | actually executing it, using the appropriate 'preview' option. |
|
198 | actually executing it, using the appropriate 'preview' option. | |
199 | """ |
|
199 | """ | |
200 | err = "Cannot downgrade a database of version %s to version %s. "\ |
|
200 | err = "Cannot downgrade a database of version %s to version %s. "\ | |
201 | "Try 'upgrade' instead." |
|
201 | "Try 'upgrade' instead." | |
202 | return _migrate(url, repository, version, upgrade=False, err=err, **opts) |
|
202 | return _migrate(url, repository, version, upgrade=False, err=err, **opts) | |
203 |
|
203 | |||
204 | @with_engine |
|
204 | @with_engine | |
205 | def test(url, repository, **opts): |
|
205 | def test(url, repository, **opts): | |
206 | """%prog test URL REPOSITORY_PATH [VERSION] |
|
206 | """%prog test URL REPOSITORY_PATH [VERSION] | |
207 |
|
207 | |||
208 | Performs the upgrade and downgrade option on the given |
|
208 | Performs the upgrade and downgrade option on the given | |
209 | database. This is not a real test and may leave the database in a |
|
209 | database. This is not a real test and may leave the database in a | |
210 | bad state. You should therefore better run the test on a copy of |
|
210 | bad state. You should therefore better run the test on a copy of | |
211 | your database. |
|
211 | your database. | |
212 | """ |
|
212 | """ | |
213 | engine = opts.pop('engine') |
|
213 | engine = opts.pop('engine') | |
214 | repos = Repository(repository) |
|
214 | repos = Repository(repository) | |
215 | script = repos.version(None).script() |
|
|||
216 |
|
215 | |||
217 | # Upgrade |
|
216 | # Upgrade | |
218 | log.info("Upgrading...") |
|
217 | log.info("Upgrading...") | |
|
218 | script = repos.version(None).script(engine.name, 'upgrade') | |||
219 | script.run(engine, 1) |
|
219 | script.run(engine, 1) | |
220 | log.info("done") |
|
220 | log.info("done") | |
221 |
|
221 | |||
222 | log.info("Downgrading...") |
|
222 | log.info("Downgrading...") | |
|
223 | script = repos.version(None).script(engine.name, 'downgrade') | |||
223 | script.run(engine, -1) |
|
224 | script.run(engine, -1) | |
224 | log.info("done") |
|
225 | log.info("done") | |
225 | log.info("Success") |
|
226 | log.info("Success") | |
226 |
|
227 | |||
227 |
|
228 | |||
228 | @with_engine |
|
229 | @with_engine | |
229 | def version_control(url, repository, version=None, **opts): |
|
230 | def version_control(url, repository, version=None, **opts): | |
230 | """%prog version_control URL REPOSITORY_PATH [VERSION] |
|
231 | """%prog version_control URL REPOSITORY_PATH [VERSION] | |
231 |
|
232 | |||
232 | Mark a database as under this repository's version control. |
|
233 | Mark a database as under this repository's version control. | |
233 |
|
234 | |||
234 | Once a database is under version control, schema changes should |
|
235 | Once a database is under version control, schema changes should | |
235 | only be done via change scripts in this repository. |
|
236 | only be done via change scripts in this repository. | |
236 |
|
237 | |||
237 | This creates the table version_table in the database. |
|
238 | This creates the table version_table in the database. | |
238 |
|
239 | |||
239 | The url should be any valid SQLAlchemy connection string. |
|
240 | The url should be any valid SQLAlchemy connection string. | |
240 |
|
241 | |||
241 | By default, the database begins at version 0 and is assumed to be |
|
242 | By default, the database begins at version 0 and is assumed to be | |
242 | empty. If the database is not empty, you may specify a version at |
|
243 | empty. If the database is not empty, you may specify a version at | |
243 | which to begin instead. No attempt is made to verify this |
|
244 | which to begin instead. No attempt is made to verify this | |
244 | version's correctness - the database schema is expected to be |
|
245 | version's correctness - the database schema is expected to be | |
245 | identical to what it would be if the database were created from |
|
246 | identical to what it would be if the database were created from | |
246 | scratch. |
|
247 | scratch. | |
247 | """ |
|
248 | """ | |
248 | engine = opts.pop('engine') |
|
249 | engine = opts.pop('engine') | |
249 | ControlledSchema.create(engine, repository, version) |
|
250 | ControlledSchema.create(engine, repository, version) | |
250 |
|
251 | |||
251 |
|
252 | |||
252 | @with_engine |
|
253 | @with_engine | |
253 | def drop_version_control(url, repository, **opts): |
|
254 | def drop_version_control(url, repository, **opts): | |
254 | """%prog drop_version_control URL REPOSITORY_PATH |
|
255 | """%prog drop_version_control URL REPOSITORY_PATH | |
255 |
|
256 | |||
256 | Removes version control from a database. |
|
257 | Removes version control from a database. | |
257 | """ |
|
258 | """ | |
258 | engine = opts.pop('engine') |
|
259 | engine = opts.pop('engine') | |
259 | schema = ControlledSchema(engine, repository) |
|
260 | schema = ControlledSchema(engine, repository) | |
260 | schema.drop() |
|
261 | schema.drop() | |
261 |
|
262 | |||
262 |
|
263 | |||
263 | def manage(file, **opts): |
|
264 | def manage(file, **opts): | |
264 | """%prog manage FILENAME [VARIABLES...] |
|
265 | """%prog manage FILENAME [VARIABLES...] | |
265 |
|
266 | |||
266 | Creates a script that runs Migrate with a set of default values. |
|
267 | Creates a script that runs Migrate with a set of default values. | |
267 |
|
268 | |||
268 | For example:: |
|
269 | For example:: | |
269 |
|
270 | |||
270 | %prog manage manage.py --repository=/path/to/repository \ |
|
271 | %prog manage manage.py --repository=/path/to/repository \ | |
271 | --url=sqlite:///project.db |
|
272 | --url=sqlite:///project.db | |
272 |
|
273 | |||
273 | would create the script manage.py. The following two commands |
|
274 | would create the script manage.py. The following two commands | |
274 | would then have exactly the same results:: |
|
275 | would then have exactly the same results:: | |
275 |
|
276 | |||
276 | python manage.py version |
|
277 | python manage.py version | |
277 | %prog version --repository=/path/to/repository |
|
278 | %prog version --repository=/path/to/repository | |
278 | """ |
|
279 | """ | |
279 | Repository.create_manage_file(file, **opts) |
|
280 | Repository.create_manage_file(file, **opts) | |
280 |
|
281 | |||
281 |
|
282 | |||
282 | @with_engine |
|
283 | @with_engine | |
283 | def compare_model_to_db(url, repository, model, **opts): |
|
284 | def compare_model_to_db(url, repository, model, **opts): | |
284 | """%prog compare_model_to_db URL REPOSITORY_PATH MODEL |
|
285 | """%prog compare_model_to_db URL REPOSITORY_PATH MODEL | |
285 |
|
286 | |||
286 | Compare the current model (assumed to be a module level variable |
|
287 | Compare the current model (assumed to be a module level variable | |
287 | of type sqlalchemy.MetaData) against the current database. |
|
288 | of type sqlalchemy.MetaData) against the current database. | |
288 |
|
289 | |||
289 | NOTE: This is EXPERIMENTAL. |
|
290 | NOTE: This is EXPERIMENTAL. | |
290 | """ # TODO: get rid of EXPERIMENTAL label |
|
291 | """ # TODO: get rid of EXPERIMENTAL label | |
291 | engine = opts.pop('engine') |
|
292 | engine = opts.pop('engine') | |
292 | return ControlledSchema.compare_model_to_db(engine, model, repository) |
|
293 | return ControlledSchema.compare_model_to_db(engine, model, repository) | |
293 |
|
294 | |||
294 |
|
295 | |||
295 | @with_engine |
|
296 | @with_engine | |
296 | def create_model(url, repository, **opts): |
|
297 | def create_model(url, repository, **opts): | |
297 | """%prog create_model URL REPOSITORY_PATH [DECLERATIVE=True] |
|
298 | """%prog create_model URL REPOSITORY_PATH [DECLERATIVE=True] | |
298 |
|
299 | |||
299 | Dump the current database as a Python model to stdout. |
|
300 | Dump the current database as a Python model to stdout. | |
300 |
|
301 | |||
301 | NOTE: This is EXPERIMENTAL. |
|
302 | NOTE: This is EXPERIMENTAL. | |
302 | """ # TODO: get rid of EXPERIMENTAL label |
|
303 | """ # TODO: get rid of EXPERIMENTAL label | |
303 | engine = opts.pop('engine') |
|
304 | engine = opts.pop('engine') | |
304 | declarative = opts.get('declarative', False) |
|
305 | declarative = opts.get('declarative', False) | |
305 | return ControlledSchema.create_model(engine, repository, declarative) |
|
306 | return ControlledSchema.create_model(engine, repository, declarative) | |
306 |
|
307 | |||
307 |
|
308 | |||
308 | @catch_known_errors |
|
309 | @catch_known_errors | |
309 | @with_engine |
|
310 | @with_engine | |
310 | def make_update_script_for_model(url, repository, oldmodel, model, **opts): |
|
311 | def make_update_script_for_model(url, repository, oldmodel, model, **opts): | |
311 | """%prog make_update_script_for_model URL OLDMODEL MODEL REPOSITORY_PATH |
|
312 | """%prog make_update_script_for_model URL OLDMODEL MODEL REPOSITORY_PATH | |
312 |
|
313 | |||
313 | Create a script changing the old Python model to the new (current) |
|
314 | Create a script changing the old Python model to the new (current) | |
314 | Python model, sending to stdout. |
|
315 | Python model, sending to stdout. | |
315 |
|
316 | |||
316 | NOTE: This is EXPERIMENTAL. |
|
317 | NOTE: This is EXPERIMENTAL. | |
317 | """ # TODO: get rid of EXPERIMENTAL label |
|
318 | """ # TODO: get rid of EXPERIMENTAL label | |
318 | engine = opts.pop('engine') |
|
319 | engine = opts.pop('engine') | |
319 | return PythonScript.make_update_script_for_model( |
|
320 | return PythonScript.make_update_script_for_model( | |
320 | engine, oldmodel, model, repository, **opts) |
|
321 | engine, oldmodel, model, repository, **opts) | |
321 |
|
322 | |||
322 |
|
323 | |||
323 | @with_engine |
|
324 | @with_engine | |
324 | def update_db_from_model(url, repository, model, **opts): |
|
325 | def update_db_from_model(url, repository, model, **opts): | |
325 | """%prog update_db_from_model URL REPOSITORY_PATH MODEL |
|
326 | """%prog update_db_from_model URL REPOSITORY_PATH MODEL | |
326 |
|
327 | |||
327 | Modify the database to match the structure of the current Python |
|
328 | Modify the database to match the structure of the current Python | |
328 | model. This also sets the db_version number to the latest in the |
|
329 | model. This also sets the db_version number to the latest in the | |
329 | repository. |
|
330 | repository. | |
330 |
|
331 | |||
331 | NOTE: This is EXPERIMENTAL. |
|
332 | NOTE: This is EXPERIMENTAL. | |
332 | """ # TODO: get rid of EXPERIMENTAL label |
|
333 | """ # TODO: get rid of EXPERIMENTAL label | |
333 | engine = opts.pop('engine') |
|
334 | engine = opts.pop('engine') | |
334 | schema = ControlledSchema(engine, repository) |
|
335 | schema = ControlledSchema(engine, repository) | |
335 | schema.update_db_from_model(model) |
|
336 | schema.update_db_from_model(model) | |
336 |
|
337 | |||
337 | @with_engine |
|
338 | @with_engine | |
338 | def _migrate(url, repository, version, upgrade, err, **opts): |
|
339 | def _migrate(url, repository, version, upgrade, err, **opts): | |
339 | engine = opts.pop('engine') |
|
340 | engine = opts.pop('engine') | |
340 | url = str(engine.url) |
|
341 | url = str(engine.url) | |
341 | schema = ControlledSchema(engine, repository) |
|
342 | schema = ControlledSchema(engine, repository) | |
342 | version = _migrate_version(schema, version, upgrade, err) |
|
343 | version = _migrate_version(schema, version, upgrade, err) | |
343 |
|
344 | |||
344 | changeset = schema.changeset(version) |
|
345 | changeset = schema.changeset(version) | |
345 | for ver, change in changeset: |
|
346 | for ver, change in changeset: | |
346 | nextver = ver + changeset.step |
|
347 | nextver = ver + changeset.step | |
347 | log.info('%s -> %s... ', ver, nextver) |
|
348 | log.info('%s -> %s... ', ver, nextver) | |
348 |
|
349 | |||
349 | if opts.get('preview_sql'): |
|
350 | if opts.get('preview_sql'): | |
350 | if isinstance(change, PythonScript): |
|
351 | if isinstance(change, PythonScript): | |
351 | log.info(change.preview_sql(url, changeset.step, **opts)) |
|
352 | log.info(change.preview_sql(url, changeset.step, **opts)) | |
352 | elif isinstance(change, SqlScript): |
|
353 | elif isinstance(change, SqlScript): | |
353 | log.info(change.source()) |
|
354 | log.info(change.source()) | |
354 |
|
355 | |||
355 | elif opts.get('preview_py'): |
|
356 | elif opts.get('preview_py'): | |
356 | if not isinstance(change, PythonScript): |
|
357 | if not isinstance(change, PythonScript): | |
357 | raise exceptions.UsageError("Python source can be only displayed" |
|
358 | raise exceptions.UsageError("Python source can be only displayed" | |
358 | " for python migration files") |
|
359 | " for python migration files") | |
359 | source_ver = max(ver, nextver) |
|
360 | source_ver = max(ver, nextver) | |
360 | module = schema.repository.version(source_ver).script().module |
|
361 | module = schema.repository.version(source_ver).script().module | |
361 | funcname = upgrade and "upgrade" or "downgrade" |
|
362 | funcname = upgrade and "upgrade" or "downgrade" | |
362 | func = getattr(module, funcname) |
|
363 | func = getattr(module, funcname) | |
363 | log.info(inspect.getsource(func)) |
|
364 | log.info(inspect.getsource(func)) | |
364 | else: |
|
365 | else: | |
365 | schema.runchange(ver, change, changeset.step) |
|
366 | schema.runchange(ver, change, changeset.step) | |
366 | log.info('done') |
|
367 | log.info('done') | |
367 |
|
368 | |||
368 |
|
369 | |||
369 | def _migrate_version(schema, version, upgrade, err): |
|
370 | def _migrate_version(schema, version, upgrade, err): | |
370 | if version is None: |
|
371 | if version is None: | |
371 | return version |
|
372 | return version | |
372 | # Version is specified: ensure we're upgrading in the right direction |
|
373 | # Version is specified: ensure we're upgrading in the right direction | |
373 | # (current version < target version for upgrading; reverse for down) |
|
374 | # (current version < target version for upgrading; reverse for down) | |
374 | version = VerNum(version) |
|
375 | version = VerNum(version) | |
375 | cur = schema.version |
|
376 | cur = schema.version | |
376 | if upgrade is not None: |
|
377 | if upgrade is not None: | |
377 | if upgrade: |
|
378 | if upgrade: | |
378 | direction = cur <= version |
|
379 | direction = cur <= version | |
379 | else: |
|
380 | else: | |
380 | direction = cur >= version |
|
381 | direction = cur >= version | |
381 | if not direction: |
|
382 | if not direction: | |
382 | raise exceptions.KnownError(err % (cur, version)) |
|
383 | raise exceptions.KnownError(err % (cur, version)) | |
383 | return version |
|
384 | return version |
@@ -1,242 +1,242 b'' | |||||
1 | """ |
|
1 | """ | |
2 | SQLAlchemy migrate repository management. |
|
2 | SQLAlchemy migrate repository management. | |
3 | """ |
|
3 | """ | |
4 | import os |
|
4 | import os | |
5 | import shutil |
|
5 | import shutil | |
6 | import string |
|
6 | import string | |
7 | import logging |
|
7 | import logging | |
8 |
|
8 | |||
9 | from pkg_resources import resource_filename |
|
9 | from pkg_resources import resource_filename | |
10 | from tempita import Template as TempitaTemplate |
|
10 | from tempita import Template as TempitaTemplate | |
11 |
|
11 | |||
12 | from rhodecode.lib.dbmigrate.migrate import exceptions |
|
12 | from rhodecode.lib.dbmigrate.migrate import exceptions | |
13 | from rhodecode.lib.dbmigrate.migrate.versioning import version, pathed, cfgparse |
|
13 | from rhodecode.lib.dbmigrate.migrate.versioning import version, pathed, cfgparse | |
14 | from rhodecode.lib.dbmigrate.migrate.versioning.template import Template |
|
14 | from rhodecode.lib.dbmigrate.migrate.versioning.template import Template | |
15 | from rhodecode.lib.dbmigrate.migrate.versioning.config import * |
|
15 | from rhodecode.lib.dbmigrate.migrate.versioning.config import * | |
16 |
|
16 | |||
17 |
|
17 | |||
18 | log = logging.getLogger(__name__) |
|
18 | log = logging.getLogger(__name__) | |
19 |
|
19 | |||
20 | class Changeset(dict): |
|
20 | class Changeset(dict): | |
21 | """A collection of changes to be applied to a database. |
|
21 | """A collection of changes to be applied to a database. | |
22 |
|
22 | |||
23 | Changesets are bound to a repository and manage a set of |
|
23 | Changesets are bound to a repository and manage a set of | |
24 | scripts from that repository. |
|
24 | scripts from that repository. | |
25 |
|
25 | |||
26 | Behaves like a dict, for the most part. Keys are ordered based on step value. |
|
26 | Behaves like a dict, for the most part. Keys are ordered based on step value. | |
27 | """ |
|
27 | """ | |
28 |
|
28 | |||
29 | def __init__(self, start, *changes, **k): |
|
29 | def __init__(self, start, *changes, **k): | |
30 | """ |
|
30 | """ | |
31 | Give a start version; step must be explicitly stated. |
|
31 | Give a start version; step must be explicitly stated. | |
32 | """ |
|
32 | """ | |
33 | self.step = k.pop('step', 1) |
|
33 | self.step = k.pop('step', 1) | |
34 | self.start = version.VerNum(start) |
|
34 | self.start = version.VerNum(start) | |
35 | self.end = self.start |
|
35 | self.end = self.start | |
36 | for change in changes: |
|
36 | for change in changes: | |
37 | self.add(change) |
|
37 | self.add(change) | |
38 |
|
38 | |||
39 | def __iter__(self): |
|
39 | def __iter__(self): | |
40 | return iter(self.items()) |
|
40 | return iter(self.items()) | |
41 |
|
41 | |||
42 | def keys(self): |
|
42 | def keys(self): | |
43 | """ |
|
43 | """ | |
44 | In a series of upgrades x -> y, keys are version x. Sorted. |
|
44 | In a series of upgrades x -> y, keys are version x. Sorted. | |
45 | """ |
|
45 | """ | |
46 | ret = super(Changeset, self).keys() |
|
46 | ret = super(Changeset, self).keys() | |
47 | # Reverse order if downgrading |
|
47 | # Reverse order if downgrading | |
48 | ret.sort(reverse=(self.step < 1)) |
|
48 | ret.sort(reverse=(self.step < 1)) | |
49 | return ret |
|
49 | return ret | |
50 |
|
50 | |||
51 | def values(self): |
|
51 | def values(self): | |
52 | return [self[k] for k in self.keys()] |
|
52 | return [self[k] for k in self.keys()] | |
53 |
|
53 | |||
54 | def items(self): |
|
54 | def items(self): | |
55 | return zip(self.keys(), self.values()) |
|
55 | return zip(self.keys(), self.values()) | |
56 |
|
56 | |||
57 | def add(self, change): |
|
57 | def add(self, change): | |
58 | """Add new change to changeset""" |
|
58 | """Add new change to changeset""" | |
59 | key = self.end |
|
59 | key = self.end | |
60 | self.end += self.step |
|
60 | self.end += self.step | |
61 | self[key] = change |
|
61 | self[key] = change | |
62 |
|
62 | |||
63 | def run(self, *p, **k): |
|
63 | def run(self, *p, **k): | |
64 | """Run the changeset scripts""" |
|
64 | """Run the changeset scripts""" | |
65 | for version, script in self: |
|
65 | for version, script in self: | |
66 | script.run(*p, **k) |
|
66 | script.run(*p, **k) | |
67 |
|
67 | |||
68 |
|
68 | |||
69 | class Repository(pathed.Pathed): |
|
69 | class Repository(pathed.Pathed): | |
70 | """A project's change script repository""" |
|
70 | """A project's change script repository""" | |
71 |
|
71 | |||
72 | _config = 'migrate.cfg' |
|
72 | _config = 'migrate.cfg' | |
73 | _versions = 'versions' |
|
73 | _versions = 'versions' | |
74 |
|
74 | |||
75 | def __init__(self, path): |
|
75 | def __init__(self, path): | |
76 | log.debug('Loading repository %s...' % path) |
|
76 | log.debug('Loading repository %s...' % path) | |
77 | self.verify(path) |
|
77 | self.verify(path) | |
78 | super(Repository, self).__init__(path) |
|
78 | super(Repository, self).__init__(path) | |
79 | self.config = cfgparse.Config(os.path.join(self.path, self._config)) |
|
79 | self.config = cfgparse.Config(os.path.join(self.path, self._config)) | |
80 | self.versions = version.Collection(os.path.join(self.path, |
|
80 | self.versions = version.Collection(os.path.join(self.path, | |
81 | self._versions)) |
|
81 | self._versions)) | |
82 | log.debug('Repository %s loaded successfully' % path) |
|
82 | log.debug('Repository %s loaded successfully' % path) | |
83 | log.debug('Config: %r' % self.config.to_dict()) |
|
83 | log.debug('Config: %r' % self.config.to_dict()) | |
84 |
|
84 | |||
85 | @classmethod |
|
85 | @classmethod | |
86 | def verify(cls, path): |
|
86 | def verify(cls, path): | |
87 | """ |
|
87 | """ | |
88 | Ensure the target path is a valid repository. |
|
88 | Ensure the target path is a valid repository. | |
89 |
|
89 | |||
90 | :raises: :exc:`InvalidRepositoryError <migrate.exceptions.InvalidRepositoryError>` |
|
90 | :raises: :exc:`InvalidRepositoryError <migrate.exceptions.InvalidRepositoryError>` | |
91 | """ |
|
91 | """ | |
92 | # Ensure the existence of required files |
|
92 | # Ensure the existence of required files | |
93 | try: |
|
93 | try: | |
94 | cls.require_found(path) |
|
94 | cls.require_found(path) | |
95 | cls.require_found(os.path.join(path, cls._config)) |
|
95 | cls.require_found(os.path.join(path, cls._config)) | |
96 | cls.require_found(os.path.join(path, cls._versions)) |
|
96 | cls.require_found(os.path.join(path, cls._versions)) | |
97 | except exceptions.PathNotFoundError, e: |
|
97 | except exceptions.PathNotFoundError, e: | |
98 | raise exceptions.InvalidRepositoryError(path) |
|
98 | raise exceptions.InvalidRepositoryError(path) | |
99 |
|
99 | |||
100 | @classmethod |
|
100 | @classmethod | |
101 | def prepare_config(cls, tmpl_dir, name, options=None): |
|
101 | def prepare_config(cls, tmpl_dir, name, options=None): | |
102 | """ |
|
102 | """ | |
103 | Prepare a project configuration file for a new project. |
|
103 | Prepare a project configuration file for a new project. | |
104 |
|
104 | |||
105 | :param tmpl_dir: Path to Repository template |
|
105 | :param tmpl_dir: Path to Repository template | |
106 | :param config_file: Name of the config file in Repository template |
|
106 | :param config_file: Name of the config file in Repository template | |
107 | :param name: Repository name |
|
107 | :param name: Repository name | |
108 | :type tmpl_dir: string |
|
108 | :type tmpl_dir: string | |
109 | :type config_file: string |
|
109 | :type config_file: string | |
110 | :type name: string |
|
110 | :type name: string | |
111 | :returns: Populated config file |
|
111 | :returns: Populated config file | |
112 | """ |
|
112 | """ | |
113 | if options is None: |
|
113 | if options is None: | |
114 | options = {} |
|
114 | options = {} | |
115 | options.setdefault('version_table', 'migrate_version') |
|
115 | options.setdefault('version_table', 'migrate_version') | |
116 | options.setdefault('repository_id', name) |
|
116 | options.setdefault('repository_id', name) | |
117 | options.setdefault('required_dbs', []) |
|
117 | options.setdefault('required_dbs', []) | |
118 |
options.setdefault('use_timestamp_numbering', |
|
118 | options.setdefault('use_timestamp_numbering', False) | |
119 |
|
119 | |||
120 | tmpl = open(os.path.join(tmpl_dir, cls._config)).read() |
|
120 | tmpl = open(os.path.join(tmpl_dir, cls._config)).read() | |
121 | ret = TempitaTemplate(tmpl).substitute(options) |
|
121 | ret = TempitaTemplate(tmpl).substitute(options) | |
122 |
|
122 | |||
123 | # cleanup |
|
123 | # cleanup | |
124 | del options['__template_name__'] |
|
124 | del options['__template_name__'] | |
125 |
|
125 | |||
126 | return ret |
|
126 | return ret | |
127 |
|
127 | |||
128 | @classmethod |
|
128 | @classmethod | |
129 | def create(cls, path, name, **opts): |
|
129 | def create(cls, path, name, **opts): | |
130 | """Create a repository at a specified path""" |
|
130 | """Create a repository at a specified path""" | |
131 | cls.require_notfound(path) |
|
131 | cls.require_notfound(path) | |
132 | theme = opts.pop('templates_theme', None) |
|
132 | theme = opts.pop('templates_theme', None) | |
133 | t_path = opts.pop('templates_path', None) |
|
133 | t_path = opts.pop('templates_path', None) | |
134 |
|
134 | |||
135 | # Create repository |
|
135 | # Create repository | |
136 | tmpl_dir = Template(t_path).get_repository(theme=theme) |
|
136 | tmpl_dir = Template(t_path).get_repository(theme=theme) | |
137 | shutil.copytree(tmpl_dir, path) |
|
137 | shutil.copytree(tmpl_dir, path) | |
138 |
|
138 | |||
139 | # Edit config defaults |
|
139 | # Edit config defaults | |
140 | config_text = cls.prepare_config(tmpl_dir, name, options=opts) |
|
140 | config_text = cls.prepare_config(tmpl_dir, name, options=opts) | |
141 | fd = open(os.path.join(path, cls._config), 'w') |
|
141 | fd = open(os.path.join(path, cls._config), 'w') | |
142 | fd.write(config_text) |
|
142 | fd.write(config_text) | |
143 | fd.close() |
|
143 | fd.close() | |
144 |
|
144 | |||
145 | opts['repository_name'] = name |
|
145 | opts['repository_name'] = name | |
146 |
|
146 | |||
147 | # Create a management script |
|
147 | # Create a management script | |
148 | manager = os.path.join(path, 'manage.py') |
|
148 | manager = os.path.join(path, 'manage.py') | |
149 | Repository.create_manage_file(manager, templates_theme=theme, |
|
149 | Repository.create_manage_file(manager, templates_theme=theme, | |
150 | templates_path=t_path, **opts) |
|
150 | templates_path=t_path, **opts) | |
151 |
|
151 | |||
152 | return cls(path) |
|
152 | return cls(path) | |
153 |
|
153 | |||
154 | def create_script(self, description, **k): |
|
154 | def create_script(self, description, **k): | |
155 | """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`""" |
|
155 | """API to :meth:`migrate.versioning.version.Collection.create_new_python_version`""" | |
156 |
|
156 | |||
157 | k['use_timestamp_numbering'] = self.use_timestamp_numbering |
|
157 | k['use_timestamp_numbering'] = self.use_timestamp_numbering | |
158 | self.versions.create_new_python_version(description, **k) |
|
158 | self.versions.create_new_python_version(description, **k) | |
159 |
|
159 | |||
160 | def create_script_sql(self, database, description, **k): |
|
160 | def create_script_sql(self, database, description, **k): | |
161 | """API to :meth:`migrate.versioning.version.Collection.create_new_sql_version`""" |
|
161 | """API to :meth:`migrate.versioning.version.Collection.create_new_sql_version`""" | |
162 | k['use_timestamp_numbering'] = self.use_timestamp_numbering |
|
162 | k['use_timestamp_numbering'] = self.use_timestamp_numbering | |
163 | self.versions.create_new_sql_version(database, description, **k) |
|
163 | self.versions.create_new_sql_version(database, description, **k) | |
164 |
|
164 | |||
165 | @property |
|
165 | @property | |
166 | def latest(self): |
|
166 | def latest(self): | |
167 | """API to :attr:`migrate.versioning.version.Collection.latest`""" |
|
167 | """API to :attr:`migrate.versioning.version.Collection.latest`""" | |
168 | return self.versions.latest |
|
168 | return self.versions.latest | |
169 |
|
169 | |||
170 | @property |
|
170 | @property | |
171 | def version_table(self): |
|
171 | def version_table(self): | |
172 | """Returns version_table name specified in config""" |
|
172 | """Returns version_table name specified in config""" | |
173 | return self.config.get('db_settings', 'version_table') |
|
173 | return self.config.get('db_settings', 'version_table') | |
174 |
|
174 | |||
175 | @property |
|
175 | @property | |
176 | def id(self): |
|
176 | def id(self): | |
177 | """Returns repository id specified in config""" |
|
177 | """Returns repository id specified in config""" | |
178 | return self.config.get('db_settings', 'repository_id') |
|
178 | return self.config.get('db_settings', 'repository_id') | |
179 |
|
179 | |||
180 | @property |
|
180 | @property | |
181 | def use_timestamp_numbering(self): |
|
181 | def use_timestamp_numbering(self): | |
182 | """Returns use_timestamp_numbering specified in config""" |
|
182 | """Returns use_timestamp_numbering specified in config""" | |
183 |
|
|
183 | if self.config.has_option('db_settings', 'use_timestamp_numbering'): | |
184 |
|
184 | return self.config.getboolean('db_settings', 'use_timestamp_numbering') | ||
185 |
return |
|
185 | return False | |
186 |
|
186 | |||
187 | def version(self, *p, **k): |
|
187 | def version(self, *p, **k): | |
188 | """API to :attr:`migrate.versioning.version.Collection.version`""" |
|
188 | """API to :attr:`migrate.versioning.version.Collection.version`""" | |
189 | return self.versions.version(*p, **k) |
|
189 | return self.versions.version(*p, **k) | |
190 |
|
190 | |||
191 | @classmethod |
|
191 | @classmethod | |
192 | def clear(cls): |
|
192 | def clear(cls): | |
193 | # TODO: deletes repo |
|
193 | # TODO: deletes repo | |
194 | super(Repository, cls).clear() |
|
194 | super(Repository, cls).clear() | |
195 | version.Collection.clear() |
|
195 | version.Collection.clear() | |
196 |
|
196 | |||
197 | def changeset(self, database, start, end=None): |
|
197 | def changeset(self, database, start, end=None): | |
198 | """Create a changeset to migrate this database from ver. start to end/latest. |
|
198 | """Create a changeset to migrate this database from ver. start to end/latest. | |
199 |
|
199 | |||
200 | :param database: name of database to generate changeset |
|
200 | :param database: name of database to generate changeset | |
201 | :param start: version to start at |
|
201 | :param start: version to start at | |
202 | :param end: version to end at (latest if None given) |
|
202 | :param end: version to end at (latest if None given) | |
203 | :type database: string |
|
203 | :type database: string | |
204 | :type start: int |
|
204 | :type start: int | |
205 | :type end: int |
|
205 | :type end: int | |
206 | :returns: :class:`Changeset instance <migration.versioning.repository.Changeset>` |
|
206 | :returns: :class:`Changeset instance <migration.versioning.repository.Changeset>` | |
207 | """ |
|
207 | """ | |
208 | start = version.VerNum(start) |
|
208 | start = version.VerNum(start) | |
209 |
|
209 | |||
210 | if end is None: |
|
210 | if end is None: | |
211 | end = self.latest |
|
211 | end = self.latest | |
212 | else: |
|
212 | else: | |
213 | end = version.VerNum(end) |
|
213 | end = version.VerNum(end) | |
214 |
|
214 | |||
215 | if start <= end: |
|
215 | if start <= end: | |
216 | step = 1 |
|
216 | step = 1 | |
217 | range_mod = 1 |
|
217 | range_mod = 1 | |
218 | op = 'upgrade' |
|
218 | op = 'upgrade' | |
219 | else: |
|
219 | else: | |
220 | step = -1 |
|
220 | step = -1 | |
221 | range_mod = 0 |
|
221 | range_mod = 0 | |
222 | op = 'downgrade' |
|
222 | op = 'downgrade' | |
223 |
|
223 | |||
224 | versions = range(start + range_mod, end + range_mod, step) |
|
224 | versions = range(start + range_mod, end + range_mod, step) | |
225 | changes = [self.version(v).script(database, op) for v in versions] |
|
225 | changes = [self.version(v).script(database, op) for v in versions] | |
226 | ret = Changeset(start, step=step, *changes) |
|
226 | ret = Changeset(start, step=step, *changes) | |
227 | return ret |
|
227 | return ret | |
228 |
|
228 | |||
229 | @classmethod |
|
229 | @classmethod | |
230 | def create_manage_file(cls, file_, **opts): |
|
230 | def create_manage_file(cls, file_, **opts): | |
231 | """Create a project management script (manage.py) |
|
231 | """Create a project management script (manage.py) | |
232 |
|
232 | |||
233 | :param file_: Destination file to be written |
|
233 | :param file_: Destination file to be written | |
234 | :param opts: Options that are passed to :func:`migrate.versioning.shell.main` |
|
234 | :param opts: Options that are passed to :func:`migrate.versioning.shell.main` | |
235 | """ |
|
235 | """ | |
236 | mng_file = Template(opts.pop('templates_path', None))\ |
|
236 | mng_file = Template(opts.pop('templates_path', None))\ | |
237 | .get_manage(theme=opts.pop('templates_theme', None)) |
|
237 | .get_manage(theme=opts.pop('templates_theme', None)) | |
238 |
|
238 | |||
239 | tmpl = open(mng_file).read() |
|
239 | tmpl = open(mng_file).read() | |
240 | fd = open(file_, 'w') |
|
240 | fd = open(file_, 'w') | |
241 | fd.write(TempitaTemplate(tmpl).substitute(opts)) |
|
241 | fd.write(TempitaTemplate(tmpl).substitute(opts)) | |
242 | fd.close() |
|
242 | fd.close() |
@@ -1,285 +1,293 b'' | |||||
1 | """ |
|
1 | """ | |
2 | Schema differencing support. |
|
2 | Schema differencing support. | |
3 | """ |
|
3 | """ | |
4 |
|
4 | |||
5 | import logging |
|
5 | import logging | |
6 | import sqlalchemy |
|
6 | import sqlalchemy | |
7 |
|
7 | |||
8 | from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06 |
|
8 | from rhodecode.lib.dbmigrate.migrate.changeset import SQLA_06 | |
9 | from sqlalchemy.types import Float |
|
9 | from sqlalchemy.types import Float | |
10 |
|
10 | |||
11 | log = logging.getLogger(__name__) |
|
11 | log = logging.getLogger(__name__) | |
12 |
|
12 | |||
13 | def getDiffOfModelAgainstDatabase(metadata, engine, excludeTables=None): |
|
13 | def getDiffOfModelAgainstDatabase(metadata, engine, excludeTables=None): | |
14 | """ |
|
14 | """ | |
15 | Return differences of model against database. |
|
15 | Return differences of model against database. | |
16 |
|
16 | |||
17 | :return: object which will evaluate to :keyword:`True` if there \ |
|
17 | :return: object which will evaluate to :keyword:`True` if there \ | |
18 | are differences else :keyword:`False`. |
|
18 | are differences else :keyword:`False`. | |
19 | """ |
|
19 | """ | |
20 | return SchemaDiff(metadata, |
|
20 | db_metadata = sqlalchemy.MetaData(engine, reflect=True) | |
21 | sqlalchemy.MetaData(engine, reflect=True), |
|
21 | ||
|
22 | # sqlite will include a dynamically generated 'sqlite_sequence' table if | |||
|
23 | # there are autoincrement sequences in the database; this should not be | |||
|
24 | # compared. | |||
|
25 | if engine.dialect.name == 'sqlite': | |||
|
26 | if 'sqlite_sequence' in db_metadata.tables: | |||
|
27 | db_metadata.remove(db_metadata.tables['sqlite_sequence']) | |||
|
28 | ||||
|
29 | return SchemaDiff(metadata, db_metadata, | |||
22 | labelA='model', |
|
30 | labelA='model', | |
23 | labelB='database', |
|
31 | labelB='database', | |
24 | excludeTables=excludeTables) |
|
32 | excludeTables=excludeTables) | |
25 |
|
33 | |||
26 |
|
34 | |||
27 | def getDiffOfModelAgainstModel(metadataA, metadataB, excludeTables=None): |
|
35 | def getDiffOfModelAgainstModel(metadataA, metadataB, excludeTables=None): | |
28 | """ |
|
36 | """ | |
29 | Return differences of model against another model. |
|
37 | Return differences of model against another model. | |
30 |
|
38 | |||
31 | :return: object which will evaluate to :keyword:`True` if there \ |
|
39 | :return: object which will evaluate to :keyword:`True` if there \ | |
32 | are differences else :keyword:`False`. |
|
40 | are differences else :keyword:`False`. | |
33 | """ |
|
41 | """ | |
34 | return SchemaDiff(metadataA, metadataB, excludeTables) |
|
42 | return SchemaDiff(metadataA, metadataB, excludeTables) | |
35 |
|
43 | |||
36 |
|
44 | |||
37 | class ColDiff(object): |
|
45 | class ColDiff(object): | |
38 | """ |
|
46 | """ | |
39 | Container for differences in one :class:`~sqlalchemy.schema.Column` |
|
47 | Container for differences in one :class:`~sqlalchemy.schema.Column` | |
40 | between two :class:`~sqlalchemy.schema.Table` instances, ``A`` |
|
48 | between two :class:`~sqlalchemy.schema.Table` instances, ``A`` | |
41 | and ``B``. |
|
49 | and ``B``. | |
42 |
|
50 | |||
43 | .. attribute:: col_A |
|
51 | .. attribute:: col_A | |
44 |
|
52 | |||
45 | The :class:`~sqlalchemy.schema.Column` object for A. |
|
53 | The :class:`~sqlalchemy.schema.Column` object for A. | |
46 |
|
54 | |||
47 | .. attribute:: col_B |
|
55 | .. attribute:: col_B | |
48 |
|
56 | |||
49 | The :class:`~sqlalchemy.schema.Column` object for B. |
|
57 | The :class:`~sqlalchemy.schema.Column` object for B. | |
50 |
|
58 | |||
51 | .. attribute:: type_A |
|
59 | .. attribute:: type_A | |
52 |
|
60 | |||
53 | The most generic type of the :class:`~sqlalchemy.schema.Column` |
|
61 | The most generic type of the :class:`~sqlalchemy.schema.Column` | |
54 | object in A. |
|
62 | object in A. | |
55 |
|
63 | |||
56 | .. attribute:: type_B |
|
64 | .. attribute:: type_B | |
57 |
|
65 | |||
58 | The most generic type of the :class:`~sqlalchemy.schema.Column` |
|
66 | The most generic type of the :class:`~sqlalchemy.schema.Column` | |
59 | object in A. |
|
67 | object in A. | |
60 |
|
68 | |||
61 | """ |
|
69 | """ | |
62 |
|
70 | |||
63 | diff = False |
|
71 | diff = False | |
64 |
|
72 | |||
65 | def __init__(self,col_A,col_B): |
|
73 | def __init__(self,col_A,col_B): | |
66 | self.col_A = col_A |
|
74 | self.col_A = col_A | |
67 | self.col_B = col_B |
|
75 | self.col_B = col_B | |
68 |
|
76 | |||
69 | self.type_A = col_A.type |
|
77 | self.type_A = col_A.type | |
70 | self.type_B = col_B.type |
|
78 | self.type_B = col_B.type | |
71 |
|
79 | |||
72 | self.affinity_A = self.type_A._type_affinity |
|
80 | self.affinity_A = self.type_A._type_affinity | |
73 | self.affinity_B = self.type_B._type_affinity |
|
81 | self.affinity_B = self.type_B._type_affinity | |
74 |
|
82 | |||
75 | if self.affinity_A is not self.affinity_B: |
|
83 | if self.affinity_A is not self.affinity_B: | |
76 | self.diff = True |
|
84 | self.diff = True | |
77 | return |
|
85 | return | |
78 |
|
86 | |||
79 | if isinstance(self.type_A,Float) or isinstance(self.type_B,Float): |
|
87 | if isinstance(self.type_A,Float) or isinstance(self.type_B,Float): | |
80 | if not (isinstance(self.type_A,Float) and isinstance(self.type_B,Float)): |
|
88 | if not (isinstance(self.type_A,Float) and isinstance(self.type_B,Float)): | |
81 | self.diff=True |
|
89 | self.diff=True | |
82 | return |
|
90 | return | |
83 |
|
91 | |||
84 | for attr in ('precision','scale','length'): |
|
92 | for attr in ('precision','scale','length'): | |
85 | A = getattr(self.type_A,attr,None) |
|
93 | A = getattr(self.type_A,attr,None) | |
86 | B = getattr(self.type_B,attr,None) |
|
94 | B = getattr(self.type_B,attr,None) | |
87 | if not (A is None or B is None) and A!=B: |
|
95 | if not (A is None or B is None) and A!=B: | |
88 | self.diff=True |
|
96 | self.diff=True | |
89 | return |
|
97 | return | |
90 |
|
98 | |||
91 | def __nonzero__(self): |
|
99 | def __nonzero__(self): | |
92 | return self.diff |
|
100 | return self.diff | |
93 |
|
101 | |||
94 | class TableDiff(object): |
|
102 | class TableDiff(object): | |
95 | """ |
|
103 | """ | |
96 | Container for differences in one :class:`~sqlalchemy.schema.Table` |
|
104 | Container for differences in one :class:`~sqlalchemy.schema.Table` | |
97 | between two :class:`~sqlalchemy.schema.MetaData` instances, ``A`` |
|
105 | between two :class:`~sqlalchemy.schema.MetaData` instances, ``A`` | |
98 | and ``B``. |
|
106 | and ``B``. | |
99 |
|
107 | |||
100 | .. attribute:: columns_missing_from_A |
|
108 | .. attribute:: columns_missing_from_A | |
101 |
|
109 | |||
102 | A sequence of column names that were found in B but weren't in |
|
110 | A sequence of column names that were found in B but weren't in | |
103 | A. |
|
111 | A. | |
104 |
|
112 | |||
105 | .. attribute:: columns_missing_from_B |
|
113 | .. attribute:: columns_missing_from_B | |
106 |
|
114 | |||
107 | A sequence of column names that were found in A but weren't in |
|
115 | A sequence of column names that were found in A but weren't in | |
108 | B. |
|
116 | B. | |
109 |
|
117 | |||
110 | .. attribute:: columns_different |
|
118 | .. attribute:: columns_different | |
111 |
|
119 | |||
112 | A dictionary containing information about columns that were |
|
120 | A dictionary containing information about columns that were | |
113 | found to be different. |
|
121 | found to be different. | |
114 | It maps column names to a :class:`ColDiff` objects describing the |
|
122 | It maps column names to a :class:`ColDiff` objects describing the | |
115 | differences found. |
|
123 | differences found. | |
116 | """ |
|
124 | """ | |
117 | __slots__ = ( |
|
125 | __slots__ = ( | |
118 | 'columns_missing_from_A', |
|
126 | 'columns_missing_from_A', | |
119 | 'columns_missing_from_B', |
|
127 | 'columns_missing_from_B', | |
120 | 'columns_different', |
|
128 | 'columns_different', | |
121 | ) |
|
129 | ) | |
122 |
|
130 | |||
123 | def __nonzero__(self): |
|
131 | def __nonzero__(self): | |
124 | return bool( |
|
132 | return bool( | |
125 | self.columns_missing_from_A or |
|
133 | self.columns_missing_from_A or | |
126 | self.columns_missing_from_B or |
|
134 | self.columns_missing_from_B or | |
127 | self.columns_different |
|
135 | self.columns_different | |
128 | ) |
|
136 | ) | |
129 |
|
137 | |||
130 | class SchemaDiff(object): |
|
138 | class SchemaDiff(object): | |
131 | """ |
|
139 | """ | |
132 | Compute the difference between two :class:`~sqlalchemy.schema.MetaData` |
|
140 | Compute the difference between two :class:`~sqlalchemy.schema.MetaData` | |
133 | objects. |
|
141 | objects. | |
134 |
|
142 | |||
135 | The string representation of a :class:`SchemaDiff` will summarise |
|
143 | The string representation of a :class:`SchemaDiff` will summarise | |
136 | the changes found between the two |
|
144 | the changes found between the two | |
137 | :class:`~sqlalchemy.schema.MetaData` objects. |
|
145 | :class:`~sqlalchemy.schema.MetaData` objects. | |
138 |
|
146 | |||
139 | The length of a :class:`SchemaDiff` will give the number of |
|
147 | The length of a :class:`SchemaDiff` will give the number of | |
140 | changes found, enabling it to be used much like a boolean in |
|
148 | changes found, enabling it to be used much like a boolean in | |
141 | expressions. |
|
149 | expressions. | |
142 |
|
150 | |||
143 | :param metadataA: |
|
151 | :param metadataA: | |
144 | First :class:`~sqlalchemy.schema.MetaData` to compare. |
|
152 | First :class:`~sqlalchemy.schema.MetaData` to compare. | |
145 |
|
153 | |||
146 | :param metadataB: |
|
154 | :param metadataB: | |
147 | Second :class:`~sqlalchemy.schema.MetaData` to compare. |
|
155 | Second :class:`~sqlalchemy.schema.MetaData` to compare. | |
148 |
|
156 | |||
149 | :param labelA: |
|
157 | :param labelA: | |
150 | The label to use in messages about the first |
|
158 | The label to use in messages about the first | |
151 | :class:`~sqlalchemy.schema.MetaData`. |
|
159 | :class:`~sqlalchemy.schema.MetaData`. | |
152 |
|
160 | |||
153 | :param labelB: |
|
161 | :param labelB: | |
154 | The label to use in messages about the second |
|
162 | The label to use in messages about the second | |
155 | :class:`~sqlalchemy.schema.MetaData`. |
|
163 | :class:`~sqlalchemy.schema.MetaData`. | |
156 |
|
164 | |||
157 | :param excludeTables: |
|
165 | :param excludeTables: | |
158 | A sequence of table names to exclude. |
|
166 | A sequence of table names to exclude. | |
159 |
|
167 | |||
160 | .. attribute:: tables_missing_from_A |
|
168 | .. attribute:: tables_missing_from_A | |
161 |
|
169 | |||
162 | A sequence of table names that were found in B but weren't in |
|
170 | A sequence of table names that were found in B but weren't in | |
163 | A. |
|
171 | A. | |
164 |
|
172 | |||
165 | .. attribute:: tables_missing_from_B |
|
173 | .. attribute:: tables_missing_from_B | |
166 |
|
174 | |||
167 | A sequence of table names that were found in A but weren't in |
|
175 | A sequence of table names that were found in A but weren't in | |
168 | B. |
|
176 | B. | |
169 |
|
177 | |||
170 | .. attribute:: tables_different |
|
178 | .. attribute:: tables_different | |
171 |
|
179 | |||
172 | A dictionary containing information about tables that were found |
|
180 | A dictionary containing information about tables that were found | |
173 | to be different. |
|
181 | to be different. | |
174 | It maps table names to a :class:`TableDiff` objects describing the |
|
182 | It maps table names to a :class:`TableDiff` objects describing the | |
175 | differences found. |
|
183 | differences found. | |
176 | """ |
|
184 | """ | |
177 |
|
185 | |||
178 | def __init__(self, |
|
186 | def __init__(self, | |
179 | metadataA, metadataB, |
|
187 | metadataA, metadataB, | |
180 | labelA='metadataA', |
|
188 | labelA='metadataA', | |
181 | labelB='metadataB', |
|
189 | labelB='metadataB', | |
182 | excludeTables=None): |
|
190 | excludeTables=None): | |
183 |
|
191 | |||
184 | self.metadataA, self.metadataB = metadataA, metadataB |
|
192 | self.metadataA, self.metadataB = metadataA, metadataB | |
185 | self.labelA, self.labelB = labelA, labelB |
|
193 | self.labelA, self.labelB = labelA, labelB | |
186 | self.label_width = max(len(labelA),len(labelB)) |
|
194 | self.label_width = max(len(labelA),len(labelB)) | |
187 | excludeTables = set(excludeTables or []) |
|
195 | excludeTables = set(excludeTables or []) | |
188 |
|
196 | |||
189 | A_table_names = set(metadataA.tables.keys()) |
|
197 | A_table_names = set(metadataA.tables.keys()) | |
190 | B_table_names = set(metadataB.tables.keys()) |
|
198 | B_table_names = set(metadataB.tables.keys()) | |
191 |
|
199 | |||
192 | self.tables_missing_from_A = sorted( |
|
200 | self.tables_missing_from_A = sorted( | |
193 | B_table_names - A_table_names - excludeTables |
|
201 | B_table_names - A_table_names - excludeTables | |
194 | ) |
|
202 | ) | |
195 | self.tables_missing_from_B = sorted( |
|
203 | self.tables_missing_from_B = sorted( | |
196 | A_table_names - B_table_names - excludeTables |
|
204 | A_table_names - B_table_names - excludeTables | |
197 | ) |
|
205 | ) | |
198 |
|
206 | |||
199 | self.tables_different = {} |
|
207 | self.tables_different = {} | |
200 | for table_name in A_table_names.intersection(B_table_names): |
|
208 | for table_name in A_table_names.intersection(B_table_names): | |
201 |
|
209 | |||
202 | td = TableDiff() |
|
210 | td = TableDiff() | |
203 |
|
211 | |||
204 | A_table = metadataA.tables[table_name] |
|
212 | A_table = metadataA.tables[table_name] | |
205 | B_table = metadataB.tables[table_name] |
|
213 | B_table = metadataB.tables[table_name] | |
206 |
|
214 | |||
207 | A_column_names = set(A_table.columns.keys()) |
|
215 | A_column_names = set(A_table.columns.keys()) | |
208 | B_column_names = set(B_table.columns.keys()) |
|
216 | B_column_names = set(B_table.columns.keys()) | |
209 |
|
217 | |||
210 | td.columns_missing_from_A = sorted( |
|
218 | td.columns_missing_from_A = sorted( | |
211 | B_column_names - A_column_names |
|
219 | B_column_names - A_column_names | |
212 | ) |
|
220 | ) | |
213 |
|
221 | |||
214 | td.columns_missing_from_B = sorted( |
|
222 | td.columns_missing_from_B = sorted( | |
215 | A_column_names - B_column_names |
|
223 | A_column_names - B_column_names | |
216 | ) |
|
224 | ) | |
217 |
|
225 | |||
218 | td.columns_different = {} |
|
226 | td.columns_different = {} | |
219 |
|
227 | |||
220 | for col_name in A_column_names.intersection(B_column_names): |
|
228 | for col_name in A_column_names.intersection(B_column_names): | |
221 |
|
229 | |||
222 | cd = ColDiff( |
|
230 | cd = ColDiff( | |
223 | A_table.columns.get(col_name), |
|
231 | A_table.columns.get(col_name), | |
224 | B_table.columns.get(col_name) |
|
232 | B_table.columns.get(col_name) | |
225 | ) |
|
233 | ) | |
226 |
|
234 | |||
227 | if cd: |
|
235 | if cd: | |
228 | td.columns_different[col_name]=cd |
|
236 | td.columns_different[col_name]=cd | |
229 |
|
237 | |||
230 | # XXX - index and constraint differences should |
|
238 | # XXX - index and constraint differences should | |
231 | # be checked for here |
|
239 | # be checked for here | |
232 |
|
240 | |||
233 | if td: |
|
241 | if td: | |
234 | self.tables_different[table_name]=td |
|
242 | self.tables_different[table_name]=td | |
235 |
|
243 | |||
236 | def __str__(self): |
|
244 | def __str__(self): | |
237 | ''' Summarize differences. ''' |
|
245 | ''' Summarize differences. ''' | |
238 | out = [] |
|
246 | out = [] | |
239 | column_template =' %%%is: %%r' % self.label_width |
|
247 | column_template =' %%%is: %%r' % self.label_width | |
240 |
|
248 | |||
241 | for names,label in ( |
|
249 | for names,label in ( | |
242 | (self.tables_missing_from_A,self.labelA), |
|
250 | (self.tables_missing_from_A,self.labelA), | |
243 | (self.tables_missing_from_B,self.labelB), |
|
251 | (self.tables_missing_from_B,self.labelB), | |
244 | ): |
|
252 | ): | |
245 | if names: |
|
253 | if names: | |
246 | out.append( |
|
254 | out.append( | |
247 | ' tables missing from %s: %s' % ( |
|
255 | ' tables missing from %s: %s' % ( | |
248 | label,', '.join(sorted(names)) |
|
256 | label,', '.join(sorted(names)) | |
249 | ) |
|
257 | ) | |
250 | ) |
|
258 | ) | |
251 |
|
259 | |||
252 | for name,td in sorted(self.tables_different.items()): |
|
260 | for name,td in sorted(self.tables_different.items()): | |
253 | out.append( |
|
261 | out.append( | |
254 | ' table with differences: %s' % name |
|
262 | ' table with differences: %s' % name | |
255 | ) |
|
263 | ) | |
256 | for names,label in ( |
|
264 | for names,label in ( | |
257 | (td.columns_missing_from_A,self.labelA), |
|
265 | (td.columns_missing_from_A,self.labelA), | |
258 | (td.columns_missing_from_B,self.labelB), |
|
266 | (td.columns_missing_from_B,self.labelB), | |
259 | ): |
|
267 | ): | |
260 | if names: |
|
268 | if names: | |
261 | out.append( |
|
269 | out.append( | |
262 | ' %s missing these columns: %s' % ( |
|
270 | ' %s missing these columns: %s' % ( | |
263 | label,', '.join(sorted(names)) |
|
271 | label,', '.join(sorted(names)) | |
264 | ) |
|
272 | ) | |
265 | ) |
|
273 | ) | |
266 | for name,cd in td.columns_different.items(): |
|
274 | for name,cd in td.columns_different.items(): | |
267 | out.append(' column with differences: %s' % name) |
|
275 | out.append(' column with differences: %s' % name) | |
268 | out.append(column_template % (self.labelA,cd.col_A)) |
|
276 | out.append(column_template % (self.labelA,cd.col_A)) | |
269 | out.append(column_template % (self.labelB,cd.col_B)) |
|
277 | out.append(column_template % (self.labelB,cd.col_B)) | |
270 |
|
278 | |||
271 | if out: |
|
279 | if out: | |
272 | out.insert(0, 'Schema diffs:') |
|
280 | out.insert(0, 'Schema diffs:') | |
273 | return '\n'.join(out) |
|
281 | return '\n'.join(out) | |
274 | else: |
|
282 | else: | |
275 | return 'No schema diffs' |
|
283 | return 'No schema diffs' | |
276 |
|
284 | |||
277 | def __len__(self): |
|
285 | def __len__(self): | |
278 | """ |
|
286 | """ | |
279 | Used in bool evaluation, return of 0 means no diffs. |
|
287 | Used in bool evaluation, return of 0 means no diffs. | |
280 | """ |
|
288 | """ | |
281 | return ( |
|
289 | return ( | |
282 | len(self.tables_missing_from_A) + |
|
290 | len(self.tables_missing_from_A) + | |
283 | len(self.tables_missing_from_B) + |
|
291 | len(self.tables_missing_from_B) + | |
284 | len(self.tables_different) |
|
292 | len(self.tables_different) | |
285 | ) |
|
293 | ) |
@@ -1,10 +1,12 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | from migrate.versioning.shell import main |
|
2 | from migrate.versioning.shell import main | |
3 |
|
3 | |||
4 | {{py: |
|
4 | {{py: | |
5 | _vars = locals().copy() |
|
5 | _vars = locals().copy() | |
6 | del _vars['__template_name__'] |
|
6 | del _vars['__template_name__'] | |
7 | _vars.pop('repository_name', None) |
|
7 | _vars.pop('repository_name', None) | |
8 | defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()]) |
|
8 | defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()]) | |
9 | }} |
|
9 | }} | |
10 | main({{ defaults }}) |
|
10 | ||
|
11 | if __name__ == '__main__': | |||
|
12 | main({{ defaults }}) |
@@ -1,29 +1,30 b'' | |||||
1 | #!/usr/bin/python |
|
1 | #!/usr/bin/python | |
2 | # -*- coding: utf-8 -*- |
|
2 | # -*- coding: utf-8 -*- | |
3 | import sys |
|
3 | import sys | |
4 |
|
4 | |||
5 | from sqlalchemy import engine_from_config |
|
5 | from sqlalchemy import engine_from_config | |
6 | from paste.deploy.loadwsgi import ConfigLoader |
|
6 | from paste.deploy.loadwsgi import ConfigLoader | |
7 |
|
7 | |||
8 | from migrate.versioning.shell import main |
|
8 | from migrate.versioning.shell import main | |
9 | from {{ locals().pop('repository_name') }}.model import migrations |
|
9 | from {{ locals().pop('repository_name') }}.model import migrations | |
10 |
|
10 | |||
11 |
|
11 | |||
12 | if '-c' in sys.argv: |
|
12 | if '-c' in sys.argv: | |
13 | pos = sys.argv.index('-c') |
|
13 | pos = sys.argv.index('-c') | |
14 | conf_path = sys.argv[pos + 1] |
|
14 | conf_path = sys.argv[pos + 1] | |
15 | del sys.argv[pos:pos + 2] |
|
15 | del sys.argv[pos:pos + 2] | |
16 | else: |
|
16 | else: | |
17 | conf_path = 'development.ini' |
|
17 | conf_path = 'development.ini' | |
18 |
|
18 | |||
19 | {{py: |
|
19 | {{py: | |
20 | _vars = locals().copy() |
|
20 | _vars = locals().copy() | |
21 | del _vars['__template_name__'] |
|
21 | del _vars['__template_name__'] | |
22 | defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()]) |
|
22 | defaults = ", ".join(["%s='%s'" % var for var in _vars.iteritems()]) | |
23 | }} |
|
23 | }} | |
24 |
|
24 | |||
25 | conf_dict = ConfigLoader(conf_path).parser._sections['app:main'] |
|
25 | conf_dict = ConfigLoader(conf_path).parser._sections['app:main'] | |
26 |
|
26 | |||
27 | # migrate supports passing url as an existing Engine instance (since 0.6.0) |
|
27 | # migrate supports passing url as an existing Engine instance (since 0.6.0) | |
28 | # usage: migrate -c path/to/config.ini COMMANDS |
|
28 | # usage: migrate -c path/to/config.ini COMMANDS | |
29 | main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }}) |
|
29 | if __name__ == '__main__': | |
|
30 | main(url=engine_from_config(conf_dict), repository=migrations.__path__[0],{{ defaults }}) |
@@ -1,240 +1,238 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | # -*- coding: utf-8 -*- |
|
2 | # -*- coding: utf-8 -*- | |
3 |
|
3 | |||
4 | import os |
|
4 | import os | |
5 | import re |
|
5 | import re | |
6 | import shutil |
|
6 | import shutil | |
7 | import logging |
|
7 | import logging | |
8 |
|
8 | |||
9 | from rhodecode.lib.dbmigrate.migrate import exceptions |
|
9 | from rhodecode.lib.dbmigrate.migrate import exceptions | |
10 | from rhodecode.lib.dbmigrate.migrate.versioning import pathed, script |
|
10 | from rhodecode.lib.dbmigrate.migrate.versioning import pathed, script | |
11 | from datetime import datetime |
|
11 | from datetime import datetime | |
12 |
|
12 | |||
13 |
|
13 | |||
14 | log = logging.getLogger(__name__) |
|
14 | log = logging.getLogger(__name__) | |
15 |
|
15 | |||
16 | class VerNum(object): |
|
16 | class VerNum(object): | |
17 | """A version number that behaves like a string and int at the same time""" |
|
17 | """A version number that behaves like a string and int at the same time""" | |
18 |
|
18 | |||
19 | _instances = dict() |
|
19 | _instances = dict() | |
20 |
|
20 | |||
21 | def __new__(cls, value): |
|
21 | def __new__(cls, value): | |
22 | val = str(value) |
|
22 | val = str(value) | |
23 | if val not in cls._instances: |
|
23 | if val not in cls._instances: | |
24 | cls._instances[val] = super(VerNum, cls).__new__(cls) |
|
24 | cls._instances[val] = super(VerNum, cls).__new__(cls) | |
25 | ret = cls._instances[val] |
|
25 | ret = cls._instances[val] | |
26 | return ret |
|
26 | return ret | |
27 |
|
27 | |||
28 | def __init__(self,value): |
|
28 | def __init__(self,value): | |
29 | self.value = str(int(value)) |
|
29 | self.value = str(int(value)) | |
30 | if self < 0: |
|
30 | if self < 0: | |
31 | raise ValueError("Version number cannot be negative") |
|
31 | raise ValueError("Version number cannot be negative") | |
32 |
|
32 | |||
33 | def __add__(self, value): |
|
33 | def __add__(self, value): | |
34 | ret = int(self) + int(value) |
|
34 | ret = int(self) + int(value) | |
35 | return VerNum(ret) |
|
35 | return VerNum(ret) | |
36 |
|
36 | |||
37 | def __sub__(self, value): |
|
37 | def __sub__(self, value): | |
38 | return self + (int(value) * -1) |
|
38 | return self + (int(value) * -1) | |
39 |
|
39 | |||
40 | def __cmp__(self, value): |
|
40 | def __cmp__(self, value): | |
41 | return int(self) - int(value) |
|
41 | return int(self) - int(value) | |
42 |
|
42 | |||
43 | def __repr__(self): |
|
43 | def __repr__(self): | |
44 | return "<VerNum(%s)>" % self.value |
|
44 | return "<VerNum(%s)>" % self.value | |
45 |
|
45 | |||
46 | def __str__(self): |
|
46 | def __str__(self): | |
47 | return str(self.value) |
|
47 | return str(self.value) | |
48 |
|
48 | |||
49 | def __int__(self): |
|
49 | def __int__(self): | |
50 | return int(self.value) |
|
50 | return int(self.value) | |
51 |
|
51 | |||
52 |
|
52 | |||
53 | class Collection(pathed.Pathed): |
|
53 | class Collection(pathed.Pathed): | |
54 | """A collection of versioning scripts in a repository""" |
|
54 | """A collection of versioning scripts in a repository""" | |
55 |
|
55 | |||
56 | FILENAME_WITH_VERSION = re.compile(r'^(\d{3,}).*') |
|
56 | FILENAME_WITH_VERSION = re.compile(r'^(\d{3,}).*') | |
57 |
|
57 | |||
58 | def __init__(self, path): |
|
58 | def __init__(self, path): | |
59 | """Collect current version scripts in repository |
|
59 | """Collect current version scripts in repository | |
60 | and store them in self.versions |
|
60 | and store them in self.versions | |
61 | """ |
|
61 | """ | |
62 | super(Collection, self).__init__(path) |
|
62 | super(Collection, self).__init__(path) | |
63 |
|
63 | |||
64 | # Create temporary list of files, allowing skipped version numbers. |
|
64 | # Create temporary list of files, allowing skipped version numbers. | |
65 | files = os.listdir(path) |
|
65 | files = os.listdir(path) | |
66 | if '1' in files: |
|
66 | if '1' in files: | |
67 | # deprecation |
|
67 | # deprecation | |
68 | raise Exception('It looks like you have a repository in the old ' |
|
68 | raise Exception('It looks like you have a repository in the old ' | |
69 | 'format (with directories for each version). ' |
|
69 | 'format (with directories for each version). ' | |
70 | 'Please convert repository before proceeding.') |
|
70 | 'Please convert repository before proceeding.') | |
71 |
|
71 | |||
72 | tempVersions = dict() |
|
72 | tempVersions = dict() | |
73 | for filename in files: |
|
73 | for filename in files: | |
74 | match = self.FILENAME_WITH_VERSION.match(filename) |
|
74 | match = self.FILENAME_WITH_VERSION.match(filename) | |
75 | if match: |
|
75 | if match: | |
76 | num = int(match.group(1)) |
|
76 | num = int(match.group(1)) | |
77 | tempVersions.setdefault(num, []).append(filename) |
|
77 | tempVersions.setdefault(num, []).append(filename) | |
78 | else: |
|
78 | else: | |
79 | pass # Must be a helper file or something, let's ignore it. |
|
79 | pass # Must be a helper file or something, let's ignore it. | |
80 |
|
80 | |||
81 | # Create the versions member where the keys |
|
81 | # Create the versions member where the keys | |
82 | # are VerNum's and the values are Version's. |
|
82 | # are VerNum's and the values are Version's. | |
83 | self.versions = dict() |
|
83 | self.versions = dict() | |
84 | for num, files in tempVersions.items(): |
|
84 | for num, files in tempVersions.items(): | |
85 | self.versions[VerNum(num)] = Version(num, path, files) |
|
85 | self.versions[VerNum(num)] = Version(num, path, files) | |
86 |
|
86 | |||
87 | @property |
|
87 | @property | |
88 | def latest(self): |
|
88 | def latest(self): | |
89 | """:returns: Latest version in Collection""" |
|
89 | """:returns: Latest version in Collection""" | |
90 | return max([VerNum(0)] + self.versions.keys()) |
|
90 | return max([VerNum(0)] + self.versions.keys()) | |
91 |
|
91 | |||
92 | def _next_ver_num(self, use_timestamp_numbering): |
|
92 | def _next_ver_num(self, use_timestamp_numbering): | |
93 | print use_timestamp_numbering |
|
|||
94 | if use_timestamp_numbering == True: |
|
93 | if use_timestamp_numbering == True: | |
95 | print "Creating new timestamp version!" |
|
|||
96 | return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S'))) |
|
94 | return VerNum(int(datetime.utcnow().strftime('%Y%m%d%H%M%S'))) | |
97 | else: |
|
95 | else: | |
98 | return self.latest + 1 |
|
96 | return self.latest + 1 | |
99 |
|
97 | |||
100 | def create_new_python_version(self, description, **k): |
|
98 | def create_new_python_version(self, description, **k): | |
101 | """Create Python files for new version""" |
|
99 | """Create Python files for new version""" | |
102 | ver = self._next_ver_num(k.pop('use_timestamp_numbering', False)) |
|
100 | ver = self._next_ver_num(k.pop('use_timestamp_numbering', False)) | |
103 | extra = str_to_filename(description) |
|
101 | extra = str_to_filename(description) | |
104 |
|
102 | |||
105 | if extra: |
|
103 | if extra: | |
106 | if extra == '_': |
|
104 | if extra == '_': | |
107 | extra = '' |
|
105 | extra = '' | |
108 | elif not extra.startswith('_'): |
|
106 | elif not extra.startswith('_'): | |
109 | extra = '_%s' % extra |
|
107 | extra = '_%s' % extra | |
110 |
|
108 | |||
111 | filename = '%03d%s.py' % (ver, extra) |
|
109 | filename = '%03d%s.py' % (ver, extra) | |
112 | filepath = self._version_path(filename) |
|
110 | filepath = self._version_path(filename) | |
113 |
|
111 | |||
114 | script.PythonScript.create(filepath, **k) |
|
112 | script.PythonScript.create(filepath, **k) | |
115 | self.versions[ver] = Version(ver, self.path, [filename]) |
|
113 | self.versions[ver] = Version(ver, self.path, [filename]) | |
116 |
|
114 | |||
117 | def create_new_sql_version(self, database, description, **k): |
|
115 | def create_new_sql_version(self, database, description, **k): | |
118 | """Create SQL files for new version""" |
|
116 | """Create SQL files for new version""" | |
119 | ver = self._next_ver_num(k.pop('use_timestamp_numbering', False)) |
|
117 | ver = self._next_ver_num(k.pop('use_timestamp_numbering', False)) | |
120 | self.versions[ver] = Version(ver, self.path, []) |
|
118 | self.versions[ver] = Version(ver, self.path, []) | |
121 |
|
119 | |||
122 | extra = str_to_filename(description) |
|
120 | extra = str_to_filename(description) | |
123 |
|
121 | |||
124 | if extra: |
|
122 | if extra: | |
125 | if extra == '_': |
|
123 | if extra == '_': | |
126 | extra = '' |
|
124 | extra = '' | |
127 | elif not extra.startswith('_'): |
|
125 | elif not extra.startswith('_'): | |
128 | extra = '_%s' % extra |
|
126 | extra = '_%s' % extra | |
129 |
|
127 | |||
130 | # Create new files. |
|
128 | # Create new files. | |
131 | for op in ('upgrade', 'downgrade'): |
|
129 | for op in ('upgrade', 'downgrade'): | |
132 | filename = '%03d%s_%s_%s.sql' % (ver, extra, database, op) |
|
130 | filename = '%03d%s_%s_%s.sql' % (ver, extra, database, op) | |
133 | filepath = self._version_path(filename) |
|
131 | filepath = self._version_path(filename) | |
134 | script.SqlScript.create(filepath, **k) |
|
132 | script.SqlScript.create(filepath, **k) | |
135 | self.versions[ver].add_script(filepath) |
|
133 | self.versions[ver].add_script(filepath) | |
136 |
|
134 | |||
137 | def version(self, vernum=None): |
|
135 | def version(self, vernum=None): | |
138 | """Returns latest Version if vernum is not given. |
|
136 | """Returns latest Version if vernum is not given. | |
139 | Otherwise, returns wanted version""" |
|
137 | Otherwise, returns wanted version""" | |
140 | if vernum is None: |
|
138 | if vernum is None: | |
141 | vernum = self.latest |
|
139 | vernum = self.latest | |
142 | return self.versions[VerNum(vernum)] |
|
140 | return self.versions[VerNum(vernum)] | |
143 |
|
141 | |||
144 | @classmethod |
|
142 | @classmethod | |
145 | def clear(cls): |
|
143 | def clear(cls): | |
146 | super(Collection, cls).clear() |
|
144 | super(Collection, cls).clear() | |
147 |
|
145 | |||
148 | def _version_path(self, ver): |
|
146 | def _version_path(self, ver): | |
149 | """Returns path of file in versions repository""" |
|
147 | """Returns path of file in versions repository""" | |
150 | return os.path.join(self.path, str(ver)) |
|
148 | return os.path.join(self.path, str(ver)) | |
151 |
|
149 | |||
152 |
|
150 | |||
153 | class Version(object): |
|
151 | class Version(object): | |
154 | """A single version in a collection |
|
152 | """A single version in a collection | |
155 | :param vernum: Version Number |
|
153 | :param vernum: Version Number | |
156 | :param path: Path to script files |
|
154 | :param path: Path to script files | |
157 | :param filelist: List of scripts |
|
155 | :param filelist: List of scripts | |
158 | :type vernum: int, VerNum |
|
156 | :type vernum: int, VerNum | |
159 | :type path: string |
|
157 | :type path: string | |
160 | :type filelist: list |
|
158 | :type filelist: list | |
161 | """ |
|
159 | """ | |
162 |
|
160 | |||
163 | def __init__(self, vernum, path, filelist): |
|
161 | def __init__(self, vernum, path, filelist): | |
164 | self.version = VerNum(vernum) |
|
162 | self.version = VerNum(vernum) | |
165 |
|
163 | |||
166 | # Collect scripts in this folder |
|
164 | # Collect scripts in this folder | |
167 | self.sql = dict() |
|
165 | self.sql = dict() | |
168 | self.python = None |
|
166 | self.python = None | |
169 |
|
167 | |||
170 | for script in filelist: |
|
168 | for script in filelist: | |
171 | self.add_script(os.path.join(path, script)) |
|
169 | self.add_script(os.path.join(path, script)) | |
172 |
|
170 | |||
173 | def script(self, database=None, operation=None): |
|
171 | def script(self, database=None, operation=None): | |
174 | """Returns SQL or Python Script""" |
|
172 | """Returns SQL or Python Script""" | |
175 | for db in (database, 'default'): |
|
173 | for db in (database, 'default'): | |
176 | # Try to return a .sql script first |
|
174 | # Try to return a .sql script first | |
177 | try: |
|
175 | try: | |
178 | return self.sql[db][operation] |
|
176 | return self.sql[db][operation] | |
179 | except KeyError: |
|
177 | except KeyError: | |
180 | continue # No .sql script exists |
|
178 | continue # No .sql script exists | |
181 |
|
179 | |||
182 | # TODO: maybe add force Python parameter? |
|
180 | # TODO: maybe add force Python parameter? | |
183 | ret = self.python |
|
181 | ret = self.python | |
184 |
|
182 | |||
185 | assert ret is not None, \ |
|
183 | assert ret is not None, \ | |
186 | "There is no script for %d version" % self.version |
|
184 | "There is no script for %d version" % self.version | |
187 | return ret |
|
185 | return ret | |
188 |
|
186 | |||
189 | def add_script(self, path): |
|
187 | def add_script(self, path): | |
190 | """Add script to Collection/Version""" |
|
188 | """Add script to Collection/Version""" | |
191 | if path.endswith(Extensions.py): |
|
189 | if path.endswith(Extensions.py): | |
192 | self._add_script_py(path) |
|
190 | self._add_script_py(path) | |
193 | elif path.endswith(Extensions.sql): |
|
191 | elif path.endswith(Extensions.sql): | |
194 | self._add_script_sql(path) |
|
192 | self._add_script_sql(path) | |
195 |
|
193 | |||
196 | SQL_FILENAME = re.compile(r'^.*\.sql') |
|
194 | SQL_FILENAME = re.compile(r'^.*\.sql') | |
197 |
|
195 | |||
198 | def _add_script_sql(self, path): |
|
196 | def _add_script_sql(self, path): | |
199 | basename = os.path.basename(path) |
|
197 | basename = os.path.basename(path) | |
200 | match = self.SQL_FILENAME.match(basename) |
|
198 | match = self.SQL_FILENAME.match(basename) | |
201 |
|
199 | |||
202 | if match: |
|
200 | if match: | |
203 | basename = basename.replace('.sql', '') |
|
201 | basename = basename.replace('.sql', '') | |
204 | parts = basename.split('_') |
|
202 | parts = basename.split('_') | |
205 | if len(parts) < 3: |
|
203 | if len(parts) < 3: | |
206 | raise exceptions.ScriptError( |
|
204 | raise exceptions.ScriptError( | |
207 | "Invalid SQL script name %s " % basename + \ |
|
205 | "Invalid SQL script name %s " % basename + \ | |
208 | "(needs to be ###_description_database_operation.sql)") |
|
206 | "(needs to be ###_description_database_operation.sql)") | |
209 | version = parts[0] |
|
207 | version = parts[0] | |
210 | op = parts[-1] |
|
208 | op = parts[-1] | |
211 | dbms = parts[-2] |
|
209 | dbms = parts[-2] | |
212 | else: |
|
210 | else: | |
213 | raise exceptions.ScriptError( |
|
211 | raise exceptions.ScriptError( | |
214 | "Invalid SQL script name %s " % basename + \ |
|
212 | "Invalid SQL script name %s " % basename + \ | |
215 | "(needs to be ###_description_database_operation.sql)") |
|
213 | "(needs to be ###_description_database_operation.sql)") | |
216 |
|
214 | |||
217 | # File the script into a dictionary |
|
215 | # File the script into a dictionary | |
218 | self.sql.setdefault(dbms, {})[op] = script.SqlScript(path) |
|
216 | self.sql.setdefault(dbms, {})[op] = script.SqlScript(path) | |
219 |
|
217 | |||
220 | def _add_script_py(self, path): |
|
218 | def _add_script_py(self, path): | |
221 | if self.python is not None: |
|
219 | if self.python is not None: | |
222 | raise exceptions.ScriptError('You can only have one Python script ' |
|
220 | raise exceptions.ScriptError('You can only have one Python script ' | |
223 | 'per version, but you have: %s and %s' % (self.python, path)) |
|
221 | 'per version, but you have: %s and %s' % (self.python, path)) | |
224 | self.python = script.PythonScript(path) |
|
222 | self.python = script.PythonScript(path) | |
225 |
|
223 | |||
226 |
|
224 | |||
227 | class Extensions: |
|
225 | class Extensions: | |
228 | """A namespace for file extensions""" |
|
226 | """A namespace for file extensions""" | |
229 | py = 'py' |
|
227 | py = 'py' | |
230 | sql = 'sql' |
|
228 | sql = 'sql' | |
231 |
|
229 | |||
232 | def str_to_filename(s): |
|
230 | def str_to_filename(s): | |
233 | """Replaces spaces, (double and single) quotes |
|
231 | """Replaces spaces, (double and single) quotes | |
234 | and double underscores to underscores |
|
232 | and double underscores to underscores | |
235 | """ |
|
233 | """ | |
236 |
|
234 | |||
237 | s = s.replace(' ', '_').replace('"', '_').replace("'", '_').replace(".", "_") |
|
235 | s = s.replace(' ', '_').replace('"', '_').replace("'", '_').replace(".", "_") | |
238 | while '__' in s: |
|
236 | while '__' in s: | |
239 | s = s.replace('__', '_') |
|
237 | s = s.replace('__', '_') | |
240 | return s |
|
238 | return s |
@@ -1,119 +1,119 b'' | |||||
1 | import logging |
|
1 | import logging | |
2 | import datetime |
|
2 | import datetime | |
3 |
|
3 | |||
4 | from sqlalchemy import * |
|
4 | from sqlalchemy import * | |
5 | from sqlalchemy.exc import DatabaseError |
|
5 | from sqlalchemy.exc import DatabaseError | |
6 | from sqlalchemy.orm import relation, backref, class_mapper |
|
6 | from sqlalchemy.orm import relation, backref, class_mapper | |
7 | from sqlalchemy.orm.session import Session |
|
7 | from sqlalchemy.orm.session import Session | |
8 |
|
8 | |||
9 | from rhodecode.lib.dbmigrate.migrate import * |
|
9 | from rhodecode.lib.dbmigrate.migrate import * | |
10 | from rhodecode.lib.dbmigrate.migrate.changeset import * |
|
10 | from rhodecode.lib.dbmigrate.migrate.changeset import * | |
11 |
|
11 | |||
12 | from rhodecode.model.meta import Base |
|
12 | from rhodecode.model.meta import Base | |
13 |
|
13 | |||
14 | log = logging.getLogger(__name__) |
|
14 | log = logging.getLogger(__name__) | |
15 |
|
15 | |||
16 | def upgrade(migrate_engine): |
|
16 | def upgrade(migrate_engine): | |
17 | """ Upgrade operations go here. |
|
17 | """ Upgrade operations go here. | |
18 | Don't create your own engine; bind migrate_engine to your metadata |
|
18 | Don't create your own engine; bind migrate_engine to your metadata | |
19 | """ |
|
19 | """ | |
20 |
|
20 | |||
21 | #========================================================================== |
|
21 | #========================================================================== | |
22 | # Add table `groups`` |
|
22 | # Add table `groups`` | |
23 | #========================================================================== |
|
23 | #========================================================================== | |
24 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import Group |
|
24 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import RepoGroup as Group | |
25 | Group().__table__.create() |
|
25 | Group().__table__.create() | |
26 |
|
26 | |||
27 | #========================================================================== |
|
27 | #========================================================================== | |
28 | # Add table `group_to_perm` |
|
28 | # Add table `group_to_perm` | |
29 | #========================================================================== |
|
29 | #========================================================================== | |
30 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserRepoGroupToPerm |
|
30 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserRepoGroupToPerm | |
31 | UserRepoGroupToPerm().__table__.create() |
|
31 | UserRepoGroupToPerm().__table__.create() | |
32 |
|
32 | |||
33 | #========================================================================== |
|
33 | #========================================================================== | |
34 | # Add table `users_groups` |
|
34 | # Add table `users_groups` | |
35 | #========================================================================== |
|
35 | #========================================================================== | |
36 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroup |
|
36 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroup | |
37 | UsersGroup().__table__.create() |
|
37 | UsersGroup().__table__.create() | |
38 |
|
38 | |||
39 | #========================================================================== |
|
39 | #========================================================================== | |
40 | # Add table `users_groups_members` |
|
40 | # Add table `users_groups_members` | |
41 | #========================================================================== |
|
41 | #========================================================================== | |
42 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupMember |
|
42 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupMember | |
43 | UsersGroupMember().__table__.create() |
|
43 | UsersGroupMember().__table__.create() | |
44 |
|
44 | |||
45 | #========================================================================== |
|
45 | #========================================================================== | |
46 | # Add table `users_group_repo_to_perm` |
|
46 | # Add table `users_group_repo_to_perm` | |
47 | #========================================================================== |
|
47 | #========================================================================== | |
48 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupRepoToPerm |
|
48 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupRepoToPerm | |
49 | UsersGroupRepoToPerm().__table__.create() |
|
49 | UsersGroupRepoToPerm().__table__.create() | |
50 |
|
50 | |||
51 | #========================================================================== |
|
51 | #========================================================================== | |
52 | # Add table `users_group_to_perm` |
|
52 | # Add table `users_group_to_perm` | |
53 | #========================================================================== |
|
53 | #========================================================================== | |
54 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupToPerm |
|
54 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupToPerm | |
55 | UsersGroupToPerm().__table__.create() |
|
55 | UsersGroupToPerm().__table__.create() | |
56 |
|
56 | |||
57 | #========================================================================== |
|
57 | #========================================================================== | |
58 | # Upgrade of `users` table |
|
58 | # Upgrade of `users` table | |
59 | #========================================================================== |
|
59 | #========================================================================== | |
60 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import User |
|
60 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import User | |
61 |
|
61 | |||
62 | #add column |
|
62 | #add column | |
63 | ldap_dn = Column("ldap_dn", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
63 | ldap_dn = Column("ldap_dn", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
64 | ldap_dn.create(User().__table__) |
|
64 | ldap_dn.create(User().__table__) | |
65 |
|
65 | |||
66 | api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
66 | api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
67 | api_key.create(User().__table__) |
|
67 | api_key.create(User().__table__) | |
68 |
|
68 | |||
69 | #remove old column |
|
69 | #remove old column | |
70 | is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False) |
|
70 | is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False) | |
71 | is_ldap.drop(User().__table__) |
|
71 | is_ldap.drop(User().__table__) | |
72 |
|
72 | |||
73 |
|
73 | |||
74 | #========================================================================== |
|
74 | #========================================================================== | |
75 | # Upgrade of `repositories` table |
|
75 | # Upgrade of `repositories` table | |
76 | #========================================================================== |
|
76 | #========================================================================== | |
77 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import Repository |
|
77 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import Repository | |
78 |
|
78 | |||
79 | #ADD clone_uri column# |
|
79 | #ADD clone_uri column# | |
80 |
|
80 | |||
81 | clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, |
|
81 | clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, | |
82 | assert_unicode=None), |
|
82 | assert_unicode=None), | |
83 | nullable=True, unique=False, default=None) |
|
83 | nullable=True, unique=False, default=None) | |
84 |
|
84 | |||
85 | clone_uri.create(Repository().__table__) |
|
85 | clone_uri.create(Repository().__table__) | |
86 |
|
86 | |||
87 | #ADD downloads column# |
|
87 | #ADD downloads column# | |
88 | enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) |
|
88 | enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) | |
89 | enable_downloads.create(Repository().__table__) |
|
89 | enable_downloads.create(Repository().__table__) | |
90 |
|
90 | |||
91 | #ADD column created_on |
|
91 | #ADD column created_on | |
92 | created_on = Column('created_on', DateTime(timezone=False), nullable=True, |
|
92 | created_on = Column('created_on', DateTime(timezone=False), nullable=True, | |
93 | unique=None, default=datetime.datetime.now) |
|
93 | unique=None, default=datetime.datetime.now) | |
94 | created_on.create(Repository().__table__) |
|
94 | created_on.create(Repository().__table__) | |
95 |
|
95 | |||
96 | #ADD group_id column# |
|
96 | #ADD group_id column# | |
97 | group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), |
|
97 | group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), | |
98 | nullable=True, unique=False, default=None) |
|
98 | nullable=True, unique=False, default=None) | |
99 |
|
99 | |||
100 | group_id.create(Repository().__table__) |
|
100 | group_id.create(Repository().__table__) | |
101 |
|
101 | |||
102 |
|
102 | |||
103 | #========================================================================== |
|
103 | #========================================================================== | |
104 | # Upgrade of `user_followings` table |
|
104 | # Upgrade of `user_followings` table | |
105 | #========================================================================== |
|
105 | #========================================================================== | |
106 |
|
106 | |||
107 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserFollowing |
|
107 | from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserFollowing | |
108 |
|
108 | |||
109 | follows_from = Column('follows_from', DateTime(timezone=False), |
|
109 | follows_from = Column('follows_from', DateTime(timezone=False), | |
110 | nullable=True, unique=None, |
|
110 | nullable=True, unique=None, | |
111 | default=datetime.datetime.now) |
|
111 | default=datetime.datetime.now) | |
112 | follows_from.create(UserFollowing().__table__) |
|
112 | follows_from.create(UserFollowing().__table__) | |
113 |
|
113 | |||
114 | return |
|
114 | return | |
115 |
|
115 | |||
116 |
|
116 | |||
117 | def downgrade(migrate_engine): |
|
117 | def downgrade(migrate_engine): | |
118 | meta = MetaData() |
|
118 | meta = MetaData() | |
119 | meta.bind = migrate_engine |
|
119 | meta.bind = migrate_engine |
General Comments 0
You need to be logged in to leave comments.
Login now