@@ -20,24 +20,31 @@
2020
2121module Database.PostgreSQL.Simple.Internal where
2222
23+import Blaze.ByteString.Builder
24+ ( Builder, fromByteString, toByteString )
2325import Control.Applicative
2426import Control.Exception
2527import Control.Concurrent.MVar
2628import Control.Monad(MonadPlus(..))
2729import Data.ByteString(ByteString)
30+import qualified Data.ByteString as B
2831import qualified Data.ByteString.Char8 as B8
2932import Data.Char (ord)
3033import Data.Int (Int64)
3134import qualified Data.IntMap as IntMap
3235import Data.IORef
3336import Data.Maybe(fromMaybe)
37+import Data.Monoid
3438import Data.String
39+import qualified Data.Text as T
40+import qualified Data.Text.Encoding as TE
3541import Data.Typeable
3642import Data.Word
3743import Database.PostgreSQL.LibPQ(Oid(..))
3844import qualified Database.PostgreSQL.LibPQ as PQ
3945import Database.PostgreSQL.LibPQ(ExecStatus(..))
4046import Database.PostgreSQL.Simple.Ok
47+import Database.PostgreSQL.Simple.ToField (Action(..), inQuotes)
4148import Database.PostgreSQL.Simple.Types (Query(..))
4249import Database.PostgreSQL.Simple.TypeInfo.Types(TypeInfo)
4350import Control.Monad.Trans.State.Strict
@@ -92,6 +99,17 @@ data QueryError = QueryError {
9299
93100instance Exception QueryError
94101
102+-- | Exception thrown if a 'Query' could not be formatted correctly.
103+-- This may occur if the number of \'@?@\' characters in the query
104+-- string does not match the number of parameters provided.
105+data FormatError = FormatError {
106+ fmtMessage :: String
107+ , fmtQuery :: Query
108+ , fmtParams :: [ByteString]
109+ } deriving (Eq, Show, Typeable)
110+
111+instance Exception FormatError
112+
95113data ConnectInfo = ConnectInfo {
96114 connectHost :: String
97115 , connectPort :: Word16
@@ -502,6 +520,39 @@ throwLibPQError conn default_desc = do
502520 msg <- maybe default_desc id <$> PQ.errorMessage conn
503521 throwIO $! libPQError msg
504522
523+
524+fmtError :: String -> Query -> [Action] -> a
525+fmtError msg q xs = throw FormatError {
526+ fmtMessage = msg
527+ , fmtQuery = q
528+ , fmtParams = map twiddle xs
529+ }
530+ where twiddle (Plain b) = toByteString b
531+ twiddle (Escape s) = s
532+ twiddle (EscapeByteA s) = s
533+ twiddle (EscapeIdentifier s) = s
534+ twiddle (Many ys) = B.concat (map twiddle ys)
535+
536+fmtErrorBs :: Query -> [Action] -> ByteString -> a
537+fmtErrorBs q xs msg = fmtError (T.unpack $ TE.decodeUtf8 msg) q xs
538+
539+-- | Quote bytestring or throw 'FormatError'
540+quote :: Query -> [Action] -> Either ByteString ByteString -> Builder
541+quote q xs = either (fmtErrorBs q xs) (inQuotes . fromByteString)
542+
543+buildAction :: Connection -- ^ Connection for string escaping
544+ -> Query -- ^ Query for message error
545+ -> [Action] -- ^ List of parameters for message error
546+ -> Action -- ^ Action to build
547+ -> IO Builder
548+buildAction _ _ _ (Plain b) = pure b
549+buildAction conn q xs (Escape s) = quote q xs <$> escapeStringConn conn s
550+buildAction conn q xs (EscapeByteA s) = quote q xs <$> escapeByteaConn conn s
551+buildAction conn q xs (EscapeIdentifier s) =
552+ either (fmtErrorBs q xs) fromByteString <$> escapeIdentifier conn s
553+buildAction conn q xs (Many ys) =
554+ mconcat <$> mapM (buildAction conn q xs) ys
555+
505556checkError :: PQ.Connection -> Maybe a -> IO (Either ByteString a)
506557checkError _ (Just x) = return $ Right x
507558checkError c Nothing = Left . maybe "" id <$> PQ.errorMessage c