2012-09-19 46 views
6

我有這樣的代碼:IntegrityError:唯一約束和NOT NULL違規區分

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 
    ) 

它試圖創建一個用戶給定的ID和電子郵件,如果已經存在一個 - 試圖讓現有的記錄。

我知道這是一個糟糕的構造,無論如何它都會被重構。但我的問題是這樣的:

我如何確定什麼樣的IntegrityError已經發生了:一個有關unique違反約束(有上(USER_ID,電子郵件)唯一鍵)或一個相關not null約束(path不能爲空)?

回答

6

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' 

代碼的含義請參見Appendix A: Error Codes in the PostgreSQL manual。請注意,你可以粗略地匹配前兩個字符的大類。在這種情況下,我可以看到SQLSTATE 42601在Syntax Error or Access Rule Violation類別中爲syntax_error

你想要的代碼是:

23505 unique_violation 
23502 not_null_violation 

,所以你可以寫:

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 

也就是說,這是一個糟糕的方式做一個upsertmerge。 @ pr0gg3d大概是正確的建議與Django一起做的正確方法;我不會做Django,所以我不能評論這一點。有關插入/合併的一般信息,請參閱depesz's article on the topic

3

這可能是更好的使用:

try: 
    obj, created = cls.objects.get_or_create(user_id=user.id, email=user.email) 
except IntegrityError: 
    .... 

https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

IntegrityError只應在的情況下提高有一個NOT NULL約束衝突。 此外,您可以使用created標誌來知道對象是否已經存在。

+0

如果有人發現這種情況:get_or_create()更清晰,但仍然不總是原子的,並且遭受競爭條件(取決於數據庫配置),正如文檔中指出的那樣。 – Bartvds

2

更新爲2017年9月6日:

一個漂亮優雅的方式做,這是try/except IntegrityError as exc,然後使用上exc.__cause__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' 

編輯澄清:我必須使用__cause__因爲我使用的是Django,因此要進入psycopg2 IntegrityError類,我必須調用exc.__cause__

相關問題