Ticket #29182: initial_patch.diff

File initial_patch.diff, 6.4 KB (added by Florian Apolloner, 20 months ago)
  • django/db/backends/sqlite3/features.py

    diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py
    index d9ee9c85b8..1198f7e9a1 100644
    a b class DatabaseFeatures(BaseDatabaseFeatures): 
    2424    can_introspect_small_integer_field = True
    2525    supports_transactions = True
    2626    atomic_transactions = False
    27     can_rollback_ddl = True
    2827    supports_atomic_references_rename = False
    2928    supports_paramstyle_pyformat = False
    3029    supports_sequence_reset = False
  • django/db/backends/sqlite3/schema.py

    diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py
    index ad22d03763..2ae756d144 100644
    a b class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): 
    8888        else:
    8989            super().alter_db_table(model, old_db_table, new_db_table)
    9090
    91     def alter_field(self, model, old_field, new_field, strict=False):
    92         old_field_name = old_field.name
    93         table_name = model._meta.db_table
    94         _, old_column_name = old_field.get_attname_column()
    95         if (new_field.name != old_field_name and
    96                 self._is_referenced_by_fk_constraint(table_name, old_column_name, ignore_self=True)):
    97             if self.connection.in_atomic_block:
    98                 raise NotSupportedError((
    99                     'Renaming the %r.%r column while in a transaction is not '
    100                     'supported on SQLite because it would break referential '
    101                     'integrity. Try adding `atomic = False` to the Migration class.'
    102                 ) % (model._meta.db_table, old_field_name))
    103             with atomic(self.connection.alias):
    104                 super().alter_field(model, old_field, new_field, strict=strict)
    105                 # Follow SQLite's documented procedure for performing changes
    106                 # that don't affect the on-disk content.
    107                 # https://sqlite.org/lang_altertable.html#otheralter
    108                 with self.connection.cursor() as cursor:
    109                     schema_version = cursor.execute('PRAGMA schema_version').fetchone()[0]
    110                     cursor.execute('PRAGMA writable_schema = 1')
    111                     references_template = ' REFERENCES "%s" ("%%s") ' % table_name
    112                     new_column_name = new_field.get_attname_column()[1]
    113                     search = references_template % old_column_name
    114                     replacement = references_template % new_column_name
    115                     cursor.execute('UPDATE sqlite_master SET sql = replace(sql, %s, %s)', (search, replacement))
    116                     cursor.execute('PRAGMA schema_version = %d' % (schema_version + 1))
    117                     cursor.execute('PRAGMA writable_schema = 0')
    118                     # The integrity check will raise an exception and rollback
    119                     # the transaction if the sqlite_master updates corrupt the
    120                     # database.
    121                     cursor.execute('PRAGMA integrity_check')
    122             # Perform a VACUUM to refresh the database representation from
    123             # the sqlite_master table.
    124             with self.connection.cursor() as cursor:
    125                 cursor.execute('VACUUM')
    126         else:
    127             super().alter_field(model, old_field, new_field, strict=strict)
    128 
    12991    def _remake_table(self, model, create_field=None, delete_field=None, alter_field=None):
    13092        """
    13193        Shortcut to transform a model from old_model into new_model
    class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): 
    250212            yield
    251213            model._meta.db_table = original_table_name
    252214
     215        schema_rewrite_needed = self._is_referenced_by_fk_constraint(model._meta.db_table, ignore_self=True)
     216        if schema_rewrite_needed and self.connection.in_atomic_block:
     217            raise NotSupportedError((
     218                'Changing the %r table while in a transaction is not '
     219                'supported on SQLite because it would break referential '
     220                'integrity. Try adding `atomic = False` to the Migration class.'
     221            ) % model._meta.db_table)
     222
    253223        with altered_table_name(model, model._meta.db_table + "__old"):
    254224            # Rename the old table to make way for the new
    255225            self.alter_db_table(
    class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): 
    271241            # Delete the old table
    272242            self.delete_model(model, handle_autom2m=False)
    273243
     244        # TODO: Should be merged with the above changes
     245        #       ... But that causes problems with BaseDatabaseSchemaEditor.execute
     246        #           (the transaction check there)
     247        if schema_rewrite_needed:
     248            with atomic(self.connection.alias):
     249                # Follow SQLite's documented procedure for performing changes
     250                # that don't affect the on-disk content.
     251                # https://sqlite.org/lang_altertable.html#otheralter
     252                with self.connection.cursor() as cursor:
     253                    # TODO: Is there any reason this doesn't go through BaseDatabaseSchemaEditor.execute
     254                    #       to record the queries if sqlmigrate is used? Not that the output would be nice
     255                    #       but it would be nearer to the truth
     256                    schema_version = cursor.execute('PRAGMA schema_version').fetchone()[0]
     257                    cursor.execute('PRAGMA writable_schema = 1')
     258                    references_template = ' REFERENCES "%s"'
     259                    search = references_template % (model._meta.db_table + "__old")
     260                    replacement = references_template % model._meta.db_table
     261                    cursor.execute('UPDATE sqlite_master SET sql = replace(sql, %s, %s)', (search, replacement))
     262                    cursor.execute('PRAGMA schema_version = %d' % (schema_version + 1))
     263                    cursor.execute('PRAGMA writable_schema = 0')
     264                    # The integrity check will raise an exception and rollback
     265                    # the transaction if the sqlite_master updates corrupt the
     266                    # database.
     267                    cursor.execute('PRAGMA integrity_check')
     268
     269            # Perform a VACUUM to refresh the database representation from
     270            # the sqlite_master table.
     271            with self.connection.cursor() as cursor:
     272                cursor.execute('VACUUM')
     273
    274274        # Run deferred SQL on correct table
    275275        for sql in self.deferred_sql:
    276276            self.execute(sql)
Back to Top