为啥 Django 不在 SQLite3 中重置序列?

Posted

技术标签:

【中文标题】为啥 Django 不在 SQLite3 中重置序列?【英文标题】:Why Doesn't Django Reset Sequences in SQLite3?为什么 Django 不在 SQLite3 中重置序列? 【发布时间】:2014-07-28 17:19:06 【问题描述】:

为什么 Django 允许您重置 postgres 和其他 DBMS 上的序列 (AutoID) 字段,而不是 SQLite3?

查看django/db/backends/sqlite3/base.pysql_flush方法的源码,有注释说:

注意:不需要重置自动递增索引(参见其他 sql_flush() 实现)。此时只需返回 SQL

我有一些测试在其中加载依赖于绝对主键 ID 的夹具文件。因为 Django 没有为 SQLite 重置 auto id 字段,所以这些设备不能正确加载。

在sqlite中重置自动id列似乎有些微不足道:How can I reset a autoincrement sequence number in sqlite

【问题讨论】:

听起来你的测试坏了,那么。什么都不应该依赖于绝对 ID。 @DanielRoseman 是的,每个人都知道这不是最佳实践。事实上,很容易质疑在测试中使用加载夹具。这就是存在像 FactoryGirl 这样的东西的原因。但是,在这种情况下,这是一个完美的解决方案。 Django 文档指定在测试用例docs.djangoproject.com/en/1.5/topics/testing/overview/… 中使用重置序列。即使他们提到这是一个坏主意。但是,我的问题是:即使这是一个坏主意,为什么他们不支持 sqlite? 我不知道 sqlite 是否像 Oracle 那样支持序列休息。例如,我发现当我清空相关表时,sqlite 自动整数主键会重置。我知道在 Oracle 下,Auto ID 序列作为一个完全独立的实体存在,可以独立重置。 【参考方案1】:

您可以按以下方式对sql_flush 进行猴子补丁以重置 SQLite 序列:

from django.db.backends.sqlite3.operations import DatabaseOperations
from django.db import connection


def _monkey_patch_sqlite_sql_flush_with_sequence_reset():
    original_sql_flush = DatabaseOperations.sql_flush

    def sql_flush_with_sequence_reset(self, style, tables, sequences, allow_cascade=False):
        sql_statement_list = original_sql_flush(self, style, tables, sequences, allow_cascade)
        if tables:
            # DELETE FROM sqlite_sequence WHERE name IN ($tables)
            sql = '%s %s %s %s %s %s (%s);' % (
                style.SQL_KEYWORD('DELETE'),
                style.SQL_KEYWORD('FROM'),
                style.SQL_TABLE(self.quote_name('sqlite_sequence')),
                style.SQL_KEYWORD('WHERE'),
                style.SQL_FIELD(self.quote_name('name')),
                style.SQL_KEYWORD('IN'),
                ', '.join(style.SQL_FIELD(f"'table'") for table in tables)
            )
            sql_statement_list.append(sql)
        return sql_statement_list

    DatabaseOperations.sql_flush = sql_flush_with_sequence_reset

您可以在TransactionTestCase 中按如下方式使用它:

from django.test import TransactionTestCase


class TransactionTestCaseWithSQLiteSequenceReset(TransactionTestCase):

    reset_sequences = True

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        if connection.vendor == 'sqlite':
            _monkey_patch_sqlite_sql_flush_with_sequence_reset()

这可确保依赖于固定主键的测试可与 SQLite 和其他数据库后端(如 PostgreSQL)一起使用。但是,请参阅 Django documentation 以了解有关 reset_sequences 的警告。一方面,它使测试变慢。

【讨论】:

【参考方案2】:

也许这个 sn-p 会有所帮助:

import os

from django.core.management import call_command
from django.db import connection
from django.utils.six import StringIO


def reset_sequences(app_name):
    os.environ['DJANGO_COLORS'] = 'nocolor'
    buf = StringIO()
    call_command('sqlsequencereset', app_name, stdout=buf)
    buf.seek(0)

    sql = "".join(buf.readlines())

    with connection.cursor() as cursor:
        cursor.execute(sql)

    print("Sequences for app '' reset".format(app_name))

【讨论】:

Django 3.0 不再有django.utils.six;见***.com/a/59420098/5139284。我们或许可以用from io import StringIO 替换该行。 确保你没有将reset_sequences定义为TestCase的类方法,否则你会得到“assert not self.reset_sequences, 'reset_sequences cannot be used on TestCase instances' AssertionError: reset_sequences cannot用于 TestCase 实例” 对我来说,这总是打印“未找到序列”。

以上是关于为啥 Django 不在 SQLite3 中重置序列?的主要内容,如果未能解决你的问题,请参考以下文章