@@ -113,7 +113,6 @@ import Control.Exception
113 ( Exception, onException, throw, throwIO, finally )
113 ( Exception, onException, throw, throwIO, finally )
114import Control.Monad (foldM)
114import Control.Monad (foldM)
115import Data.ByteString (ByteString)
115import Data.ByteString (ByteString)
116−import Data.Char(ord)
117import Data.Int (Int64)
116import Data.Int (Int64)
118import qualified Data.IntMap as IntMap
117import qualified Data.IntMap as IntMap
119import Data.List (intersperse)
118import Data.List (intersperse)
@@ -133,6 +132,8 @@ import Database.PostgreSQL.Simple.Internal as Base
133import qualified Database.PostgreSQL.LibPQ as PQ
132import qualified Database.PostgreSQL.LibPQ as PQ
134import Text.Regex.PCRE.Light (compile, caseless, match)
133import Text.Regex.PCRE.Light (compile, caseless, match)
135import qualified Data.ByteString.Char8 as B
134import qualified Data.ByteString.Char8 as B
135+import qualified Data.Text as T
136+import qualified Data.Text.Encoding as TE
136import qualified Data.Vector as V
137import qualified Data.Vector as V
137import Control.Monad.Trans.Reader
138import Control.Monad.Trans.Reader
138import Control.Monad.Trans.State.Strict
139import Control.Monad.Trans.State.Strict
@@ -148,15 +149,6 @@ data FormatError = FormatError {
149instance Exception FormatError
150instance Exception FormatError
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 {
156− } deriving (Eq, Show, Typeable)
158−instance Exception QueryError
160-- | Format a query string.
152-- | Format a query string.
162-- This function is exposed to help with debugging and logging. Do not
154-- This function is exposed to help with debugging and logging. Do not
@@ -202,16 +194,29 @@ formatMany conn q@(Query template) qs = do
202 \([^?]*)$"
194 \([^?]*)$"
203 [caseless]
195 [caseless]
205−escapeStringConn :: Connection -> ByteString -> IO (Maybe ByteString)
197+escapeStringConn :: Connection -> ByteString -> IO (Either ByteString ByteString)
206−escapeStringConn conn s = withConnection conn $ \c -> do
198+escapeStringConn conn s =
207− PQ.escapeStringConn c s
199+ withConnection conn $ \c ->
200+ PQ.escapeStringConn c s >>= checkError c
202+escapeByteaConn :: Connection -> ByteString -> IO (Either ByteString ByteString)
203+escapeByteaConn conn s =
204+ withConnection conn $ \c ->
205+ PQ.escapeByteaConn c s >>= checkError c
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
209buildQuery :: Connection -> Query -> ByteString -> [Action] -> IO Builder
211buildQuery :: Connection -> Query -> ByteString -> [Action] -> IO Builder
210buildQuery conn q template xs = zipParams (split template) <$> mapM sub xs
212buildQuery conn q template xs = zipParams (split template) <$> mapM sub xs
213+ where quote = either (\msg -> fmtError (utf8ToString msg) q xs)
211− where quote = inQuotes . fromByteString . maybe undefined id
214+ (inQuotes . fromByteString)
215+ utf8ToString = T.unpack . TE.decodeUtf8
212− sub (Plain b) = pure b
216+ sub (Plain b) = pure b
213− sub (Escape s) = quote <$> escapeStringConn conn s
217+ sub (Escape s) = quote <$> escapeStringConn conn s
218+ sub (EscapeBytea s) = quote <$> escapeByteaConn conn s
214− sub (Many ys) = mconcat <$> mapM sub ys
219+ sub (Many ys) = mconcat <$> mapM sub ys
215 split s = fromByteString h : if B.null t then [] else split (B.tail t)
220 split s = fromByteString h : if B.null t then [] else split (B.tail t)
216 where (h,t) = B.break (=='?') s
221 where (h,t) = B.break (=='?') s
217 zipParams (t:ts) (p:ps) = t `mappend` p `mappend` zipParams ts ps
222 zipParams (t:ts) (p:ps) = t `mappend` p `mappend` zipParams ts ps
@@ -231,12 +236,6 @@ execute conn template qs = do
231 result <- exec conn =<< formatQuery conn template qs
236 result <- exec conn =<< formatQuery conn template qs
232 finishExecute conn template result
237 finishExecute conn template result
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
240-- | Execute a multi-row @INSERT@, @UPDATE@, or other SQL query that is not
239-- | Execute a multi-row @INSERT@, @UPDATE@, or other SQL query that is not
241-- expected to return results.
240-- expected to return results.
@@ -249,44 +248,6 @@ executeMany conn q qs = do
249 result <- exec conn =<< formatMany conn q qs
248 result <- exec conn =<< formatMany conn q qs
250 finishExecute conn q result
249 finishExecute conn q result
252−finishExecute :: Connection -> Query -> PQ.Result -> IO Int64
253−finishExecute _conn q result = do
254− status <- PQ.resultStatus result
257− ncols <- PQ.nfields result
259− then throwIO $ QueryError ("execute resulted in " ++ show ncols ++
262− nstr <- PQ.cmdTuples result
263− return $ case nstr of
264− Nothing -> 0 -- is this appropriate?
265− Just str -> toInteger str
267− ncols <- PQ.nfields result
268− throwIO $ QueryError ("execute resulted in " ++ show ncols ++
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"
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
281− toInteger str = B.foldl' delta 0 str
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)
290-- | Perform a @SELECT@ or other SQL query that is expected to return
251-- | Perform a @SELECT@ or other SQL query that is expected to return
291-- results. All results are retrieved and converted before this
252-- results. All results are retrieved and converted before this
292-- function returns.
253-- function returns.
@@ -467,6 +428,8 @@ finishQuery :: (FromRow r) => Connection -> Query -> PQ.Result -> IO [r]
467finishQuery conn q result = do
428finishQuery conn q result = do
468 status <- PQ.resultStatus result
429 status <- PQ.resultStatus result
469 case status of
430 case status of
432+ throwIO $ QueryError "query: Empty query" q
470 PQ.CommandOk -> do
433 PQ.CommandOk -> do
471 throwIO $ QueryError "query resulted in a command response" q
434 throwIO $ QueryError "query resulted in a command response" q
472 PQ.TuplesOk -> do
435 PQ.TuplesOk -> do
@@ -494,6 +457,13 @@ finishQuery conn q result = do
494 Errors [] -> throwIO $ ConversionFailed "" "" "unknown error"
457 Errors [] -> throwIO $ ConversionFailed "" "" "unknown error"
495 Errors [x] -> throwIO x
458 Errors [x] -> throwIO x
496 Errors xs -> throwIO $ ManyErrors xs
459 Errors xs -> throwIO $ ManyErrors xs
461+ throwIO $ QueryError "query: COPY TO is not supported" q
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
498ellipsis :: ByteString -> ByteString
468ellipsis :: ByteString -> ByteString
499ellipsis bs
469ellipsis bs
@@ -611,9 +581,10 @@ fmtError msg q xs = throw FormatError {
611 , fmtQuery = q
581 , fmtQuery = q
612 , fmtParams = map twiddle xs
582 , fmtParams = map twiddle xs
614− where twiddle (Plain b) = toByteString b
584+ where twiddle (Plain b) = toByteString b
615− twiddle (Escape s) = s
585+ twiddle (Escape s) = s
586+ twiddle (EscapeBytea s) = s
616− twiddle (Many ys) = B.concat (map twiddle ys)
587+ twiddle (Many ys) = B.concat (map twiddle ys)