Changeset - 9bb609d164e4
[Not reviewed]
rhodecode/lib/dbmigrate/migrate/changeset/constraint.py
Show inline comments
 
@@ -19,25 +19,25 @@ class ConstraintChangeset(object):
 
                if col.table is not None and table is None:
 
                    table = col.table
 
                if table_name:
 
                    col = '.'.join((col.table.name, col.name))
 
                else:
 
                    col = col.name
 
            colnames.append(col)
 
        return colnames, table
 

	
 
    def __do_imports(self, visitor_name, *a, **kw):
 
        engine = kw.pop('engine', self.table.bind)
 
        from rhodecode.lib.dbmigrate.migrate.changeset.databases.visitor import (get_engine_visitor,
 
                                                         run_single_visitor)
 
                                                                                 run_single_visitor)
 
        visitorcallable = get_engine_visitor(engine, visitor_name)
 
        run_single_visitor(engine, visitorcallable, self, *a, **kw)
 

	
 
    def create(self, *a, **kw):
 
        """Create the constraint in the database.
 

	
 
        :param engine: the database engine to use. If this is \
 
        :keyword:`None` the instance's engine will be used
 
        :type engine: :class:`sqlalchemy.engine.base.Engine`
 
        :param connection: reuse connection istead of creating new one.
 
        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
 
        """
rhodecode/lib/dbmigrate/migrate/changeset/databases/firebird.py
Show inline comments
 
"""
 
   Firebird database specific implementations of changeset classes.
 
"""
 
from sqlalchemy.databases import firebird as sa_base
 

	
 
from sqlalchemy.schema import PrimaryKeyConstraint
 
from rhodecode.lib.dbmigrate.migrate import exceptions
 
from rhodecode.lib.dbmigrate.migrate.changeset import ansisql, SQLA_06
 

	
 

	
 
if SQLA_06:
 
    FBSchemaGenerator = sa_base.FBDDLCompiler
 
else:
 
    FBSchemaGenerator = sa_base.FBSchemaGenerator
 

	
 
class FBColumnGenerator(FBSchemaGenerator, ansisql.ANSIColumnGenerator):
 
    """Firebird column generator implementation."""
 

	
 

	
 
class FBColumnDropper(ansisql.ANSIColumnDropper):
 
    """Firebird column dropper implementation."""
 

	
 
    def visit_column(self, column):
 
        """Firebird supports 'DROP col' instead of 'DROP COLUMN col' syntax
 

	
 
        Drop primary key and unique constraints if dropped column is referencing it."""
 
        if column.primary_key:
 
            if column.table.primary_key.columns.contains_column(column):
 
                column.table.primary_key.drop()
 
                # TODO: recreate primary key if it references more than this column
 
        if column.unique or getattr(column, 'unique_name', None):
 
            for cons in column.table.constraints:
 
                if cons.contains_column(column):
 
                    cons.drop()
 
                    # TODO: recreate unique constraint if it refenrences more than this column
 

	
 
        table = self.start_alter_table(column)
 
        for index in column.table.indexes:
 
            # "column in index.columns" causes problems as all
 
            # column objects compare equal and return a SQL expression
 
            if column.name in [col.name for col in index.columns]:
 
                index.drop()
 
                # TODO: recreate index if it references more than this column
 
        
 
        for cons in column.table.constraints:
 
            if isinstance(cons,PrimaryKeyConstraint):
 
                # will be deleted only when the column its on
 
                # is deleted!
 
                continue
 

	
 
            if SQLA_06:
 
                should_drop = column.name in cons.columns
 
            else:
 
                should_drop = cons.contains_column(column) and cons.name
 
            if should_drop:
 
                self.start_alter_table(column)
 
                self.append("DROP CONSTRAINT ")
 
                self.append(self.preparer.format_constraint(cons))
 
                self.execute()
 
            # TODO: recreate unique constraint if it refenrences more than this column
 

	
 
        self.start_alter_table(column)
 
        self.append('DROP %s' % self.preparer.format_column(column))
 
        self.execute()
 

	
 

	
 
class FBSchemaChanger(ansisql.ANSISchemaChanger):
 
    """Firebird schema changer implementation."""
 

	
 
    def visit_table(self, table):
 
        """Rename table not supported"""
 
        raise exceptions.NotSupportedError(
 
            "Firebird does not support renaming tables.")
 

	
rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py
Show inline comments
 
@@ -71,28 +71,35 @@ class SQLiteColumnGenerator(SQLiteSchema
 
                'SELECT %(cols)s from migration_tmp')%{'cols':columns}
 

	
 
    def visit_column(self,column):
 
        if column.foreign_keys:
 
            SQLiteHelper.visit_column(self,column)
 
        else:
 
            super(SQLiteColumnGenerator,self).visit_column(column)
 

	
 
class SQLiteColumnDropper(SQLiteHelper, ansisql.ANSIColumnDropper):
 
    """SQLite ColumnDropper"""
 

	
 
    def _modify_table(self, table, column, delta):
 
        
 
        columns = ' ,'.join(map(self.preparer.format_column, table.columns))
 
        return 'INSERT INTO %(table_name)s SELECT ' + columns + \
 
            ' from migration_tmp'
 

	
 
    def visit_column(self,column):
 
        # For SQLite, we *have* to remove the column here so the table
 
        # is re-created properly.
 
        column.remove_from_table(column.table,unset_table=False)
 
        super(SQLiteColumnDropper,self).visit_column(column)
 

	
 

	
 
class SQLiteSchemaChanger(SQLiteHelper, ansisql.ANSISchemaChanger):
 
    """SQLite SchemaChanger"""
 

	
 
    def _modify_table(self, table, column, delta):
 
        return 'INSERT INTO %(table_name)s SELECT * from migration_tmp'
 

	
 
    def visit_index(self, index):
 
        """Does not support ALTER INDEX"""
 
        self._not_supported('ALTER INDEX')
 

	
 

	
rhodecode/lib/dbmigrate/migrate/changeset/schema.py
Show inline comments
 
@@ -20,27 +20,24 @@ __all__ = [
 
    'create_column',
 
    'drop_column',
 
    'alter_column',
 
    'rename_table',
 
    'rename_index',
 
    'ChangesetTable',
 
    'ChangesetColumn',
 
    'ChangesetIndex',
 
    'ChangesetDefaultClause',
 
    'ColumnDelta',
 
]
 

	
 
DEFAULT_ALTER_METADATA = True
 

	
 

	
 
def create_column(column, table=None, *p, **kw):
 
    """Create a column, given the table.
 
    
 
    API to :meth:`ChangesetColumn.create`.
 
    """
 
    if table is not None:
 
        return table.create_column(column, *p, **kw)
 
    return column.create(*p, **kw)
 

	
 

	
 
def drop_column(column, table=None, *p, **kw):
 
    """Drop a column, given the table.
 
@@ -100,50 +97,48 @@ def alter_column(*p, **k):
 
    :argument column:
 
      The name of the column to be altered or a
 
      :class:`ChangesetColumn` column representing it.
 

	
 
    :param table:
 
      A :class:`~sqlalchemy.schema.Table` or table name to
 
      for the table where the column will be changed.
 

	
 
    :param engine:
 
      The :class:`~sqlalchemy.engine.base.Engine` to use for table
 
      reflection and schema alterations.
 
    
 
    :param alter_metadata:
 
      If `True`, which is the default, the
 
      :class:`~sqlalchemy.schema.Column` will also modified.
 
      If `False`, the :class:`~sqlalchemy.schema.Column` will be left
 
      as it was.
 
    
 
    :returns: A :class:`ColumnDelta` instance representing the change.
 

	
 
    
 
    """
 

	
 
    k.setdefault('alter_metadata', DEFAULT_ALTER_METADATA)
 

	
 
    
 
    if 'table' not in k and isinstance(p[0], sqlalchemy.Column):
 
        k['table'] = p[0].table
 
    if 'engine' not in k:
 
        k['engine'] = k['table'].bind
 

	
 
    # deprecation
 
    if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
 
        warnings.warn(
 
            "Passing a Column object to alter_column is deprecated."
 
            " Just pass in keyword parameters instead.",
 
            MigrateDeprecationWarning
 
            )
 
    engine = k['engine']
 

	
 
    # enough tests seem to break when metadata is always altered
 
    # that this crutch has to be left in until they can be sorted
 
    # out
 
    k['alter_metadata']=True
 
    
 
    delta = ColumnDelta(*p, **k)
 

	
 
    visitorcallable = get_engine_visitor(engine, 'schemachanger')
 
    engine._run_visitor(visitorcallable, delta)
 

	
 
    return delta
 

	
 

	
 
def _to_table(table, engine=None):
 
    """Return if instance of Table, else construct new with metadata"""
 
    if isinstance(table, sqlalchemy.Table):
 
        return table
 
@@ -179,84 +174,89 @@ class ColumnDelta(DictMixin, sqlalchemy.
 
        * **current_column, \*p, \*\*kw**
 
            Additional parameters alter current_column. Table name is extracted
 
            from current_column object.
 
            Name is changed to current_column.name from current_name,
 
            if current_name is specified.
 

	
 
        * **current_col_name, \*p, \*\*kw**
 
            Table kw must specified.
 

	
 
        :param table: Table at which current Column should be bound to.\
 
        If table name is given, reflection will be used.
 
        :type table: string or Table instance
 
        :param alter_metadata: If True, it will apply changes to metadata.
 
        :type alter_metadata: bool
 
        :param metadata: If `alter_metadata` is true, \
 
        metadata is used to reflect table names into
 
        :type metadata: :class:`MetaData` instance
 
        
 
        :param metadata: A :class:`MetaData` instance to store
 
                         reflected table names
 
                         
 
        :param engine: When reflecting tables, either engine or metadata must \
 
        be specified to acquire engine object.
 
        :type engine: :class:`Engine` instance
 
        :returns: :class:`ColumnDelta` instance provides interface for altered attributes to \
 
        `result_column` through :func:`dict` alike object.
 

	
 
        * :class:`ColumnDelta`.result_column is altered column with new attributes
 

	
 
        * :class:`ColumnDelta`.current_name is current name of column in db
 

	
 

	
 
    """
 

	
 
    # Column attributes that can be altered
 
    diff_keys = ('name', 'type', 'primary_key', 'nullable',
 
        'server_onupdate', 'server_default', 'autoincrement')
 
    diffs = dict()
 
    __visit_name__ = 'column'
 

	
 
    def __init__(self, *p, **kw):
 
        # 'alter_metadata' is not a public api. It exists purely
 
        # as a crutch until the tests that fail when 'alter_metadata'
 
        # behaviour always happens can be sorted out
 
        self.alter_metadata = kw.pop("alter_metadata", False)
 
        
 
        self.meta = kw.pop("metadata", None)
 
        self.engine = kw.pop("engine", None)
 

	
 
        # Things are initialized differently depending on how many column
 
        # parameters are given. Figure out how many and call the appropriate
 
        # method.
 
        if len(p) >= 1 and isinstance(p[0], sqlalchemy.Column):
 
            # At least one column specified
 
            if len(p) >= 2 and isinstance(p[1], sqlalchemy.Column):
 
                # Two columns specified
 
                diffs = self.compare_2_columns(*p, **kw)
 
            else:
 
                # Exactly one column specified
 
                diffs = self.compare_1_column(*p, **kw)
 
        else:
 
            # Zero columns specified
 
            if not len(p) or not isinstance(p[0], basestring):
 
                raise ValueError("First argument must be column name")
 
            diffs = self.compare_parameters(*p, **kw)
 

	
 
        self.apply_diffs(diffs)
 

	
 
    def __repr__(self):
 
        return '<ColumnDelta altermetadata=%r, %s>' % (self.alter_metadata,
 
            super(ColumnDelta, self).__repr__())
 

	
 
        return '<ColumnDelta altermetadata=%r, %s>' % (
 
            self.alter_metadata,
 
            super(ColumnDelta, self).__repr__()
 
            )
 
    
 
    def __getitem__(self, key):
 
        if key not in self.keys():
 
            raise KeyError("No such diff key, available: %s" % self.diffs)
 
            raise KeyError("No such diff key, available: %s" % self.diffs )
 
        return getattr(self.result_column, key)
 

	
 
    def __setitem__(self, key, value):
 
        if key not in self.keys():
 
            raise KeyError("No such diff key, available: %s" % self.diffs)
 
            raise KeyError("No such diff key, available: %s" % self.diffs )
 
        setattr(self.result_column, key, value)
 

	
 
    def __delitem__(self, key):
 
        raise NotImplementedError
 

	
 
    def keys(self):
 
        return self.diffs.keys()
 

	
 
    def compare_parameters(self, current_name, *p, **k):
 
        """Compares Column objects with reflection"""
 
        self.table = k.pop('table')
 
        self.result_column = self._table.c.get(current_name)
 
@@ -358,25 +358,25 @@ class ColumnDelta(DictMixin, sqlalchemy.
 
            if isinstance(column.server_default, sqlalchemy.FetchedValue):
 
                toinit.append(column.server_default)
 
            else:
 
                toinit.append(sqlalchemy.DefaultClause(column.server_default))
 
        if column.server_onupdate is not None:
 
            if isinstance(column.server_onupdate, FetchedValue):
 
                toinit.append(column.server_default)
 
            else:
 
                toinit.append(sqlalchemy.DefaultClause(column.server_onupdate,
 
                                            for_update=True))
 
        if toinit:
 
            column._init_items(*toinit)
 

	
 
            
 
        if not SQLA_06:
 
            column.args = []
 

	
 
    def _get_table(self):
 
        return getattr(self, '_table', None)
 

	
 
    def _set_table(self, table):
 
        if isinstance(table, basestring):
 
            if self.alter_metadata:
 
                if not self.meta:
 
                    raise ValueError("metadata must be specified for table"
 
                        " reflection when using alter_metadata")
 
@@ -386,25 +386,24 @@ class ColumnDelta(DictMixin, sqlalchemy.
 
            else:
 
                if not self.engine and not self.meta:
 
                    raise ValueError("engine or metadata must be specified"
 
                        " to reflect tables")
 
                if not self.engine:
 
                    self.engine = self.meta.bind
 
                meta = sqlalchemy.MetaData(bind=self.engine)
 
            self._table = sqlalchemy.Table(table, meta, autoload=True)
 
        elif isinstance(table, sqlalchemy.Table):
 
            self._table = table
 
            if not self.alter_metadata:
 
                self._table.meta = sqlalchemy.MetaData(bind=self._table.bind)
 

	
 
    def _get_result_column(self):
 
        return getattr(self, '_result_column', None)
 

	
 
    def _set_result_column(self, column):
 
        """Set Column to Table based on alter_metadata evaluation."""
 
        self.process_column(column)
 
        if not hasattr(self, 'current_name'):
 
            self.current_name = column.name
 
        if self.alter_metadata:
 
            self._result_column = column
 
        else:
 
            self._result_column = column.copy_fixed()
 
@@ -447,40 +446,36 @@ class ChangesetTable(object):
 
            except AttributeError:
 
                # That column isn't part of the table. We don't need
 
                # its entire definition to drop the column, just its
 
                # name, so create a dummy column with the same name.
 
                column = sqlalchemy.Column(str(column), sqlalchemy.Integer())
 
        column.drop(table=self, *p, **kw)
 

	
 
    def rename(self, name, connection=None, **kwargs):
 
        """Rename this table.
 

	
 
        :param name: New name of the table.
 
        :type name: string
 
        :param alter_metadata: If True, table will be removed from metadata
 
        :type alter_metadata: bool
 
        :param connection: reuse connection istead of creating new one.
 
        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
 
        """
 
        self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA)
 
        engine = self.bind
 
        self.new_name = name
 
        visitorcallable = get_engine_visitor(engine, 'schemachanger')
 
        run_single_visitor(engine, visitorcallable, self, connection, **kwargs)
 

	
 
        # Fix metadata registration
 
        if self.alter_metadata:
 
            self.name = name
 
            self.deregister()
 
            self._set_parent(self.metadata)
 
        self.name = name
 
        self.deregister()
 
        self._set_parent(self.metadata)
 

	
 
    def _meta_key(self):
 
        return sqlalchemy.schema._get_table_key(self.name, self.schema)
 

	
 
    def deregister(self):
 
        """Remove this table from its metadata"""
 
        key = self._meta_key()
 
        meta = self.metadata
 
        if key in meta.tables:
 
            del meta.tables[key]
 

	
 

	
 
@@ -501,117 +496,108 @@ class ChangesetColumn(object):
 
               primary_key_name=None, populate_default=True, connection=None, **kwargs):
 
        """Create this column in the database.
 

	
 
        Assumes the given table exists. ``ALTER TABLE ADD COLUMN``,
 
        for most databases.
 

	
 
        :param table: Table instance to create on.
 
        :param index_name: Creates :class:`ChangesetIndex` on this column.
 
        :param unique_name: Creates :class:\
 
`~migrate.changeset.constraint.UniqueConstraint` on this column.
 
        :param primary_key_name: Creates :class:\
 
`~migrate.changeset.constraint.PrimaryKeyConstraint` on this column.
 
        :param alter_metadata: If True, column will be added to table object.
 
        :param populate_default: If True, created column will be \
 
populated with defaults
 
        :param connection: reuse connection istead of creating new one.
 
        :type table: Table instance
 
        :type index_name: string
 
        :type unique_name: string
 
        :type primary_key_name: string
 
        :type alter_metadata: bool
 
        :type populate_default: bool
 
        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
 

	
 
        :returns: self
 
        """
 
        self.populate_default = populate_default
 
        self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA)
 
        self.index_name = index_name
 
        self.unique_name = unique_name
 
        self.primary_key_name = primary_key_name
 
        for cons in ('index_name', 'unique_name', 'primary_key_name'):
 
            self._check_sanity_constraints(cons)
 

	
 
        if self.alter_metadata:
 
            self.add_to_table(table)
 
        self.add_to_table(table)
 
        engine = self.table.bind
 
        visitorcallable = get_engine_visitor(engine, 'columngenerator')
 
        engine._run_visitor(visitorcallable, self, connection, **kwargs)
 

	
 
        # TODO: reuse existing connection
 
        if self.populate_default and self.default is not None:
 
            stmt = table.update().values({self: engine._execute_default(self.default)})
 
            engine.execute(stmt)
 

	
 
        return self
 

	
 
    def drop(self, table=None, connection=None, **kwargs):
 
        """Drop this column from the database, leaving its table intact.
 

	
 
        ``ALTER TABLE DROP COLUMN``, for most databases.
 

	
 
        :param alter_metadata: If True, column will be removed from table object.
 
        :type alter_metadata: bool
 
        :param connection: reuse connection istead of creating new one.
 
        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
 
        """
 
        self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA)
 
        if table is not None:
 
            self.table = table
 
        engine = self.table.bind
 
        if self.alter_metadata:
 
            self.remove_from_table(self.table, unset_table=False)
 
        visitorcallable = get_engine_visitor(engine, 'columndropper')
 
        engine._run_visitor(visitorcallable, self, connection, **kwargs)
 
        if self.alter_metadata:
 
            self.table = None
 
        self.remove_from_table(self.table, unset_table=False)
 
        self.table = None
 
        return self
 

	
 
    def add_to_table(self, table):
 
        if table is not None  and self.table is None:
 
            self._set_parent(table)
 

	
 
    def _col_name_in_constraint(self, cons, name):
 
    def _col_name_in_constraint(self,cons,name):
 
        return False
 

	
 
    
 
    def remove_from_table(self, table, unset_table=True):
 
        # TODO: remove primary keys, constraints, etc
 
        if unset_table:
 
            self.table = None
 

	
 
            
 
        to_drop = set()
 
        for index in table.indexes:
 
            columns = []
 
            for col in index.columns:
 
                if col.name != self.name:
 
                if col.name!=self.name:
 
                    columns.append(col)
 
            if columns:
 
                index.columns = columns
 
                index.columns=columns
 
            else:
 
                to_drop.add(index)
 
        table.indexes = table.indexes - to_drop
 

	
 
        
 
        to_drop = set()
 
        for cons in table.constraints:
 
            # TODO: deal with other types of constraint
 
            if isinstance(cons, (ForeignKeyConstraint,
 
            if isinstance(cons,(ForeignKeyConstraint,
 
                                UniqueConstraint)):
 
                for col_name in cons.columns:
 
                    if not isinstance(col_name, basestring):
 
                    if not isinstance(col_name,basestring):
 
                        col_name = col_name.name
 
                    if self.name == col_name:
 
                    if self.name==col_name:
 
                        to_drop.add(cons)
 
        table.constraints = table.constraints - to_drop
 

	
 
        
 
        if table.c.contains_column(self):
 
            table.c.remove(self)
 

	
 
    # TODO: this is fixed in 0.6
 
    def copy_fixed(self, **kw):
 
        """Create a copy of this ``Column``, with all attributes."""
 
        return sqlalchemy.Column(self.name, self.type, self.default,
 
            key=self.key,
 
            primary_key=self.primary_key,
 
            nullable=self.nullable,
 
            quote=self.quote,
 
            index=self.index,
 
@@ -634,36 +620,32 @@ populated with defaults
 

	
 

	
 
class ChangesetIndex(object):
 
    """Changeset extensions to SQLAlchemy Indexes."""
 

	
 
    __visit_name__ = 'index'
 

	
 
    def rename(self, name, connection=None, **kwargs):
 
        """Change the name of an index.
 

	
 
        :param name: New name of the Index.
 
        :type name: string
 
        :param alter_metadata: If True, Index object will be altered.
 
        :type alter_metadata: bool
 
        :param connection: reuse connection istead of creating new one.
 
        :type connection: :class:`sqlalchemy.engine.base.Connection` instance
 
        """
 
        self.alter_metadata = kwargs.pop('alter_metadata', DEFAULT_ALTER_METADATA)
 
        engine = self.table.bind
 
        self.new_name = name
 
        visitorcallable = get_engine_visitor(engine, 'schemachanger')
 
        engine._run_visitor(visitorcallable, self, connection, **kwargs)
 
        if self.alter_metadata:
 
            self.name = name
 
        self.name = name
 

	
 

	
 
class ChangesetDefaultClause(object):
 
    """Implements comparison between :class:`DefaultClause` instances"""
 

	
 
    def __eq__(self, other):
 
        if isinstance(other, self.__class__):
 
            if self.arg == other.arg:
 
                return True
 

	
 
    def __ne__(self, other):
 
        return not self.__eq__(other)
rhodecode/lib/dbmigrate/migrate/versioning/genmodel.py
Show inline comments
 
@@ -102,54 +102,54 @@ class ModelGenerator(object):
 
            out.append("class %(table)s(Base):" % {'table': tableName})
 
            out.append("  __tablename__ = '%(table)s'" % {'table': tableName})
 
            for col in table.columns:
 
                out.append("  %s" % self.column_repr(col))
 
        else:
 
            out.append("%(table)s = Table('%(table)s', meta," % \
 
                           {'table': tableName})
 
            for col in table.columns:
 
                out.append("  %s," % self.column_repr(col))
 
            out.append(")")
 
        return out
 

	
 
    def _get_tables(self, missingA=False, missingB=False, modified=False):
 
    def _get_tables(self,missingA=False,missingB=False,modified=False):
 
        to_process = []
 
        for bool_, names, metadata in (
 
            (missingA, self.diff.tables_missing_from_A, self.diff.metadataB),
 
            (missingB, self.diff.tables_missing_from_B, self.diff.metadataA),
 
            (modified, self.diff.tables_different, self.diff.metadataA),
 
        for bool_,names,metadata in (
 
            (missingA,self.diff.tables_missing_from_A,self.diff.metadataB),
 
            (missingB,self.diff.tables_missing_from_B,self.diff.metadataA),
 
            (modified,self.diff.tables_different,self.diff.metadataA),
 
                ):
 
            if bool_:
 
                for name in names:
 
                    yield metadata.tables.get(name)
 

	
 
    def toPython(self):
 
        """Assume database is current and model is empty."""
 
        out = []
 
        if self.declarative:
 
            out.append(DECLARATIVE_HEADER)
 
        else:
 
            out.append(HEADER)
 
        out.append("")
 
        for table in self._get_tables(missingA=True):
 
            out.extend(self.getTableDefn(table))
 
            out.append("")
 
        return '\n'.join(out)
 

	
 
    def toUpgradeDowngradePython(self, indent='    '):
 
        ''' Assume model is most current and database is out-of-date. '''
 
        decls = ['from rhodecode.lib.dbmigrate.migrate.changeset import schema',
 
                 'meta = MetaData()']
 
        for table in self._get_tables(
 
            missingA=True, missingB=True, modified=True
 
            missingA=True,missingB=True,modified=True
 
            ):
 
            decls.extend(self.getTableDefn(table))
 

	
 
        upgradeCommands, downgradeCommands = [], []
 
        for tableName in self.diff.tables_missing_from_A:
 
            upgradeCommands.append("%(table)s.drop()" % {'table': tableName})
 
            downgradeCommands.append("%(table)s.create()" % \
 
                                         {'table': tableName})
 
        for tableName in self.diff.tables_missing_from_B:
 
            upgradeCommands.append("%(table)s.create()" % {'table': tableName})
 
            downgradeCommands.append("%(table)s.drop()" % {'table': tableName})
 

	
 
@@ -160,37 +160,37 @@ class ModelGenerator(object):
 
            for col in missingInDatabase:
 
                upgradeCommands.append('%s.columns[%r].create()' % (
 
                        modelTable, col.name))
 
                downgradeCommands.append('%s.columns[%r].drop()' % (
 
                        modelTable, col.name))
 
            for col in missingInModel:
 
                upgradeCommands.append('%s.columns[%r].drop()' % (
 
                        modelTable, col.name))
 
                downgradeCommands.append('%s.columns[%r].create()' % (
 
                        modelTable, col.name))
 
            for modelCol, databaseCol, modelDecl, databaseDecl in diffDecl:
 
                upgradeCommands.append(
 
                    'assert False, "Can\'t alter columns: %s:%s=>%s"',
 
                    modelTable, modelCol.name, databaseCol.name)
 
                    'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
 
                    modelTable, modelCol.name, databaseCol.name))
 
                downgradeCommands.append(
 
                    'assert False, "Can\'t alter columns: %s:%s=>%s"',
 
                    modelTable, modelCol.name, databaseCol.name)
 
                    'assert False, "Can\'t alter columns: %s:%s=>%s"' % (
 
                    modelTable, modelCol.name, databaseCol.name))
 
        pre_command = '    meta.bind = migrate_engine'
 

	
 
        return (
 
            '\n'.join(decls),
 
            '\n'.join([pre_command] + ['%s%s' % (indent, line) for line in upgradeCommands]),
 
            '\n'.join([pre_command] + ['%s%s' % (indent, line) for line in downgradeCommands]))
 

	
 
    def _db_can_handle_this_change(self, td):
 
    def _db_can_handle_this_change(self,td):
 
        if (td.columns_missing_from_B
 
            and not td.columns_missing_from_A
 
            and not td.columns_different):
 
            # Even sqlite can handle this.
 
            return True
 
        else:
 
            return not self.engine.url.drivername.startswith('sqlite')
 

	
 
    def applyModel(self):
 
        """Apply model to current database."""
 

	
 
        meta = sqlalchemy.MetaData(self.engine)
 
@@ -198,27 +198,27 @@ class ModelGenerator(object):
 
        for table in self._get_tables(missingA=True):
 
            table = table.tometadata(meta)
 
            table.drop()
 
        for table in self._get_tables(missingB=True):
 
            table = table.tometadata(meta)
 
            table.create()
 
        for modelTable in self._get_tables(modified=True):
 
            tableName = modelTable.name
 
            modelTable = modelTable.tometadata(meta)
 
            dbTable = self.diff.metadataB.tables[tableName]
 

	
 
            td = self.diff.tables_different[tableName]
 

	
 
            
 
            if self._db_can_handle_this_change(td):
 

	
 
                
 
                for col in td.columns_missing_from_B:
 
                    modelTable.columns[col].create()
 
                for col in td.columns_missing_from_A:
 
                    dbTable.columns[col].drop()
 
                # XXX handle column changes here.
 
            else:
 
                # Sqlite doesn't support drop column, so you have to
 
                # do more: create temp table, copy data to it, drop
 
                # old table, create new table, copy data back.
 
                #
 
                # I wonder if this is guaranteed to be unique?
 
                tempName = '_temp_%s' % modelTable.name
rhodecode/lib/dbmigrate/migrate/versioning/script/py.py
Show inline comments
 
#!/usr/bin/env python
 
# -*- coding: utf-8 -*-
 

	
 
import shutil
 
import warnings
 
import logging
 
import inspect
 
from StringIO import StringIO
 

	
 
from rhodecode.lib.dbmigrate import migrate
 
from rhodecode.lib.dbmigrate.migrate.versioning import genmodel, schemadiff
 
from rhodecode.lib.dbmigrate.migrate.versioning.config import operations
 
from rhodecode.lib.dbmigrate.migrate.versioning.template import Template
 
from rhodecode.lib.dbmigrate.migrate.versioning.script import base
 
from rhodecode.lib.dbmigrate.migrate.versioning.util import import_path, load_model, with_engine
 
from rhodecode.lib.dbmigrate.migrate.exceptions import MigrateDeprecationWarning, InvalidScriptError, ScriptError
 

	
 
log = logging.getLogger(__name__)
 
__all__ = ['PythonScript']
 
@@ -40,41 +41,41 @@ class PythonScript(base.BaseScript):
 
        
 
        :param repository: path to migrate repository
 
        :param oldmodel: dotted.module.name:SAClass or SAClass object
 
        :param model: dotted.module.name:SAClass or SAClass object
 
        :param engine: SQLAlchemy engine
 
        :type repository: string or :class:`Repository instance <migrate.versioning.repository.Repository>`
 
        :type oldmodel: string or Class
 
        :type model: string or Class
 
        :type engine: Engine instance
 
        :returns: Upgrade / Downgrade script
 
        :rtype: string
 
        """
 

	
 
        
 
        if isinstance(repository, basestring):
 
            # oh dear, an import cycle!
 
            from rhodecode.lib.dbmigrate.migrate.versioning.repository import Repository
 
            repository = Repository(repository)
 

	
 
        oldmodel = load_model(oldmodel)
 
        model = load_model(model)
 

	
 
        # Compute differences.
 
        diff = schemadiff.getDiffOfModelAgainstModel(
 
            oldmodel,
 
            model,
 
            excludeTables=[repository.version_table])
 
        # TODO: diff can be False (there is no difference?)
 
        decls, upgradeCommands, downgradeCommands = \
 
            genmodel.ModelGenerator(diff, engine).toUpgradeDowngradePython()
 
            genmodel.ModelGenerator(diff,engine).toUpgradeDowngradePython()
 

	
 
        # Store differences into file.
 
        src = Template(opts.pop('templates_path', None)).get_script(opts.pop('templates_theme', None))
 
        f = open(src)
 
        contents = f.read()
 
        f.close()
 

	
 
        # generate source
 
        search = 'def upgrade(migrate_engine):'
 
        contents = contents.replace(search, '\n\n'.join((decls, search)), 1)
 
        if upgradeCommands:
 
            contents = contents.replace('    pass', upgradeCommands, 1)
 
@@ -127,30 +128,30 @@ class PythonScript(base.BaseScript):
 
        :type step: int
 
        """
 
        if step > 0:
 
            op = 'upgrade'
 
        elif step < 0:
 
            op = 'downgrade'
 
        else:
 
            raise ScriptError("%d is not a valid step" % step)
 

	
 
        funcname = base.operations[op]
 
        script_func = self._func(funcname)
 

	
 
        try:
 
            script_func(engine)
 
        except TypeError:
 
            warnings.warn("upgrade/downgrade functions must accept engine"
 
                " parameter (since version > 0.5.4)", MigrateDeprecationWarning)
 
            raise
 
        # check for old way of using engine
 
        if not inspect.getargspec(script_func)[0]:
 
            raise TypeError("upgrade/downgrade functions must accept engine"
 
                " parameter (since version 0.5.4)")
 

	
 
        script_func(engine)
 

	
 
    @property
 
    def module(self):
 
        """Calls :meth:`migrate.versioning.script.py.verify_module`
 
        and returns it.
 
        """
 
        if not hasattr(self, '_module'):
 
            self._module = self.verify_module(self.path)
 
        return self._module
 

	
 
    def _func(self, funcname):
 
        if not hasattr(self.module, funcname):
rhodecode/lib/dbmigrate/migrate/versioning/script/sql.py
Show inline comments
 
@@ -9,24 +9,25 @@ from rhodecode.lib.dbmigrate.migrate.ver
 

	
 
log = logging.getLogger(__name__)
 

	
 
class SqlScript(base.BaseScript):
 
    """A file containing plain SQL statements."""
 

	
 
    @classmethod
 
    def create(cls, path, **opts):
 
        """Create an empty migration script at specified path
 
        
 
        :returns: :class:`SqlScript instance <migrate.versioning.script.sql.SqlScript>`"""
 
        cls.require_notfound(path)
 

	
 
        src = Template(opts.pop('templates_path', None)).get_sql_script(theme=opts.pop('templates_theme', None))
 
        shutil.copy(src, path)
 
        return cls(path)
 

	
 
    # TODO: why is step parameter even here?
 
    def run(self, engine, step=None, executemany=True):
 
        """Runs SQL script through raw dbapi execute call"""
 
        text = self.source()
 
        # Don't rely on SA's autocommit here
 
        # (SA uses .startswith to check if a commit is needed. What if script
 
        # starts with a comment?)
 
        conn = engine.connect()
rhodecode/lib/dbmigrate/migrate/versioning/shell.py
Show inline comments
 
@@ -68,26 +68,25 @@ def main(argv=None, **kwargs):
 
        argv = argv
 
    else:
 
        argv = list(sys.argv[1:])
 
    commands = list(api.__all__)
 
    commands.sort()
 

	
 
    usage = """%%prog COMMAND ...
 

	
 
    Available commands:
 
        %s
 

	
 
    Enter "%%prog help COMMAND" for information on a particular command.
 
    """ % '\n\t'.join(["%s - %s" % (command.ljust(28),
 
                    api.command_desc.get(command)) for command in commands])
 
    """ % '\n\t'.join(["%s - %s" % (command.ljust(28), api.command_desc.get(command)) for command in commands])
 

	
 
    parser = PassiveOptionParser(usage=usage)
 
    parser.add_option("-d", "--debug",
 
                     action="store_true",
 
                     dest="debug",
 
                     default=False,
 
                     help="Shortcut to turn on DEBUG mode for logging")
 
    parser.add_option("-q", "--disable_logging",
 
                      action="store_true",
 
                      dest="disable_logging",
 
                      default=False,
 
                      help="Use this option to disable logging configuration")
rhodecode/lib/dbmigrate/migrate/versioning/template.py
Show inline comments
 
@@ -71,24 +71,24 @@ class Template(pathed.Pathed):
 
        :param type_: type of subfolder in collection (defaults to "_default")
 
        :returns: (package, source)
 
        :rtype: str, str
 
        """
 
        item = getattr(self, collection)
 
        theme_mask = getattr(item, '_mask')
 
        theme = theme_mask % (theme or 'default')
 
        return item.get_path(theme)
 

	
 
    def get_repository(self, *a, **kw):
 
        """Calls self._get_item('repository', *a, **kw)"""
 
        return self._get_item('repository', *a, **kw)
 

	
 
    
 
    def get_script(self, *a, **kw):
 
        """Calls self._get_item('script', *a, **kw)"""
 
        return self._get_item('script', *a, **kw)
 

	
 
    def get_sql_script(self, *a, **kw):
 
        """Calls self._get_item('sql_script', *a, **kw)"""
 
        return self._get_item('sql_script', *a, **kw)
 

	
 
    def get_manage(self, *a, **kw):
 
        """Calls self._get_item('manage', *a, **kw)"""
 
        return self._get_item('manage', *a, **kw)
0 comments (0 inline, 0 general)