IntegrityError 中缺少 table 名称(Django ORM)

Missing table name in IntegrityError (Django ORM)

我在 Django 的 IntegrityError 中缺少 table 名称:

Traceback (most recent call last):
...
    return self.cursor.execute(sql, params)
  File ".../django/db/utils.py", line 94, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File ".../django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
IntegrityError: null value in column "manager_slug" violates not-null constraint
DETAIL:  Failing row contains (17485, null, 2017-10-10 09:32:19, , 306).

有没有办法查看 table INSERT/UPDATE 正在访问哪个?

我们使用 PostgreSQL 9.6。

这是一个一般性问题:如何获得更好的错误信息?

这不是关于这个特定专栏的问题。我很快就找到了相关的 table 和专栏。但我想改进来自我们 CI 系统的错误消息。下次想马上看到table名字

我知道如果我在软件开发过程中看到此错误,我可以使用调试器轻松揭示缺失的信息。但就我而言,这发生在生产中,我只有上面的堆栈跟踪。

您错过了第 "manager_slug" 列的设置值。您不能在此列设置 NULL 值。您应该设置值或删除非空条件。

IntegrityError: null value in column "manager_slug" violates not-null constraint

此回溯中的异常消息是来自数据库驱动程序的原始消息。如果有任何内容被谷歌搜索、报告等,了解这一点和回溯很有用。

所有后端的异常 class 都是相同的 django.db.utils.IntegrityError,但消息或参数取决于后端:

  • postgres: null value in column "manager_slug" violates not-null constraint\n DETAILS...\n
  • mysql。 . : (1048, "Column 'manager_slug' cannot be null")
  • sqlite3。 : NOT NULL constraint failed: appname_modelname.manager_slug

table 名称仅在 sqlite3 后端可见。一些后端仅使用异常的字符串参数,但 mysql 使用两个参数:数字错误代码和消息。 (我愿意接受这是一个普遍的问题,不仅是 PostgreSQL。)一些后端的作者希望应用程序的作者直接或从 SQL 知道 table 名称,但对于一般的 ORM 包来说并非如此。没有更好的和普遍接受的table方式,如何扩展消息,即使它在技术上可以做到完美。

开发调试容易:

  • 在开发中的 DEBUG 模式下有很多额外的信息可用("SQL" 在最后一帧或 class 行中的对象名称,如 "myobj.save()")
  • python manage.py test --debug-sql: "Prints logged SQL queries on failure."
  • development/tests 与 sqlite3 中的相同错误更易于阅读。

...但您可能要求在生产中出现 运行 次错误。

我猜你在一个如此笼统的问题中的可能意图,你可能对哪个方向感兴趣。

A) 来自 traceback 的最重要的信息通常是 above 的几行许多行 ".../django/db/..."。这对古鲁来说是非常容易的。如果代码不像 Django 管理站点那样动态和通用,则很有可能使用它,其中 myobj.save() 调用附近的代码(父框架中都不包含)不包含显式模型名称。示例:

# skip some initial universal code in ".../django/..."
...
# our apps start to be interesting... (maybe other installed app)
...
# START HERE: Open this line in the editor. If the function is universal, jump to the previous.
File ".../me/app/...py", line 47, in my...
  my_obj.save()
# skip many stack frames .../django/db/... below
File ".../django/db/models/base.py", line 734, in save
  # self.save_base(...    # this line 733 is not visible
      force_update=force_update, update_fields=update_fields)
...
# interesting only sql and params, but not visible in production
File ".../django/db/backends/utils.py", line 64, in execute
  return self.cursor.execute(sql, params)
IntegrityError (or DataError similarly)...

B) 通过模型的共同祖先获取信息

class ...(models.Model):
    def save(self, *args, **wkargs):
        try:
            super(..., self).save(*args, **wkargs)
        except django.db.utils.IntegrityError as exc:
            new_message = 'table {}'.format(self._meta.db_table)
            exc.extra_info = new_message
            # this is less compatible, but it doesn't require additional reading support
            # exc.args = exc.args + (new_message,)
            reraise

这可能会使多重继承的调试复杂化。

C) 在 Django db 中的 实现会更好,但我无法想象它会被接受并且之后不会恢复一些问题。

如果您可以创建 sql 函数,您可以尝试:

创建函数以获取最后一个序列值 get_sequence_last_value (original post)

CREATE FUNCTION public.get_sequence_last_value(name) RETURNS int4 AS '
DECLARE
ls_sequence ALIAS FOR ;
lr_record RECORD;
li_return INT4;
BEGIN
FOR lr_record IN EXECUTE ''SELECT last_value FROM '' || ls_sequence LOOP
li_return := lr_record.last_value;
END LOOP;
RETURN li_return;
END;' LANGUAGE 'plpgsql' VOLATILE;

在它得到 table 之后,错误堆栈中的序列更多,并且有列 manager_slug

SELECT table_name, column_name 
FROM information_schema.columns 
WHERE table_name in (
    SELECT table_name

    FROM (
        SELECT table_name,
               get_sequence_last_value(
                    substr(column_default, 10, strpos(column_default, '::regclass') - 11)
                    ) as lv
        FROM information_schema.columns 
        WHERE column_default LIKE 'nextval%'
        ) as t_seq_lv

    WHERE lv > 17485
    )
   AND column_name = 'manager_slug';

我知道解决方案不完整,但无论如何我希望它能帮助你

Is there a way to see which table the INSERT/UPDATE is accessing?

如果您是 运行 migrate,您可以访问标准输出并检查正在执行的迁移。然后您可以打开迁移文件并更好地了解问题。

如果你不这样做,我想你想看看 PEP249 以了解可选的错误处理。因为 django 数据库包装器基于 PEP249 规范。

DatabaseErrorWrapper

中对 django 代码的引用

第二次编辑:

您可以捕获完整性错误并从数据库包装器访问 .messages 属性。

伪例子:

try:
    # operation on database
except IntegrityError as ie:
    print(ie.wrapper.cursor.messages[:])

我建议使用哨兵 (https://sentry.io/welcome/). In Sentry Issues you can observe all local variables for all parts of a stack trace.

我发现针对您的问题的最佳解决方案是覆盖 DataBaseErrorWrapper 方法,为此转到 \django\db\utils.py 并在第 86 行中将 dj_exc_value = dj_exc_type(*exc_value.args) 替换为:

if exec_value.diag:
    a, b = exc_value.args + ("In the table '%s'" % exc_value.diag.table_name,)
    dj_exc_value = dj_exc_type(a + b)
else:
    dj_exc_value = dj_exc_type(*exc_value.args)

如果出现 IntegrityError,消息应该有效

django.db.utils.IntegrityError: null value in column "signature" violates not-null constraint
DETAIL:  Failing row contains (89, null).
In the table 'teachers_signature'

我不确定,但这应该适用于以下例外情况:

DataError
OperationalError
IntegrityError
InternalError
ProgrammingError
NotSupportedError

它对我有用,告诉我是否对你有用。请记住使用您正在工作的 Django 文件夹编辑文件

我倾向于使用尽可能少的依赖,并保持第 3 方库的原样。

大多数时候,我会记录每个 sql insert/update/delete 查询。通过这种方式,我可以轻松识别出哪个查询出错而无需额外的努力。这使我可以跟踪谁在我的系统中何时做了什么。

这恰好满足了我所在行业对行为跟踪的部分监管要求。