Ejemplo n.º 1
0
    def change_column_attrs(self, model, mutation, field_name, new_attrs):
        """Returns the SQL for changing one or more column attributes.

        This will generate all the statements needed for changing a set
        of attributes for a column.

        The resulting AlterTableSQLResult contains all the SQL needed
        to apply these attributes.
        """
        field = model._meta.get_field(field_name)
        attrs_sql_result = AlterTableSQLResult(self, model)

        for attr_name, attr_info in new_attrs.iteritems():
            method_name = 'change_column_attr_%s' % attr_name
            evolve_func = getattr(self, method_name)

            try:
                sql_result = evolve_func(model, mutation, field,
                                         attr_info['old_value'],
                                         attr_info['new_value'])
                assert not sql_result or isinstance(sql_result, SQLResult)
            except Exception, e:
                logging.critical(
                    'Error running database evolver function %s: %s',
                    method_name,
                    e,
                    exc_info=1)
                raise

            attrs_sql_result.add(sql_result)
Ejemplo n.º 2
0
    def change_column_attrs(self, model, mutation, field_name, new_attrs):
        """Returns the SQL for changing one or more column attributes.

        This will generate all the statements needed for changing a set
        of attributes for a column.

        The resulting AlterTableSQLResult contains all the SQL needed
        to apply these attributes.
        """
        field = model._meta.get_field(field_name)
        attrs_sql_result = AlterTableSQLResult(self, model)

        for attr_name, attr_info in new_attrs.iteritems():
            method_name = 'change_column_attr_%s' % attr_name
            evolve_func = getattr(self, method_name)

            try:
                sql_result = evolve_func(model, mutation, field,
                                         attr_info['old_value'],
                                         attr_info['new_value'])
                assert not sql_result or isinstance(sql_result, SQLResult)
            except Exception, e:
                logging.critical(
                    'Error running database evolver function %s: %s',
                    method_name, e,
                    exc_info=1)
                raise

            attrs_sql_result.add(sql_result)
Ejemplo n.º 3
0
    def delete_column(self, model, f):
        sql_result = AlterTableSQLResult(self, model)

        if f.rel:
            sql_result.add(sql_delete_constraints(self.connection,
                                                  f.rel.to,
                                                  {f.rel.to: [(model, f)]}))

        sql_result.add_sql(
            super(EvolutionOperations, self).delete_column(model, f))

        return sql_result
Ejemplo n.º 4
0
    def rename_column(self, model, old_field, new_field):
        if old_field.column == new_field.column:
            # No Operation
            return []

        qn = self.connection.ops.quote_name
        max_name_length = self.connection.ops.max_name_length()

        sql_result = AlterTableSQLResult(self, model)

        pre_sql, stash = self.stash_field_ref_constraints(model=model,
                                                          replaced_fields={
                                                              old_field:
                                                              new_field,
                                                          })
        sql_result.add_pre_sql(pre_sql)

        sql_result.add_alter_table([{
            'independent':
            True,
            'sql':
            'RENAME COLUMN %s TO %s' %
            (truncate_name(qn(old_field.column), max_name_length),
             truncate_name(qn(new_field.column), max_name_length)),
        }])

        sql_result.add_post_sql(self.restore_field_ref_constraints(stash))

        return sql_result
Ejemplo n.º 5
0
    def delete_column(self, model, f):
        sql_result = AlterTableSQLResult(self, model)

        if f.rel:
            creation = self.connection.creation
            style = color.no_style()

            sql_result.add(creation.sql_remove_table_constraints(
                f.rel.to,
                {f.rel.to: [(model, f)]},
                style))

        sql_result.add_sql(
            super(EvolutionOperations, self).delete_column(model, f))

        return sql_result
Ejemplo n.º 6
0
    def change_column_attr_max_length(self, model, mutation, field, old_value,
                                      new_value):
        qn = self.connection.ops.quote_name

        field.max_length = new_value

        db_type = field.db_type(connection=self.connection)
        params = {
            'table': qn(model._meta.db_table),
            'column': qn(field.column),
            'length': field.max_length,
            'type': db_type,
        }

        return AlterTableSQLResult(
            self,
            model,
            pre_sql=[
                'UPDATE %(table)s SET %(column)s=LEFT(%(column)s,%(length)d);'
                % params,
            ],
            alter_table=[
                {
                    'op': 'MODIFY COLUMN',
                    'column': field.column,
                    'db_type': db_type,
                },
            ])
Ejemplo n.º 7
0
    def get_drop_unique_constraint_sql(self, model, index_name):
        qn = self.connection.ops.quote_name

        return AlterTableSQLResult(
            self, model, [{
                'sql': 'DROP CONSTRAINT %s' % qn(index_name)
            }])
Ejemplo n.º 8
0
    def rename_column(self, model, old_field, new_field):
        if old_field.column == new_field.column:
            # No Operation
            return []

        col_type = new_field.db_type(connection=self.connection)

        if col_type is None:
            # Skip ManyToManyFields, because they're not represented as
            # database columns in this table.
            return []

        models = []
        refs = {}
        opts = model._meta

        pre_sql = self.remove_field_constraints(old_field, opts, models, refs)
        alter_table_items = self._get_rename_column_sql(
            opts, old_field, new_field)
        post_sql = self.add_primary_key_field_constraints(
            old_field, new_field, models, refs)

        return AlterTableSQLResult(self,
                                   model,
                                   pre_sql=pre_sql,
                                   alter_table=alter_table_items,
                                   post_sql=post_sql)
Ejemplo n.º 9
0
    def rename_column(self, model, old_field, new_field):
        if old_field.column == new_field.column:
            # No Operation
            return []

        qn = self.connection.ops.quote_name
        max_name_length = self.connection.ops.max_name_length()
        opts = model._meta
        refs = {}
        models = []

        return AlterTableSQLResult(
            self,
            model,
            pre_sql=self.remove_field_constraints(old_field, opts, models,
                                                  refs),
            alter_table=[
                {
                    'independent':
                    True,
                    'sql':
                    'RENAME COLUMN %s TO %s' %
                    (truncate_name(qn(old_field.column), max_name_length),
                     truncate_name(qn(new_field.column), max_name_length)),
                },
            ],
            post_sql=self.add_primary_key_field_constraints(
                old_field, new_field, models, refs))
Ejemplo n.º 10
0
 def delete_column(self, model, f):
     return AlterTableSQLResult(
         self,
         model,
         [
             {
                 'op': 'DROP COLUMN',
                 'column': f.column,
                 'params': ['CASCADE']
             },
         ],
     )
Ejemplo n.º 11
0
    def set_field_null(self, model, field, null):
        if null:
            null_attr = 'DEFAULT NULL'
        else:
            null_attr = 'NOT NULL'

        return AlterTableSQLResult(
            self, model, [{
                'op': 'MODIFY COLUMN',
                'column': field.column,
                'db_type': field.db_type(connection=self.connection),
                'params': [null_attr],
            }])
Ejemplo n.º 12
0
    def set_field_null(self, model, field, null):
        if null:
            attr = 'DROP NOT NULL'
        else:
            attr = 'SET NOT NULL'

        return AlterTableSQLResult(self, model, [
            {
                'op': 'ALTER COLUMN',
                'column': field.column,
                'params': [attr],
            },
        ])
Ejemplo n.º 13
0
    def get_change_column_type_sql(self, model, old_field, new_field):
        """Return SQL to change the type of a column.

        Version Added:
            2.2

        Args:
            model (type):
                The type of model owning the field.

            old_field (django.db.models.Field):
                The old field.

            new_field (django.db.models.Field):
                The new field.

        Returns:
            django_evolution.sql_result.AlterTableSQLResult:
            The SQL statements for changing the column type.
        """
        schema = self.build_column_schema(
            model=model,
            field=new_field,
            initial=new_field.default,
            skip_references=True)

        alter_table_items = []

        if old_field.primary_key:
            alter_table_items.append({
                'sql': 'DROP PRIMARY KEY',
            })

        params = [schema['db_type']]

        if new_field.null:
            params.append('NULL')
        else:
            params.append('NOT NULL')

        alter_table_items.append({
            'op': 'MODIFY',
            'column': schema['name'],
            'params': [schema['db_type']] + schema['definition'],
            'sql_params': schema['definition_sql_params'],
        })

        return AlterTableSQLResult(self, model, alter_table_items)
Ejemplo n.º 14
0
    def delete_column(self, model, f):
        sql_result = AlterTableSQLResult(self, model)

        if f.rel:
            sql_result.add(
                sql_delete_constraints(self.connection, f.rel.to,
                                       {f.rel.to: [(model, f)]}))

        sql_result.add_sql(
            super(EvolutionOperations, self).delete_column(model, f))

        return sql_result
Ejemplo n.º 15
0
    def get_change_unique_sql(self, model, field, new_unique_value,
                              constraint_name, initial):
        """Returns the database-specific SQL to change a column's unique flag.

        This can be overridden by subclasses if they use a different syntax.
        """
        qn = self.connection.ops.quote_name

        if new_unique_value:
            alter_table_item = {
                'sql':
                'ADD CONSTRAINT %s UNIQUE(%s)' %
                (constraint_name, qn(field.column))
            }
        else:
            alter_table_item = {'sql': 'DROP CONSTRAINT %s' % constraint_name}

        return AlterTableSQLResult(self, model, [alter_table_item])
Ejemplo n.º 16
0
    def change_column_attr_max_length(self, model, mutation, field, old_value,
                                      new_value):
        """Returns the SQL for changing a column's max length."""
        field.max_length = new_value

        qn = self.connection.ops.quote_name
        column = field.column
        db_type = field.db_type(connection=self.connection)

        return AlterTableSQLResult(self, model, [
            {
                'op':
                'ALTER COLUMN',
                'column':
                column,
                'params': [
                    'TYPE %s USING CAST(%s as %s)' %
                    (db_type, qn(column), db_type),
                ],
            },
        ])
Ejemplo n.º 17
0
    def delete_column(self, model, f):
        sql_result = AlterTableSQLResult(self, model)

        remote_field = get_remote_field(f)

        if remote_field:
            remote_field_model = get_remote_field_model(remote_field)

            sql_result.add(
                sql_delete_constraints(self.connection, remote_field_model,
                                       {remote_field_model: [(model, f)]}))

        sql_result.add_sql(
            super(EvolutionOperations, self).delete_column(model, f))

        return sql_result
Ejemplo n.º 18
0
    def generate_table_op_sql(self, mutator, op, prev_sql_result, prev_op):
        """Generates SQL for a single mutation operation.

        This will call different SQL-generating functions provided by the
        class, depending on the details of the operation.

        If two adjacent operations can be merged together (meaning that
        they can be turned into one ALTER TABLE statement), they'll be placed
        in the same AlterTableSQLResult.
        """
        model = mutator.create_model()

        op_type = op['type']
        mutation = op['mutation']

        if prev_op and self._are_ops_mergeable(prev_op, op):
            sql_result = prev_sql_result
        else:
            sql_result = AlterTableSQLResult(self, model)

        if op_type == 'add_column':
            field = op['field']
            sql_result.add(self.add_column(model, field, op['initial']))
            sql_result.add(self.create_index(model, field))
        elif op_type == 'change_column':
            sql_result.add(self.change_column_attrs(model, mutation,
                                                    op['field'].name,
                                                    op['new_attrs']))
        elif op_type == 'delete_column':
            sql_result.add(self.delete_column(model, op['field']))
        elif op_type == 'change_meta':
            evolve_func = getattr(self, 'change_meta_%s' % op['prop_name'])
            sql_result.add(evolve_func(model, op['old_value'],
                                       op['new_value']))
        elif op_type == 'sql':
            sql_result.add(op['sql'])
        else:
            raise EvolutionNotImplementedError(
                'Unknown mutation operation "%s"' % op_type)

        mutator.finish_op(op)

        return sql_result
Ejemplo n.º 19
0
    def add_column(self, model, f, initial):
        qn = self.connection.ops.quote_name
        sql_result = AlterTableSQLResult(self, model)

        if f.rel:
            # it is a foreign key field
            # NOT NULL REFERENCES "django_evolution_addbasemodel"
            # ("id") DEFERRABLE INITIALLY DEFERRED

            # ALTER TABLE <tablename> ADD COLUMN <column name> NULL
            # REFERENCES <tablename1> ("<colname>") DEFERRABLE INITIALLY
            # DEFERRED
            related_model = f.rel.to
            related_table = related_model._meta.db_table
            related_pk_col = related_model._meta.pk.name
            constraints = ['%sNULL' % (not f.null and 'NOT ' or '')]

            if f.unique or f.primary_key:
                constraints.append('UNIQUE')

            sql_result.add_alter_table([
                {
                    'op': 'ADD COLUMN',
                    'column': f.column,
                    'db_type': f.db_type(connection=self.connection),
                    'params': constraints + [
                        'REFERENCES',
                        qn(related_table),
                        '(%s)' % qn(related_pk_col),
                        self.connection.ops.deferrable_sql(),
                    ]
                }
            ])
        else:
            null_constraints = '%sNULL' % (not f.null and 'NOT ' or '')

            if f.unique or f.primary_key:
                unique_constraints = 'UNIQUE'
            else:
                unique_constraints = ''

            # At this point, initial can only be None if null=True,
            # otherwise it is a user callable or the default
            # AddFieldInitialCallback which will shortly raise an exception.
            if initial is not None:
                if callable(initial):
                    sql_result.add_alter_table([
                        {
                            'op': 'ADD COLUMN',
                            'column': f.column,
                            'db_type': f.db_type(connection=self.connection),
                            'params': [unique_constraints],
                        }
                    ])

                    sql_result.add_sql([
                        'UPDATE %s SET %s = %s WHERE %s IS NULL;'
                        % (qn(model._meta.db_table), qn(f.column),
                           initial(), qn(f.column))
                    ])

                    if not f.null:
                        # Only put this sql statement if the column cannot
                        # be null.
                        sql_result.add_sql(
                            self.set_field_null(model, f, f.null))
                else:
                    sql_result.add_alter_table([
                        {
                            'op': 'ADD COLUMN',
                            'column': f.column,
                            'db_type': f.db_type(connection=self.connection),
                            'params': [
                                null_constraints,
                                unique_constraints,
                                'DEFAULT',
                                '%s',
                            ],
                            'sql_params': [initial]
                        }
                    ])

                    # Django doesn't generate default columns, so now that
                    # we've added one to get default values for existing
                    # tables, drop that default.
                    sql_result.add_post_sql([
                        'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT;'
                        % (qn(model._meta.db_table), qn(f.column))
                    ])
            else:
                sql_result.add_alter_table([
                    {
                        'op': 'ADD COLUMN',
                        'column': f.column,
                        'db_type': f.db_type(connection=self.connection),
                        'params': [null_constraints, unique_constraints],
                    }
                ])

        if f.unique or f.primary_key:
            self.record_index(model, [f], use_constraint_name=True,
                              unique=True)

        return sql_result
Ejemplo n.º 20
0
 def get_drop_unique_constraint_sql(self, model, index_name):
     return AlterTableSQLResult(self, model,
                                [{
                                    'sql': 'DROP CONSTRAINT %s' % index_name
                                }])
Ejemplo n.º 21
0
    def get_change_column_type_sql(self, model, old_field, new_field):
        """Return SQL to change the type of a column.

        Version Added:
            2.2

        Args:
            model (type):
                The type of model owning the field.

            old_field (django.db.models.Field):
                The old field.

            new_field (django.db.models.Field):
                The new field.

        Returns:
            django_evolution.sql_result.AlterTableSQLResult:
            The SQL statements for changing the column type.
        """
        connection = self.connection
        qn = connection.ops.quote_name

        schema = self.build_column_schema(
            model=model,
            field=new_field,
            initial=new_field.default,
            skip_null_constraint=True,
            skip_primary_or_unique_constraint=True,
            skip_references=True)
        column_name = schema['name']
        table_name = model._meta.db_table

        sql_result = AlterTableSQLResult(self, model)

        old_field_type = old_field.db_type(connection=connection).lower()
        new_field_type = schema['db_type'].lower()

        was_serial = old_field_type in self.alter_field_type_map
        is_serial = new_field_type in self.alter_field_type_map

        if is_serial:
            # This is a serial field. We will need to change the type and
            # update the sequence. We will also need to choose the actual
            # type to set for the column definition.
            new_field_type = self.alter_field_type_map.get(
                new_field_type, new_field_type)

        alter_type_params = ['TYPE', new_field_type] + schema['definition']

        if not self._are_column_types_compatible(old_field, new_field):
            alter_type_params += [
                'USING',
                '%s::%s' % (column_name, new_field_type),
            ]

        sql_result.add_alter_table([{
            'op':
            'ALTER COLUMN',
            'column':
            column_name,
            'params':
            alter_type_params,
            'sql_params':
            schema['definition_sql_params'],
        }])

        if is_serial:
            # Reset the sequence.
            sequence_name = '%s_%s_seq' % (table_name, column_name)

            sequence_sql_result = AlterTableSQLResult(self, model)
            sequence_sql_result.add_pre_sql([
                'DROP SEQUENCE IF EXISTS %s CASCADE;' % qn(sequence_name),
                'CREATE SEQUENCE %s;' % qn(sequence_name),
            ])
            sequence_sql_result.add_alter_table([{
                'op':
                'ALTER COLUMN',
                'column':
                column_name,
                'params': [
                    'SET',
                    'DEFAULT',
                    "nextval('%s')" % qn(sequence_name),
                ],
            }])
            sequence_sql_result.add_post_sql([
                "SELECT setval('%s', MAX(%s)) FROM %s;" %
                (qn(sequence_name), qn(column_name), qn(table_name)),
                'ALTER SEQUENCE %s OWNED BY %s.%s;' %
                (qn(sequence_name), qn(table_name), qn(column_name)),
            ])

            sql_result.add_post_sql(sequence_sql_result)
        elif was_serial:
            # Drop the old sequence, since we no longer need it.
            sequence_name = '%s_%s_seq' % (table_name, old_field.column)
            sql_result.add_post_sql([
                'DROP SEQUENCE IF EXISTS %s CASCADE;' % qn(sequence_name),
            ])

        return sql_result
Ejemplo n.º 22
0
    def generate_table_op_sql(self, mutator, op, prev_sql_result, prev_op):
        """Generates SQL for a single mutation operation.

        This will call different SQL-generating functions provided by the
        class, depending on the details of the operation.

        If two adjacent operations can be merged together (meaning that
        they can be turned into one ALTER TABLE statement), they'll be placed
        in the same AlterTableSQLResult.
        """
        model = mutator.create_model()

        op_type = op['type']
        mutation = op['mutation']

        if prev_op and self._are_ops_mergeable(prev_op, op):
            sql_result = prev_sql_result
        else:
            sql_result = AlterTableSQLResult(self, model)

        if op_type == 'add_column':
            field = op['field']
            sql_result.add(self.add_column(model, field, op['initial']))
            sql_result.add(self.create_index(model, field))
        elif op_type == 'change_column':
            sql_result.add(
                self.change_column_attrs(model, mutation, op['field'].name,
                                         op['new_attrs']))
        elif op_type == 'delete_column':
            sql_result.add(self.delete_column(model, op['field']))
        elif op_type == 'change_meta':
            evolve_func = getattr(self, 'change_meta_%s' % op['prop_name'])
            sql_result.add(evolve_func(model, op['old_value'],
                                       op['new_value']))
        elif op_type == 'sql':
            sql_result.add(op['sql'])
        else:
            raise EvolutionNotImplementedError(
                'Unknown mutation operation "%s"' % op_type)

        mutator.finish_op(op)

        return sql_result
Ejemplo n.º 23
0
    def add_column(self, model, f, initial):
        qn = self.connection.ops.quote_name
        sql_result = AlterTableSQLResult(self, model)

        if f.rel:
            # it is a foreign key field
            # NOT NULL REFERENCES "django_evolution_addbasemodel"
            # ("id") DEFERRABLE INITIALLY DEFERRED

            # ALTER TABLE <tablename> ADD COLUMN <column name> NULL
            # REFERENCES <tablename1> ("<colname>") DEFERRABLE INITIALLY
            # DEFERRED
            related_model = f.rel.to
            related_table = related_model._meta.db_table
            related_pk_col = related_model._meta.pk.name
            constraints = ['%sNULL' % (not f.null and 'NOT ' or '')]

            if f.unique or f.primary_key:
                constraints.append('UNIQUE')

            sql_result.add_alter_table([{
                'op':
                'ADD COLUMN',
                'column':
                f.column,
                'db_type':
                f.db_type(connection=self.connection),
                'params':
                constraints + [
                    'REFERENCES',
                    qn(related_table),
                    '(%s)' % qn(related_pk_col),
                    self.connection.ops.deferrable_sql(),
                ]
            }])
        else:
            null_constraints = '%sNULL' % (not f.null and 'NOT ' or '')

            if f.unique or f.primary_key:
                unique_constraints = 'UNIQUE'
            else:
                unique_constraints = ''

            # At this point, initial can only be None if null=True,
            # otherwise it is a user callable or the default
            # AddFieldInitialCallback which will shortly raise an exception.
            if initial is not None:
                if callable(initial):
                    sql_result.add_alter_table([{
                        'op':
                        'ADD COLUMN',
                        'column':
                        f.column,
                        'db_type':
                        f.db_type(connection=self.connection),
                        'params': [unique_constraints],
                    }])

                    sql_result.add_sql([
                        'UPDATE %s SET %s = %s WHERE %s IS NULL;' %
                        (qn(model._meta.db_table), qn(
                            f.column), initial(), qn(f.column))
                    ])

                    if not f.null:
                        # Only put this sql statement if the column cannot
                        # be null.
                        sql_result.add_sql(
                            self.set_field_null(model, f, f.null))
                else:
                    sql_result.add_alter_table([{
                        'op':
                        'ADD COLUMN',
                        'column':
                        f.column,
                        'db_type':
                        f.db_type(connection=self.connection),
                        'params': [
                            null_constraints,
                            unique_constraints,
                            'DEFAULT',
                            '%s',
                        ],
                        'sql_params': [initial]
                    }])

                    # Django doesn't generate default columns, so now that
                    # we've added one to get default values for existing
                    # tables, drop that default.
                    sql_result.add_post_sql([
                        'ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT;' %
                        (qn(model._meta.db_table), qn(f.column))
                    ])
            else:
                sql_result.add_alter_table([{
                    'op':
                    'ADD COLUMN',
                    'column':
                    f.column,
                    'db_type':
                    f.db_type(connection=self.connection),
                    'params': [null_constraints, unique_constraints],
                }])

        if f.unique or f.primary_key:
            self.record_index(model, [f],
                              use_constraint_name=True,
                              unique=True)

        return sql_result
Ejemplo n.º 24
0
    def rename_column(self, model, old_field, new_field):
        """Rename the specified column.

        This will rename the column through ``ALTER TABLE .. CHANGE COLUMN``.

        Any constraints on the column will be stashed away before the
        ``ALTER TABLE`` and restored afterward.

        If the column has not actually changed, or it's not a real column
        (a many-to-many relation), then this will return empty statements.

        Args:
            model (type):
                The model representing the table containing the column.

            old_field (django.db.models.Field):
                The old field definition.

            new_field (django.db.models.Field):
                The new field definition.

        Returns:
            django_evolution.db.sql_result.AlterTableSQLResult or list:
            The statements for renaming the column. This may be an empty
            list if the column won't be renamed.
        """
        if old_field.column == new_field.column:
            # No Operation
            return []

        col_type = new_field.db_type(connection=self.connection)

        if col_type is None:
            # Skip ManyToManyFields, because they're not represented as
            # database columns in this table.
            return []

        qn = self.connection.ops.quote_name
        sql_result = AlterTableSQLResult(self, model)

        pre_sql, stash = self.stash_field_ref_constraints(
            model=model,
            replaced_fields={
                old_field: new_field,
            })
        sql_result.add_pre_sql(pre_sql)

        schema = self.build_column_schema(model=model,
                                          field=new_field,
                                          initial=new_field.default)

        alter_table_items = []

        if old_field.primary_key:
            alter_table_items.append('DROP PRIMARY KEY')

        alter_table_items.append(
            'CHANGE COLUMN %s %s'
            % (qn(old_field.column), ' '.join([
                qn(schema['name']),
                schema['db_type'],
            ] + schema['definition'])))

        sql_result.add_alter_table([{
            'sql': ', '.join(alter_table_items),
        }])
        sql_result.add_post_sql(self.restore_field_ref_constraints(stash))

        return sql_result