@@ -113,7 +113,6 @@ import Control.Exception
113113 ( Exception, onException, throw, throwIO, finally )
114114import Control.Monad (foldM)
115115import Data.ByteString (ByteString)
116−import Data.Char(ord)
117116import Data.Int (Int64)
118117import qualified Data.IntMap as IntMap
119118import Data.List (intersperse)
@@ -133,6 +132,8 @@ import Database.PostgreSQL.Simple.Internal as Base
133132import qualified Database.PostgreSQL.LibPQ as PQ
134133import Text.Regex.PCRE.Light (compile, caseless, match)
135134import qualified Data.ByteString.Char8 as B
135+import qualified Data.Text as T
136+import qualified Data.Text.Encoding as TE
136137import qualified Data.Vector as V
137138import Control.Monad.Trans.Reader
138139import Control.Monad.Trans.State.Strict
@@ -148,15 +149,6 @@ data FormatError = FormatError {
148149
149150instance Exception FormatError
150151
151−-- | Exception thrown if 'query' is used to perform an @INSERT@-like
152−-- operation, or 'execute' is used to perform a @SELECT@-like operation.
153−data QueryError = QueryError {
154− qeMessage :: String
155− , qeQuery :: Query
156− } deriving (Eq, Show, Typeable)
157−
158−instance Exception QueryError
159−
160152-- | Format a query string.
161153--
162154-- This function is exposed to help with debugging and logging. Do not
@@ -202,16 +194,29 @@ formatMany conn q@(Query template) qs = do
202194 \([^?]*)$"
203195 [caseless]
204196
205−escapeStringConn :: Connection -> ByteString -> IO (Maybe ByteString)
206−escapeStringConn conn s = withConnection conn $ \c -> do
207− PQ.escapeStringConn c s
197+escapeStringConn :: Connection -> ByteString -> IO (Either ByteString ByteString)
198+escapeStringConn conn s =
199+ withConnection conn $ \c ->
200+ PQ.escapeStringConn c s >>= checkError c
201+
202+escapeByteaConn :: Connection -> ByteString -> IO (Either ByteString ByteString)
203+escapeByteaConn conn s =
204+ withConnection conn $ \c ->
205+ PQ.escapeByteaConn c s >>= checkError c
206+
207+checkError :: PQ.Connection -> Maybe a -> IO (Either ByteString a)
208+checkError c (Just x) = return $ Right x
209+checkError c Nothing = Left . maybe "" id <$> PQ.errorMessage c
208210
209211buildQuery :: Connection -> Query -> ByteString -> [Action] -> IO Builder
210212buildQuery conn q template xs = zipParams (split template) <$> mapM sub xs
211− where quote = inQuotes . fromByteString . maybe undefined id
212− sub (Plain b) = pure b
213− sub (Escape s) = quote <$> escapeStringConn conn s
214− sub (Many ys) = mconcat <$> mapM sub ys
213+ where quote = either (\msg -> fmtError (utf8ToString msg) q xs)
214+ (inQuotes . fromByteString)
215+ utf8ToString = T.unpack . TE.decodeUtf8
216+ sub (Plain b) = pure b
217+ sub (Escape s) = quote <$> escapeStringConn conn s
218+ sub (EscapeBytea s) = quote <$> escapeByteaConn conn s
219+ sub (Many ys) = mconcat <$> mapM sub ys
215220 split s = fromByteString h : if B.null t then [] else split (B.tail t)
216221 where (h,t) = B.break (=='?') s
217222 zipParams (t:ts) (p:ps) = t `mappend` p `mappend` zipParams ts ps
@@ -231,12 +236,6 @@ execute conn template qs = do
231236 result <- exec conn =<< formatQuery conn template qs
232237 finishExecute conn template result
233238
234−-- | A version of 'execute' that does not perform query substitution.
235−execute_ :: Connection -> Query -> IO Int64
236−execute_ conn q@(Query stmt) = do
237− result <- exec conn stmt
238− finishExecute conn q result
239−
240239-- | Execute a multi-row @INSERT@, @UPDATE@, or other SQL query that is not
241240-- expected to return results.
242241--
@@ -249,44 +248,6 @@ executeMany conn q qs = do
249248 result <- exec conn =<< formatMany conn q qs
250249 finishExecute conn q result
251250
252−finishExecute :: Connection -> Query -> PQ.Result -> IO Int64
253−finishExecute _conn q result = do
254− status <- PQ.resultStatus result
255− case status of
256− PQ.CommandOk -> do
257− ncols <- PQ.nfields result
258− if ncols /= 0
259− then throwIO $ QueryError ("execute resulted in " ++ show ncols ++
260− "-column result") q
261− else do
262− nstr <- PQ.cmdTuples result
263− return $ case nstr of
264− Nothing -> 0 -- is this appropriate?
265− Just str -> toInteger str
266− PQ.TuplesOk -> do
267− ncols <- PQ.nfields result
268− throwIO $ QueryError ("execute resulted in " ++ show ncols ++
269− "-column result") q
270− PQ.CopyIn -> fail "FIXME: postgresql-simple does not currently support COPY IN"
271− PQ.CopyOut -> fail "FIXME: postgresql-simple does not currently support COPY OUT"
272− _ -> do
273− errormsg <- maybe "" id <$> PQ.resultErrorMessage result
274− statusmsg <- PQ.resStatus status
275− state <- maybe "" id <$> PQ.resultErrorField result PQ.DiagSqlstate
276− throwIO $ SqlError { sqlState = state
277− , sqlNativeError = fromEnum status
278− , sqlErrorMsg = B.concat [ "execute: ", statusmsg
279− , ": ", errormsg ]}
280− where
281− toInteger str = B.foldl' delta 0 str
282− where
283− delta acc c =
284− if '0' <= c && c <= '9'
285− then 10 * acc + fromIntegral (ord c - ord '0')
286− else error ("finishExecute: not an int: " ++ B.unpack str)
287−
288−
289−
290251-- | Perform a @SELECT@ or other SQL query that is expected to return
291252-- results. All results are retrieved and converted before this
292253-- function returns.
@@ -467,6 +428,8 @@ finishQuery :: (FromRow r) => Connection -> Query -> PQ.Result -> IO [r]
467428finishQuery conn q result = do
468429 status <- PQ.resultStatus result
469430 case status of
431+ PQ.EmptyQuery ->
432+ throwIO $ QueryError "query: Empty query" q
470433 PQ.CommandOk -> do
471434 throwIO $ QueryError "query resulted in a command response" q
472435 PQ.TuplesOk -> do
@@ -494,6 +457,13 @@ finishQuery conn q result = do
494457 Errors [] -> throwIO $ ConversionFailed "" "" "unknown error"
495458 Errors [x] -> throwIO x
496459 Errors xs -> throwIO $ ManyErrors xs
460+ PQ.CopyOut ->
461+ throwIO $ QueryError "query: COPY TO is not supported" q
462+ PQ.CopyIn ->
463+ throwIO $ QueryError "query: COPY FROM is not supported" q
464+ PQ.BadResponse -> throwResultError "query" result status
465+ PQ.NonfatalError -> throwResultError "query" result status
466+ PQ.FatalError -> throwResultError "query" result status
497467
498468ellipsis :: ByteString -> ByteString
499469ellipsis bs
@@ -611,9 +581,10 @@ fmtError msg q xs = throw FormatError {
611581 , fmtQuery = q
612582 , fmtParams = map twiddle xs
613583 }
614− where twiddle (Plain b) = toByteString b
615− twiddle (Escape s) = s
616− twiddle (Many ys) = B.concat (map twiddle ys)
584+ where twiddle (Plain b) = toByteString b
585+ twiddle (Escape s) = s
586+ twiddle (EscapeBytea s) = s
587+ twiddle (Many ys) = B.concat (map twiddle ys)
617588
618589-- $use
619590--