IntegrityError:区分唯一约束和非空违规

war*_*iuc 10 python django postgresql psycopg2

我有这个代码:

try:
    principal = cls.objects.create(
        user_id=user.id,
        email=user.email,
        path='something'
    )
except IntegrityError:
    principal = cls.objects.get(
        user_id=user.id,
        email=user.email
    )
Run Code Online (Sandbox Code Playgroud)

它尝试使用给定的id和电子邮件创建用户,如果已存在,则尝试获取现有记录.

我知道这是一个糟糕的结构,无论如何它都会被重构.但我的问题是:

我如何确定IntegrityError发生了什么类型:与unique约束违规相关的一个((user_id,email)上有唯一键)或者与not null约束相关的那个(path不能为空)?

Cra*_*ger 9

psycopg2提供了SQLSTATE作为pgcode成员的异常,它为您提供了相当细粒度的错误信息.

python3
>>> import psycopg2
>>> conn = psycopg2.connect("dbname=regress")
>>> curs = conn.cursor()
>>> try:
...     curs.execute("INVALID;")
... except Exception as ex:
...     xx = ex
>>> xx.pgcode
'42601'
Run Code Online (Sandbox Code Playgroud)

有关代码含义,请参阅PostgreSQL手册中的附录A:错误代码.请注意,对于大类,您可以粗略匹配前两个字符.在这种情况下,我可以看到SQLSTATE 42601属于syntax_errorSyntax Error or Access Rule Violation类别.

你想要的代码是:

23505   unique_violation
23502   not_null_violation
Run Code Online (Sandbox Code Playgroud)

所以你可以写:

try:
    principal = cls.objects.create(
        user_id=user.id,
        email=user.email,
        path='something'
    )
except IntegrityError as ex:
    if ex.pgcode == '23505':
        principal = cls.objects.get(
            user_id=user.id,
            email=user.email
        )
    else:
        raise
Run Code Online (Sandbox Code Playgroud)

也就是说,这是一个糟糕的方式做一个upsertmerge.@ pr0gg3d可能正确地建议用Django做正确的方法; 我不做Django所以我不能评论那一点.有关upsert/merge的一般信息,请参阅depesz关于该主题的文章.


小智 8

2017 年 9 月 6 日更新:

一个非常优雅的方法是使用try/ except IntegrityError as exc,然后在exc.__cause__and上使用一些有用的属性exc.__cause__.diag(一个诊断类,它为您提供有关手头错误的一些其他超级相关信息 - 您可以使用 自己探索它dir(exc.__cause__.diag))。

上面描述了您可以使用的第一个。为了使您的代码更具前瞻性,您可以直接引用 psycopg2 代码,您甚至可以使用我上面提到的诊断类检查违反的约束:

except IntegrityError as exc:
    from psycopg2 import errorcodes as pg_errorcodes
    assert exc.__cause__.pgcode == pg_errorcodes.UNIQUE_VIOLATION
    assert exc.__cause__.diag.constraint_name == 'tablename_colA_colB_unique_constraint'
Run Code Online (Sandbox Code Playgroud)

编辑澄清:我必须使用__cause__访问器,因为我使用的是 Django,所以要访问我必须调用的 psycopg2 IntegrityError 类exc.__cause__