@@ -25,6 +25,10 @@ import Control.Applicative
2525import Control.Exception
2626import Control.Concurrent.MVar
2727import Data.ByteString(ByteString)
28+import qualified Data.ByteString as B
29+import qualified Data.ByteString.Char8 as B8
30+import Data.Char (ord)
31+import Data.Int (Int64)
2832import qualified Data.IntMap as IntMap
2933import Data.String
3034import Data.Typeable
@@ -33,6 +37,7 @@ import Database.PostgreSQL.LibPQ(Oid(..))
3337import qualified Database.PostgreSQL.LibPQ as PQ
3438import Database.PostgreSQL.Simple.BuiltinTypes (BuiltinType)
3539import Database.PostgreSQL.Simple.Ok
40+import Database.PostgreSQL.Simple.Types (Query(..))
3641import Control.Monad.Trans.State.Strict
3742import Control.Monad.Trans.Reader
3843import qualified Data.Vector as V
@@ -86,6 +91,15 @@ data SqlError = SqlError {
8691
8792instance Exception SqlError
8893
94+-- | Exception thrown if 'query' is used to perform an @INSERT@-like
95+-- operation, or 'execute' is used to perform a @SELECT@-like operation.
96+data QueryError = QueryError {
97+ qeMessage :: String
98+ , qeQuery :: Query
99+ } deriving (Eq, Show, Typeable)
100+
101+instance Exception QueryError
102+
89103data ConnectInfo = ConnectInfo {
90104 connectHost :: String
91105 , connectPort :: Word16
@@ -138,6 +152,8 @@ connectPostgreSQL connstr = do
138152 PQ.ConnectionOk -> do
139153 connectionHandle <- newMVar conn
140154 connectionObjects <- newMVar (IntMap.empty)
155+ let wconn = Connection{..}
156+ _ <- execute_ wconn "SET standard_conforming_strings TO on"
141157 return Connection{..}
142158 _ -> do
143159 msg <- maybe "connectPostgreSQL error" id <$> PQ.errorMessage conn
@@ -198,6 +214,49 @@ exec conn sql =
198214 Just res -> do
199215 return res
200216
217+-- | A version of 'execute' that does not perform query substitution.
218+execute_ :: Connection -> Query -> IO Int64
219+execute_ conn q@(Query stmt) = do
220+ result <- exec conn stmt
221+ finishExecute conn q result
222+
223+finishExecute :: Connection -> Query -> PQ.Result -> IO Int64
224+finishExecute _conn q result = do
225+ status <- PQ.resultStatus result
226+ case status of
227+ PQ.CommandOk -> do
228+ ncols <- PQ.nfields result
229+ if ncols /= 0
230+ then throwIO $ QueryError ("execute resulted in " ++ show ncols ++
231+ "-column result") q
232+ else do
233+ nstr <- PQ.cmdTuples result
234+ return $ case nstr of
235+ Nothing -> 0 -- is this appropriate?
236+ Just str -> toInteger str
237+ PQ.TuplesOk -> do
238+ ncols <- PQ.nfields result
239+ throwIO $ QueryError ("execute resulted in " ++ show ncols ++
240+ "-column result") q
241+ PQ.CopyIn -> fail "FIXME: postgresql-simple does not currently support COPY IN"
242+ PQ.CopyOut -> fail "FIXME: postgresql-simple does not currently support COPY OUT"
243+ _ -> do
244+ errormsg <- maybe "" id <$> PQ.resultErrorMessage result
245+ statusmsg <- PQ.resStatus status
246+ state <- maybe "" id <$> PQ.resultErrorField result PQ.DiagSqlstate
247+ throwIO $ SqlError { sqlState = state
248+ , sqlNativeError = fromEnum status
249+ , sqlErrorMsg = B.concat [ "execute: ", statusmsg
250+ , ": ", errormsg ]}
251+ where
252+ toInteger str = B8.foldl' delta 0 str
253+ where
254+ delta acc c =
255+ if '0' <= c && c <= '9'
256+ then 10 * acc + fromIntegral (ord c - ord '0')
257+ else error ("finishExecute: not an int: " ++ B8.unpack str)
258+
259+
201260disconnectedError :: SqlError
202261disconnectedError = SqlError {
203262 sqlNativeError = -1,