@@ -1,6 +1,11 @@
1−{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE DeriveDataTypeable #-}
1{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE OverloadedStrings #-}
4+-- | Module for parsing errors from posgresql error messages.
5+-- Currently in only parses integrity violation errors (class 23).
7+-- /Note: Success of parsing may depend on language settings./
8+----------------------------------------------------------
4module Database.PostgreSQL.Simple.Errors
9module Database.PostgreSQL.Simple.Errors
5 ( ConstraintViolation(..)
10 ( ConstraintViolation(..)
6 , constraintViolation
11 , constraintViolation
@@ -11,28 +16,44 @@ module Database.PostgreSQL.Simple.Errors
12import Prelude hiding (catch)
17import Prelude hiding (catch)
14−import Control.Applicative
19+import Control.Applicative
15−import Control.Exception
20+import Control.Exception
16−import Control.Monad.IO.Class
18−import Data.Attoparsec.Char8
22+import Data.Attoparsec.Char8
19−import Data.ByteString(ByteString)
23+import Data.ByteString (ByteString)
20−import Data.Typeable
24+import Data.Typeable
22−import Database.PostgreSQL.Simple.Internal
26+import Database.PostgreSQL.Simple.Internal
28+-- Examples of parsed error messages
30+-- `ERROR: new row for relation "users" violates check
31+-- constraint "user_kind_check"`
33+-- `ERROR: insert or update on table "user_group_map" violates foreign key
34+-- constraint "user_id"`
36+-- `ERROR: null value in column "login" violates not-null constraint`
38+-- `ERROR: duplicate key value violates unique constraint "users_login_key"`
25data ConstraintViolation
40data ConstraintViolation
26 = NotNullViolation ByteString
41 = NotNullViolation ByteString
42+ -- ^ The field is a column name
27 | ForeignKeyViolation ByteString ByteString
43 | ForeignKeyViolation ByteString ByteString
44+ -- ^ Table name and name of violated constraint
28 | UniqueViolation ByteString
45 | UniqueViolation ByteString
46+ -- ^ Name of violated constraint
29 | CheckViolation ByteString ByteString
47 | CheckViolation ByteString ByteString
48+ -- ^ Relation name (usually table), constraint name
30 deriving (Show, Eq, Ord, Typeable)
49 deriving (Show, Eq, Ord, Typeable)
51+-- Default instance should be enough
52+instance Exception ConstraintViolation
33-- | Tries to convert 'SqlError' to 'ConstrainViolation', checks sqlState and
55-- | Tries to convert 'SqlError' to 'ConstrainViolation', checks sqlState and
34−-- succeedes only if able to parse sqlErrorMsg. May depend on postgres language
56+-- succeedes only if able to parse sqlErrorMsg.
37-- > createUser = catchJust constraintViolation catcher $ execute conn ...
58-- > createUser = catchJust constraintViolation catcher $ execute conn ...
@@ -41,10 +62,10 @@ data ConstraintViolation
41constraintViolation :: SqlError -> Maybe ConstraintViolation
62constraintViolation :: SqlError -> Maybe ConstraintViolation
42constraintViolation e =
63constraintViolation e =
43 case sqlState e of
64 case sqlState e of
44− "23502" -> parseMaybe parseQ1 msg >>= Just . NotNullViolation
65+ "23502" -> NotNullViolation <$> parseMaybe parseQ1 msg
45− "23503" -> parseMaybe parseQ2 msg >>= Just . uncurry ForeignKeyViolation
66+ "23503" -> uncurry ForeignKeyViolation <$> parseMaybe parseQ2 msg
46− "23505" -> parseMaybe parseQ1 msg >>= Just . UniqueViolation
67+ "23505" -> UniqueViolation <$> parseMaybe parseQ1 msg
47− "23514" -> parseMaybe parseQ2 msg >>= Just . uncurry CheckViolation
68+ "23514" -> uncurry CheckViolation <$> parseMaybe parseQ2 msg
48 _ -> Nothing
69 _ -> Nothing
49 where msg = sqlErrorMsg e
70 where msg = sqlErrorMsg e
@@ -60,7 +81,7 @@ constraintViolationE :: SqlError -> Maybe (SqlError, ConstraintViolation)
60constraintViolationE e = fmap ((,) e) $ constraintViolation e
81constraintViolationE e = fmap ((,) e) $ constraintViolation e
62-- | Catches SqlError, tries to convert to ConstraintViolation, re-throws
83-- | Catches SqlError, tries to convert to ConstraintViolation, re-throws
63−-- on fail. Unlike catchJust, provides alternative interface to catchJust
84+-- on fail. Provides alternative interface to catchJust
65-- > createUser = catchViolation catcher $ execute conn ...
86-- > createUser = catchViolation catcher $ execute conn ...
@@ -76,7 +97,7 @@ scanTillQuote :: Parser ByteString
76scanTillQuote = scan False go
97scanTillQuote = scan False go
77 where go True _ = Just False -- escaped character
98 where go True _ = Just False -- escaped character
78 go False '"' = Nothing -- end parse
99 go False '"' = Nothing -- end parse
79− go False '\\' = Just True -- nest one is escaped
100+ go False '\\' = Just True -- next one is escaped
80 go _ _ = Just False
101 go _ _ = Just False
82parseQ1 :: Parser ByteString
103parseQ1 :: Parser ByteString