git.iko.soy/typeable/postgresql-simple
on master help sign in
Initial Commit
commit32c3648b5feca7e3562bd93abfa84c32ad76714a authorLeon P Smith <leon@melding-monads.com> dateSun 25 Dec 2011, 00:49 (15 years ago)
15 files changed+2578
.gitignore+10 LICENSE+62 Setup.hs+2 postgresql-simple.cabal+62 src/Database/PostgreSQL/Simple.hs+753 src/Database/PostgreSQL/Simple/BuiltinTypes.hs+403 src/Database/PostgreSQL/Simple/Field.hs+15 src/Database/PostgreSQL/Simple/Implementation.hs+187 src/Database/PostgreSQL/Simple/Param.hs+205 src/Database/PostgreSQL/Simple/QueryParams.hs+85 src/Database/PostgreSQL/Simple/QueryResults.hs+219 src/Database/PostgreSQL/Simple/Result.hs+252 src/Database/PostgreSQL/Simple/Types.hs+108 tools/GenBuiltinTypes.hs+185 tools/StringsQQ.hs+30
.gitignorenew file+10
@@ -0,0 +1,10 @@
1+.hg/
2+_darcs/
3+*~
4+dist/
5+*.prof
6+*.hi
7+*.o
8+\#*\#
9+.#*
10+postgresql-simple-*.tar.gz
LICENSEnew file+62
@@ -0,0 +1,62 @@
1+Copyright (c) 2011, Leon P Smith
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions are met:
7+
8+ * Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+
11+ * Redistributions in binary form must reproduce the above
12+ copyright notice, this list of conditions and the following
13+ disclaimer in the documentation and/or other materials provided
14+ with the distribution.
15+
16+ * Neither the name of Leon P Smith nor the names of other
17+ contributors may be used to endorse or promote products derived
18+ from this software without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
32+
33+Copyright (c) 2011, MailRank, Inc.
34+
35+All rights reserved.
36+
37+Redistribution and use in source and binary forms, with or without
38+modification, are permitted provided that the following conditions
39+are met:
40+
41+1. Redistributions of source code must retain the above copyright
42+ notice, this list of conditions and the following disclaimer.
43+
44+2. Redistributions in binary form must reproduce the above copyright
45+ notice, this list of conditions and the following disclaimer in the
46+ documentation and/or other materials provided with the distribution.
47+
48+3. Neither the name of the author nor the names of his contributors
49+ may be used to endorse or promote products derived from this software
50+ without specific prior written permission.
51+
52+THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
53+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
54+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
55+DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
56+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
57+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
58+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
59+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
60+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
61+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
62+POSSIBILITY OF SUCH DAMAGE.
Setup.hsnew file+2
@@ -0,0 +1,2 @@
1+import Distribution.Simple
2+main = defaultMain
postgresql-simple.cabalnew file+62
@@ -0,0 +1,62 @@
1+Name: postgresql-simple
2+Version: 0.0
3+Synopsis: Mid-Level PostgreSQL client library
4+License: BSD3
5+License-file: LICENSE
6+Author: Bryan O'Sullivan, Leon P Smith
7+Maintainer: Leon P Smith <leon@melding-monads.com>
8+Copyright: (c) 2011 MailRank, Inc.
9+ (c) 2011 Leon P Smith
10+Category: Database
11+Build-type: Simple
12+
13+-- Extra files to be distributed with the package, such as examples or
14+-- a README.
15+-- Extra-source-files:
16+
17+-- Constraint on the version of Cabal needed to build this package.
18+Cabal-version: >=1.2
19+
20+Library
21+ hs-source-dirs: src
22+ -- Modules exported by the library.
23+ Exposed-modules:
24+ Database.PostgreSQL.Simple
25+ Database.PostgreSQL.Simple.Param
26+ Database.PostgreSQL.Simple.QueryParams
27+ Database.PostgreSQL.Simple.Result
28+ Database.PostgreSQL.Simple.Field
29+ Database.PostgreSQL.Simple.QueryResults
30+ Database.PostgreSQL.Simple.BuiltinTypes
31+ Database.PostgreSQL.Simple.Types
32+ Other-modules:
33+ Database.PostgreSQL.Simple.Implementation
34+
35+ Build-depends:
36+ attoparsec >= 0.8.5.3,
37+ base < 5,
38+ base16-bytestring,
39+ blaze-builder,
40+ blaze-textual,
41+ bytestring >= 0.9,
42+ containers,
43+ postgresql-libpq,
44+ pcre-light,
45+ old-locale,
46+ text >= 0.11.0.2,
47+ time,
48+ transformers >= 0.2.0.0
49+ -- Modules not exported by this package.
50+ -- Other-modules:
51+
52+ -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.
53+ -- Build-tools:
54+
55+source-repository head
56+ type: git
57+ location: http://github.com/lpsmith/postgresql-simple
58+
59+source-repository this
60+ type: git
61+ location: http://github.com/lpsmith/postgresql-simple
62+ tag: v0.0
src/Database/PostgreSQL/Simple.hsnew file+753
@@ -0,0 +1,753 @@
1+{-# LANGUAGE BangPatterns, DeriveDataTypeable, OverloadedStrings, DoAndIfThenElse, RecordWildCards, NamedFieldPuns #-}
2+
3+-- |
4+-- Module: Database.PostgreSQL.Simple
5+-- Copyright: (c) 2011 MailRank, Inc.
6+-- (c) 2011 Leon P Smith
7+-- License: BSD3
8+-- Maintainer: Leon P Smith <leon@melding-monads.com>
9+-- Stability: experimental
10+-- Portability: portable
11+--
12+-- A mid-level client library for the MySQL database, aimed at ease of
13+-- use and high performance.
14+
15+module Database.PostgreSQL.Simple
16+ (
17+ -- * Writing queries
18+ -- $use
19+
20+ -- ** The Query type
21+ -- $querytype
22+
23+ -- ** Parameter substitution
24+ -- $subst
25+
26+ -- *** Type inference
27+ -- $inference
28+
29+ -- ** Substituting a single parameter
30+ -- $only_param
31+
32+ -- ** Representing a list of values
33+ -- $in
34+
35+ -- ** Modifying multiple rows at once
36+ -- $many
37+
38+ -- * Extracting results
39+ -- $result
40+
41+ -- ** Handling null values
42+ -- $null
43+
44+ -- ** Type conversions
45+ -- $types
46+
47+ -- * Types
48+ Base.ConnectInfo(..)
49+ , Connection
50+ , Query
51+ , In(..)
52+ , Binary(..)
53+ , Only(..)
54+ -- ** Exceptions
55+ , FormatError(fmtMessage, fmtQuery, fmtParams)
56+ , QueryError(qeMessage, qeQuery)
57+ , ResultError(errSQLType, errHaskellType, errMessage)
58+ -- * Connection management
59+ , Base.connect
60+ , Base.connectPostgreSQL
61+ , Base.postgreSQLConnectionString
62+ , Base.defaultConnectInfo
63+ , Base.close
64+ -- * Queries that return results
65+ , query
66+ , query_
67+{--
68+ -- * Queries that stream results
69+ , fold
70+ , fold_
71+ , forEach
72+ , forEach_
73+--}
74+ -- * Statements that do not return results
75+ , execute
76+ , execute_
77+ , executeMany
78+-- , Base.insertID
79+ -- * Transaction handling
80+ , withTransaction
81+-- , Base.autocommit
82+ , begin
83+ , commit
84+ , rollback
85+ -- * Helper functions
86+ , formatMany
87+ , formatQuery
88+ ) where
89+
90+import Blaze.ByteString.Builder (Builder, fromByteString, toByteString)
91+import Blaze.ByteString.Builder.Char8 (fromChar)
92+import Control.Applicative ((<$>), pure)
93+import Control.Concurrent.MVar
94+import Control.Exception (Exception, bracket, onException, throw, throwIO)
95+import Control.Monad (forM)
96+import Control.Monad.Fix (fix)
97+import Data.ByteString (ByteString)
98+-- import qualified Data.ByteString as B (unpack)
99+import Data.Char(ord)
100+import Data.Int (Int64)
101+import qualified Data.IntMap as IntMap
102+import Data.List (intersperse)
103+import Data.Monoid (mappend, mconcat)
104+import Data.Typeable (Typeable)
105+--import Database.MySQL.Base (Connection, Result)
106+--import Database.MySQL.Base.Types (Field)
107+import Database.PostgreSQL.Simple.BuiltinTypes (oid2builtin, builtin2typname)
108+import Database.PostgreSQL.Simple.Param (Action(..), inQuotes)
109+import Database.PostgreSQL.Simple.QueryParams (QueryParams(..))
110+import Database.PostgreSQL.Simple.Result (ResultError(..))
111+import Database.PostgreSQL.Simple.QueryResults (QueryResults(..))
112+-- import Database.PostgreSQL.Simple.Result (ResultError(..))
113+import Database.PostgreSQL.Simple.Types (Binary(..), In(..), Only(..), Query(..))
114+import Database.PostgreSQL.Simple.Implementation as Base
115+import qualified Database.PostgreSQL.LibPQ as PQ
116+import Text.Regex.PCRE.Light (compile, caseless, match)
117+import qualified Data.ByteString.Char8 as B
118+--import qualified Database.MySQL.Base as Base
119+
120+-- | Exception thrown if a 'Query' could not be formatted correctly.
121+-- This may occur if the number of \'@?@\' characters in the query
122+-- string does not match the number of parameters provided.
123+data FormatError = FormatError {
124+ fmtMessage :: String
125+ , fmtQuery :: Query
126+ , fmtParams :: [ByteString]
127+ } deriving (Eq, Show, Typeable)
128+
129+instance Exception FormatError
130+
131+-- | Exception thrown if 'query' is used to perform an @INSERT@-like
132+-- operation, or 'execute' is used to perform a @SELECT@-like operation.
133+data QueryError = QueryError {
134+ qeMessage :: String
135+ , qeQuery :: Query
136+ } deriving (Eq, Show, Typeable)
137+
138+instance Exception QueryError
139+
140+-- | Format a query string.
141+--
142+-- This function is exposed to help with debugging and logging. Do not
143+-- use it to prepare queries for execution.
144+--
145+-- String parameters are escaped according to the character set in use
146+-- on the 'Connection'.
147+--
148+-- Throws 'FormatError' if the query string could not be formatted
149+-- correctly.
150+formatQuery :: QueryParams q => Connection -> Query -> q -> IO ByteString
151+formatQuery conn q@(Query template) qs
152+ | null xs && '?' `B.notElem` template = return template
153+ | otherwise = toByteString <$> buildQuery conn q template xs
154+ where xs = renderParams qs
155+
156+-- | Format a query string with a variable number of rows.
157+--
158+-- This function is exposed to help with debugging and logging. Do not
159+-- use it to prepare queries for execution.
160+--
161+-- The query string must contain exactly one substitution group,
162+-- identified by the SQL keyword \"@VALUES@\" (case insensitive)
163+-- followed by an \"@(@\" character, a series of one or more \"@?@\"
164+-- characters separated by commas, and a \"@)@\" character. White
165+-- space in a substitution group is permitted.
166+--
167+-- Throws 'FormatError' if the query string could not be formatted
168+-- correctly.
169+formatMany :: (QueryParams q) => Connection -> Query -> [q] -> IO ByteString
170+formatMany _ q [] = fmtError "no rows supplied" q []
171+formatMany conn q@(Query template) qs = do
172+ case match re template [] of
173+ Just [_,before,qbits,after] -> do
174+ bs <- mapM (buildQuery conn q qbits . renderParams) qs
175+ return . toByteString . mconcat $ fromByteString before :
176+ intersperse (fromChar ',') bs ++
177+ [fromByteString after]
178+ _ -> error "foo"
179+ where
180+ re = compile "^([^?]+\\bvalues\\s*)\
181+ \(\\(\\s*[?](?:\\s*,\\s*[?])*\\s*\\))\
182+ \([^?]*)$"
183+ [caseless]
184+
185+escapeStringConn conn s = withConnection conn $ \c -> do
186+ PQ.escapeStringConn c s
187+
188+buildQuery :: Connection -> Query -> ByteString -> [Action] -> IO Builder
189+buildQuery conn q template xs = zipParams (split template) <$> mapM sub xs
190+ where quote = inQuotes . fromByteString . maybe undefined id
191+ sub (Plain b) = pure b
192+ sub (Escape s) = quote <$> escapeStringConn conn s
193+ sub (Many ys) = mconcat <$> mapM sub ys
194+ split s = fromByteString h : if B.null t then [] else split (B.tail t)
195+ where (h,t) = B.break (=='?') s
196+ zipParams (t:ts) (p:ps) = t `mappend` p `mappend` zipParams ts ps
197+ zipParams [t] [] = t
198+ zipParams _ _ = fmtError (show (B.count '?' template) ++
199+ " '?' characters, but " ++
200+ show (length xs) ++ " parameters") q xs
201+
202+-- | Execute an @INSERT@, @UPDATE@, or other SQL query that is not
203+-- expected to return results.
204+--
205+-- Returns the number of rows affected.
206+--
207+-- Throws 'FormatError' if the query could not be formatted correctly.
208+execute :: (QueryParams q) => Connection -> Query -> q -> IO Int64
209+execute conn template qs = do
210+ result <- exec conn =<< formatQuery conn template qs
211+ finishExecute conn template result
212+
213+-- | A version of 'execute' that does not perform query substitution.
214+execute_ :: Connection -> Query -> IO Int64
215+execute_ conn q@(Query stmt) = do
216+ result <- exec conn stmt
217+ finishExecute conn q result
218+
219+-- | Execute a multi-row @INSERT@, @UPDATE@, or other SQL query that is not
220+-- expected to return results.
221+--
222+-- Returns the number of rows affected.
223+--
224+-- Throws 'FormatError' if the query could not be formatted correctly.
225+executeMany :: (QueryParams q) => Connection -> Query -> [q] -> IO Int64
226+executeMany _ _ [] = return 0
227+executeMany conn q qs = do
228+ result <- exec conn =<< formatMany conn q qs
229+ finishExecute conn q result
230+
231+finishExecute :: Connection -> Query -> PQ.Result -> IO Int64
232+finishExecute conn q result = do
233+ status <- PQ.resultStatus result
234+ case status of
235+ PQ.EmptyQuery -> fail "empty query" -- FIXME: error message
236+ PQ.CommandOk -> do
237+ ncols <- PQ.nfields result
238+ if ncols /= 0
239+ then throwIO $ QueryError ("execute resulted in " ++ show ncols ++
240+ "-column result") q
241+ else do
242+ nstr <- PQ.cmdTuples result
243+ return $ case nstr of
244+ Nothing -> 0 -- is this appropriate?
245+ Just str -> toInteger str
246+ PQ.TuplesOk -> do
247+ ncols <- PQ.nfields result
248+ throwIO $ QueryError ("execute resulted in " ++ show ncols ++
249+ "-column result") q
250+ PQ.CopyIn -> fail "FIXME: postgresql-simple does not currently support COPY IN"
251+ PQ.CopyOut -> fail "FIXME: postgresql-simple does not currently support COPY OUT"
252+ _ -> do
253+ errormsg <- maybe "" id <$> PQ.resultErrorMessage result
254+ statusmsg <- PQ.resStatus status
255+ state <- maybe "" id <$> PQ.resultErrorField result PQ.DiagSqlstate
256+ throw $ SqlError { sqlState = state
257+ , sqlNativeError = 0 -- FIXME
258+ , sqlErrorMsg = B.concat [ "execute: ", statusmsg
259+ , ": ", errormsg ]}
260+ where
261+ toInteger str = B.foldl' delta 0 str
262+ where
263+ delta acc c =
264+ if '0' <= c && c <= '9'
265+ then 10 * acc + fromIntegral (ord c - ord '0')
266+ else error ("finishExecute: not an int: " ++ B.unpack str)
267+
268+
269+
270+-- | Perform a @SELECT@ or other SQL query that is expected to return
271+-- results. All results are retrieved and converted before this
272+-- function returns.
273+--
274+-- When processing large results, this function will consume a lot of
275+-- client-side memory. Consider using 'fold' instead.
276+--
277+-- Exceptions that may be thrown:
278+--
279+-- * 'FormatError': the query string could not be formatted correctly.
280+--
281+-- * 'QueryError': the result contains no columns (i.e. you should be
282+-- using 'execute' instead of 'query').
283+--
284+-- * 'ResultError': result conversion failed.
285+query :: (QueryParams q, QueryResults r)
286+ => Connection -> Query -> q -> IO [r]
287+query conn template qs = do
288+ result <- exec conn =<< formatQuery conn template qs
289+ finishQuery conn template result
290+
291+-- | A version of 'query' that does not perform query substitution.
292+query_ :: (QueryResults r) => Connection -> Query -> IO [r]
293+query_ conn q@(Query que) = do
294+ result <- exec conn que
295+ finishQuery conn q result
296+{--
297+-- | Perform a @SELECT@ or other SQL query that is expected to return
298+-- results. Results are streamed incrementally from the server, and
299+-- consumed via a left fold.
300+--
301+-- The result consumer must be carefully written to execute
302+-- quickly. If the consumer is slow, server resources will be tied up,
303+-- and other clients may not be able to update the tables from which
304+-- the results are being streamed.
305+--
306+-- When dealing with small results, it may be simpler (and perhaps
307+-- faster) to use 'query' instead.
308+--
309+-- This fold is /not/ strict. The stream consumer is responsible for
310+-- forcing the evaluation of its result to avoid space leaks.
311+--
312+-- Exceptions that may be thrown:
313+--
314+-- * 'FormatError': the query string could not be formatted correctly.
315+--
316+-- * 'QueryError': the result contains no columns (i.e. you should be
317+-- using 'execute' instead of 'query').
318+--
319+-- * 'ResultError': result conversion failed.
320+fold :: (QueryParams q, QueryResults r) =>
321+ Connection
322+ -> Query -- ^ Query template.
323+ -> q -- ^ Query parameters.
324+ -> a -- ^ Initial state for result consumer.
325+ -> (a -> r -> IO a) -- ^ Result consumer.
326+ -> IO a
327+fold conn template qs z f = do
328+ Base.query conn =<< formatQuery conn template qs
329+ finishFold conn template z f
330+--}
331+{--
332+-- | A version of 'fold' that does not perform query substitution.
333+fold_ :: (QueryResults r) =>
334+ Connection
335+ -> Query -- ^ Query.
336+ -> a -- ^ Initial state for result consumer.
337+ -> (a -> r -> IO a) -- ^ Result consumer.
338+ -> IO a
339+fold_ conn q@(Query que) z f = do
340+ Base.query conn que
341+ finishFold conn q z f
342+--}
343+{--
344+-- | A version of 'fold' that does not transform a state value.
345+forEach :: (QueryParams q, QueryResults r) =>
346+ Connection
347+ -> Query -- ^ Query template.
348+ -> q -- ^ Query parameters.
349+ -> (r -> IO ()) -- ^ Result consumer.
350+ -> IO ()
351+forEach conn template qs = fold conn template qs () . const
352+{-# INLINE forEach #-}
353+
354+-- | A version of 'forEach' that does not perform query substitution.
355+forEach_ :: (QueryResults r) =>
356+ Connection
357+ -> Query -- ^ Query template.
358+ -> (r -> IO ()) -- ^ Result consumer.
359+ -> IO ()
360+forEach_ conn template = fold_ conn template () . const
361+{-# INLINE forEach_ #-}
362+--}
363+
364+forM' :: (Ord n, Num n) => n -> n -> (n -> IO a) -> IO [a]
365+forM' lo hi m = loop hi []
366+ where
367+ loop !n !as
368+ | n < lo = return as
369+ | otherwise = do
370+ a <- m n
371+ loop (n-1) (a:as)
372+
373+finishQuery :: (QueryResults r) => Connection -> Query -> PQ.Result -> IO [r]
374+finishQuery conn q result = do
375+ status <- PQ.resultStatus result
376+ case status of
377+ PQ.EmptyQuery -> fail "empty query" -- FIXME: error message
378+ PQ.CommandOk -> do
379+ throwIO $ QueryError "query resulted in a command response" q
380+ PQ.TuplesOk -> do
381+ ncols <- PQ.nfields result
382+ fields <- forM' 0 (ncols-1) $ \column -> do
383+ type_oid <- PQ.ftype result column
384+ typename <- getTypename conn type_oid
385+ return Field{..}
386+ nrows <- PQ.ntuples result
387+ forM' 0 (nrows-1) $ \row -> do
388+ values <- forM' 0 (ncols-1) (\col -> PQ.getvalue' result row col)
389+ case convertResults fields values of
390+ Fail err -> throwIO err
391+ Success a -> return a
392+ PQ.CopyIn -> fail "FIXME: postgresql-simple does not currently support COPY IN"
393+ PQ.CopyOut -> fail "FIXME: postgresql-simple does not currently support COPY OUT"
394+ _ -> do
395+ errormsg <- maybe "" id <$> PQ.resultErrorMessage result
396+ statusmsg <- PQ.resStatus status
397+ state <- maybe "" id <$> PQ.resultErrorField result PQ.DiagSqlstate
398+ throw $ SqlError { sqlState = state
399+ , sqlNativeError = 0 -- FIXME
400+ , sqlErrorMsg = B.concat [ "execute: ", statusmsg
401+ , ": ", errormsg ]}
402+
403+{--
404+withResult (Base.storeResult conn) q $ \r fs -> do
405+ status <-
406+/ flip fix [] $ \loop acc -> do
407+ row <- Base.fetchRow r
408+ case row of
409+ [] -> return (reverse acc)
410+ _ -> let !c = convertResults fs row
411+ in loop (c:acc)
412+--}
413+{--
414+finishFold :: (QueryResults r) =>
415+ Connection -> Query -> a -> (a -> r -> IO a) -> IO a
416+finishFold conn q z0 f = withResult (Base.useResult conn) q $ \r fs ->
417+ flip fix z0 $ \loop z -> do
418+ row <- Base.fetchRow r
419+ case row of
420+ [] -> return z
421+ _ -> (f z $! convertResults fs row) >>= loop
422+--}
423+{--
424+withResult :: (IO Result) -> Query -> (Result -> [Field] -> IO a) -> IO a
425+withResult fetchResult q act = bracket fetchResult Base.freeResult $ \r -> do
426+ ncols <- Base.fieldCount (Right r)
427+ if ncols == 0
428+ then throwIO $ QueryError "query resulted in zero-column result" q
429+ else act r =<< Base.fetchFields r
430+--}
431+-- | Execute an action inside a SQL transaction.
432+--
433+-- This function initiates a transaction with a \"@begin
434+-- transaction@\" statement, then executes the supplied action. If
435+-- the action succeeds, the transaction will be completed with
436+-- 'Base.commit' before this function returns.
437+--
438+-- If the action throws /any/ kind of exception (not just a
439+-- MySQL-related exception), the transaction will be rolled back using
440+-- 'Base.rollback', then the exception will be rethrown.
441+withTransaction :: Connection -> IO a -> IO a
442+withTransaction conn act = do
443+ _ <- begin conn
444+ r <- act `onException` rollback conn
445+ commit conn
446+ return r
447+
448+-- | Rollback a transaction.
449+-- rollback :: (MonadCatchIO m,MonadIO m) => Connection -> m ()
450+rollback :: Connection -> IO ()
451+rollback conn = do
452+ _ <- execute_ conn "ABORT;"
453+ return ()
454+
455+-- | Commit a transaction.
456+-- commit :: (MonadCatchIO m,MonadIO m) => Connection -> m ()
457+commit :: Connection -> IO ()
458+commit conn = do
459+ _ <- execute_ conn "COMMIT;"
460+ return ()
461+
462+-- | Begin a transaction.
463+-- begin :: (MonadCatchIO m,MonadIO m) => Connection -> m ()
464+begin :: Connection -> IO ()
465+begin conn = do
466+ _ <- execute_ conn "BEGIN;"
467+ return ()
468+
469+fmtError :: String -> Query -> [Action] -> a
470+fmtError msg q xs = throw FormatError {
471+ fmtMessage = msg
472+ , fmtQuery = q
473+ , fmtParams = map twiddle xs
474+ }
475+ where twiddle (Plain b) = toByteString b
476+ twiddle (Escape s) = s
477+ twiddle (Many ys) = B.concat (map twiddle ys)
478+
479+-- $use
480+--
481+-- SQL-based applications are somewhat notorious for their
482+-- susceptibility to attacks through the injection of maliciously
483+-- crafted data. The primary reason for widespread vulnerability to
484+-- SQL injections is that many applications are sloppy in handling
485+-- user data when constructing SQL queries.
486+--
487+-- This library provides a 'Query' type and a parameter substitution
488+-- facility to address both ease of use and security.
489+
490+-- $querytype
491+--
492+-- A 'Query' is a @newtype@-wrapped 'ByteString'. It intentionally
493+-- exposes a tiny API that is not compatible with the 'ByteString'
494+-- API; this makes it difficult to construct queries from fragments of
495+-- strings. The 'query' and 'execute' functions require queries to be
496+-- of type 'Query'.
497+--
498+-- To most easily construct a query, enable GHC's @OverloadedStrings@
499+-- language extension and write your query as a normal literal string.
500+--
501+-- > {-# LANGUAGE OverloadedStrings #-}
502+-- >
503+-- > import Database.MySQL.Simple
504+-- >
505+-- > hello = do
506+-- > conn <- connect defaultConnectInfo
507+-- > query conn "select 2 + 2"
508+--
509+-- A 'Query' value does not represent the actual query that will be
510+-- executed, but is a template for constructing the final query.
511+
512+-- $subst
513+--
514+-- Since applications need to be able to construct queries with
515+-- parameters that change, this library provides a query substitution
516+-- capability.
517+--
518+-- The 'Query' template accepted by 'query' and 'execute' can contain
519+-- any number of \"@?@\" characters. Both 'query' and 'execute'
520+-- accept a third argument, typically a tuple. When constructing the
521+-- real query to execute, these functions replace the first \"@?@\" in
522+-- the template with the first element of the tuple, the second
523+-- \"@?@\" with the second element, and so on. If necessary, each
524+-- tuple element will be quoted and escaped prior to substitution;
525+-- this defeats the single most common injection vector for malicious
526+-- data.
527+--
528+-- For example, given the following 'Query' template:
529+--
530+-- > select * from user where first_name = ? and age > ?
531+--
532+-- And a tuple of this form:
533+--
534+-- > ("Boris" :: String, 37 :: Int)
535+--
536+-- The query to be executed will look like this after substitution:
537+--
538+-- > select * from user where first_name = 'Boris' and age > 37
539+--
540+-- If there is a mismatch between the number of \"@?@\" characters in
541+-- your template and the number of elements in your tuple, a
542+-- 'FormatError' will be thrown.
543+--
544+-- Note that the substitution functions do not attempt to parse or
545+-- validate your query. It's up to you to write syntactically valid
546+-- SQL, and to ensure that each \"@?@\" in your query template is
547+-- matched with the right tuple element.
548+
549+-- $inference
550+--
551+-- Automated type inference means that you will often be able to avoid
552+-- supplying explicit type signatures for the elements of a tuple.
553+-- However, sometimes the compiler will not be able to infer your
554+-- types. Consider a care where you write a numeric literal in a
555+-- parameter tuple:
556+--
557+-- > query conn "select ? + ?" (40,2)
558+--
559+-- The above query will be rejected by the compiler, because it does
560+-- not know the specific numeric types of the literals @40@ and @2@.
561+-- This is easily fixed:
562+--
563+-- > query conn "select ? + ?" (40 :: Double, 2 :: Double)
564+--
565+-- The same kind of problem can arise with string literals if you have
566+-- the @OverloadedStrings@ language extension enabled. Again, just
567+-- use an explicit type signature if this happens.
568+
569+-- $only_param
570+--
571+-- Haskell lacks a single-element tuple type, so if you have just one
572+-- value you want substituted into a query, what should you do?
573+--
574+-- The obvious approach would appear to be something like this:
575+--
576+-- > instance (Param a) => QueryParam a where
577+-- > ...
578+--
579+-- Unfortunately, this wreaks havoc with type inference, so we take a
580+-- different tack. To represent a single value @val@ as a parameter, write
581+-- a singleton list @[val]@, use 'Just' @val@, or use 'Only' @val@.
582+--
583+-- Here's an example using a singleton list:
584+--
585+-- > execute conn "insert into users (first_name) values (?)"
586+-- > ["Nuala"]
587+
588+-- $in
589+--
590+-- Suppose you want to write a query using an @IN@ clause:
591+--
592+-- > select * from users where first_name in ('Anna', 'Boris', 'Carla')
593+--
594+-- In such cases, it's common for both the elements and length of the
595+-- list after the @IN@ keyword to vary from query to query.
596+--
597+-- To address this case, use the 'In' type wrapper, and use a single
598+-- \"@?@\" character to represent the list. Omit the parentheses
599+-- around the list; these will be added for you.
600+--
601+-- Here's an example:
602+--
603+-- > query conn "select * from users where first_name in ?" $
604+-- > In ["Anna", "Boris", "Carla"]
605+--
606+-- If your 'In'-wrapped list is empty, the string @\"(null)\"@ will be
607+-- substituted instead, to ensure that your clause remains
608+-- syntactically valid.
609+
610+-- $many
611+--
612+-- If you know that you have many rows of data to insert into a table,
613+-- it is much more efficient to perform all the insertions in a single
614+-- multi-row @INSERT@ statement than individually.
615+--
616+-- The 'executeMany' function is intended specifically for helping
617+-- with multi-row @INSERT@ and @UPDATE@ statements. Its rules for
618+-- query substitution are different than those for 'execute'.
619+--
620+-- What 'executeMany' searches for in your 'Query' template is a
621+-- single substring of the form:
622+--
623+-- > values (?,?,?)
624+--
625+-- The rules are as follows:
626+--
627+-- * The keyword @VALUES@ is matched case insensitively.
628+--
629+-- * There must be no other \"@?@\" characters anywhere in your
630+-- template.
631+--
632+-- * There must one or more \"@?@\" in the parentheses.
633+--
634+-- * Extra white space is fine.
635+--
636+-- The last argument to 'executeMany' is a list of parameter
637+-- tuples. These will be substituted into the query where the @(?,?)@
638+-- string appears, in a form suitable for use in a multi-row @INSERT@
639+-- or @UPDATE@.
640+--
641+-- Here is an example:
642+--
643+-- > executeMany conn
644+-- > "insert into users (first_name,last_name) values (?,?)"
645+-- > [("Boris","Karloff"),("Ed","Wood")]
646+--
647+-- The query that will be executed here will look like this
648+-- (reformatted for tidiness):
649+--
650+-- > insert into users (first_name,last_name) values
651+-- > ('Boris','Karloff'),('Ed','Wood')
652+
653+-- $result
654+--
655+-- The 'query' and 'query_' functions return a list of values in the
656+-- 'QueryResults' typeclass. This class performs automatic extraction
657+-- and type conversion of rows from a query result.
658+--
659+-- Here is a simple example of how to extract results:
660+--
661+-- > import qualified Data.Text as Text
662+-- >
663+-- > xs <- query_ conn "select name,age from users"
664+-- > forM_ xs $ \(name,age) ->
665+-- > putStrLn $ Text.unpack name ++ " is " ++ show (age :: Int)
666+--
667+-- Notice two important details about this code:
668+--
669+-- * The number of columns we ask for in the query template must
670+-- exactly match the number of elements we specify in a row of the
671+-- result tuple. If they do not match, a 'ResultError' exception
672+-- will be thrown.
673+--
674+-- * Sometimes, the compiler needs our help in specifying types. It
675+-- can infer that @name@ must be a 'Text', due to our use of the
676+-- @unpack@ function. However, we have to tell it the type of @age@,
677+-- as it has no other information to determine the exact type.
678+
679+-- $null
680+--
681+-- The type of a result tuple will look something like this:
682+--
683+-- > (Text, Int, Int)
684+--
685+-- Although SQL can accommodate @NULL@ as a value for any of these
686+-- types, Haskell cannot. If your result contains columns that may be
687+-- @NULL@, be sure that you use 'Maybe' in those positions of of your
688+-- tuple.
689+--
690+-- > (Text, Maybe Int, Int)
691+--
692+-- If 'query' encounters a @NULL@ in a row where the corresponding
693+-- Haskell type is not 'Maybe', it will throw a 'ResultError'
694+-- exception.
695+
696+-- $only_result
697+--
698+-- To specify that a query returns a single-column result, use the
699+-- 'Only' type.
700+--
701+-- > xs <- query_ conn "select id from users"
702+-- > forM_ xs $ \(Only dbid) -> {- ... -}
703+
704+-- $types
705+--
706+-- Conversion of SQL values to Haskell values is somewhat
707+-- permissive. Here are the rules.
708+--
709+-- * For numeric types, any Haskell type that can accurately represent
710+-- all values of the given MySQL type is considered \"compatible\".
711+-- For instance, you can always extract a MySQL @TINYINT@ column to
712+-- a Haskell 'Int'. The Haskell 'Float' type can accurately
713+-- represent MySQL integer types of size up to @INT24@, so it is
714+-- considered compatble with those types.
715+--
716+-- * A numeric compatibility check is based only on the type of a
717+-- column, /not/ on its values. For instance, a MySQL @LONG_LONG@
718+-- column will be considered incompatible with a Haskell 'Int8',
719+-- even if it contains the value @1@.
720+--
721+-- * If a numeric incompatibility is found, 'query' will throw a
722+-- 'ResultError'.
723+--
724+-- * The 'String' and 'Text' types are assumed to be encoded as
725+-- UTF-8. If you use some other encoding, decoding may fail or give
726+-- wrong results. In such cases, write a @newtype@ wrapper and a
727+-- custom 'Result' instance to handle your encoding.
728+
729+getTypename :: Connection -> PQ.Oid -> IO ByteString
730+getTypename conn@Connection{..} oid =
731+ case oid2builtin oid of
732+ Just builtin -> return $! builtin2typname builtin
733+ Nothing -> do
734+ oidmap <- takeMVar connectionObjects
735+ case IntMap.lookup (oid2int oid) oidmap of
736+ Just name -> putMVar connectionObjects oidmap >> return name
737+ Nothing -> do
738+ names <- query conn "SELECT typname FROM pg_type WHERE oid=?"
739+ (Only oid)
740+ name <- case names of
741+ [] -> return $ throw SqlError {
742+ sqlNativeError = -1,
743+ sqlErrorMsg = "invalid type oid",
744+ sqlState = ""
745+ }
746+ [Only x] -> return x
747+ _ -> fail "typename query returned more than one result"
748+ -- oid is a primary key, so the query should
749+ -- never return more than one result
750+ putMVar connectionObjects (IntMap.insert (oid2int oid) name oidmap)
751+ return name
752+ `onException` do
753+ putMVar connectionObjects oidmap
src/Database/PostgreSQL/Simple/BuiltinTypes.hsnew file+403
@@ -0,0 +1,403 @@
1+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}
2+
3+------------------------------------------------------------------------------
4+-- |
5+-- Module: Database.PostgreSQL.Simple.BuiltinTypes
6+-- Copyright: (c) 2011 Leon P Smith
7+-- License: BSD3
8+-- Maintainer: Leon P Smith <leon@melding-monads.com>
9+-- Stability: experimental
10+--
11+------------------------------------------------------------------------------
12+
13+-- Note that this file is generated by tools/GenBuiltinTypes.hs, and should
14+-- not be edited directly
15+
16+module Database.PostgreSQL.Simple.BuiltinTypes
17+ ( BuiltinType (..)
18+ , builtin2oid
19+ , oid2builtin
20+ , builtin2typname
21+ , oid2typname
22+ ) where
23+
24+import Data.Typeable
25+import Data.ByteString (ByteString)
26+import qualified Database.PostgreSQL.LibPQ as PQ
27+
28+data BuiltinType
29+ = Bool
30+ | Bytea
31+ | Char
32+ | Name
33+ | Int8
34+ | Int2
35+ | Int4
36+ | Regproc
37+ | Text
38+ | Oid
39+ | Tid
40+ | Xid
41+ | Cid
42+ | Xml
43+ | Point
44+ | Lseg
45+ | Path
46+ | Box
47+ | Polygon
48+ | Line
49+ | Cidr
50+ | Float4
51+ | Float8
52+ | Abstime
53+ | Reltime
54+ | Tinterval
55+ | Unknown
56+ | Circle
57+ | Money
58+ | Macaddr
59+ | Inet
60+ | Bpchar
61+ | Varchar
62+ | Date
63+ | Time
64+ | Timestamp
65+ | TimestampWithTimeZone
66+ | Interval
67+ | TimeWithTimeZone
68+ | Bit
69+ | Varbit
70+ | Numeric
71+ | Refcursor
72+ | Record
73+ | Void
74+ deriving (Eq, Ord, Enum, Bounded, Read, Show, Typeable)
75+
76+builtin2oid :: BuiltinType -> PQ.Oid
77+builtin2oid typ = PQ.Oid $ case typ of
78+ Bool -> 16
79+ Bytea -> 17
80+ Char -> 18
81+ Name -> 19
82+ Int8 -> 20
83+ Int2 -> 21
84+ Int4 -> 23
85+ Regproc -> 24
86+ Text -> 25
87+ Oid -> 26
88+ Tid -> 27
89+ Xid -> 28
90+ Cid -> 29
91+ Xml -> 142
92+ Point -> 600
93+ Lseg -> 601
94+ Path -> 602
95+ Box -> 603
96+ Polygon -> 604
97+ Line -> 628
98+ Cidr -> 650
99+ Float4 -> 700
100+ Float8 -> 701
101+ Abstime -> 702
102+ Reltime -> 703
103+ Tinterval -> 704
104+ Unknown -> 705
105+ Circle -> 718
106+ Money -> 790
107+ Macaddr -> 829
108+ Inet -> 869
109+ Bpchar -> 1042
110+ Varchar -> 1043
111+ Date -> 1082
112+ Time -> 1083
113+ Timestamp -> 1114
114+ TimestampWithTimeZone -> 1184
115+ Interval -> 1186
116+ TimeWithTimeZone -> 1266
117+ Bit -> 1560
118+ Varbit -> 1562
119+ Numeric -> 1700
120+ Refcursor -> 1790
121+ Record -> 2249
122+ Void -> 2278
123+
124+oid2builtin :: PQ.Oid -> Maybe BuiltinType
125+oid2builtin (PQ.Oid x) = case x of
126+ 16 -> Just Bool
127+ 17 -> Just Bytea
128+ 18 -> Just Char
129+ 19 -> Just Name
130+ 20 -> Just Int8
131+ 21 -> Just Int2
132+ 23 -> Just Int4
133+ 24 -> Just Regproc
134+ 25 -> Just Text
135+ 26 -> Just Oid
136+ 27 -> Just Tid
137+ 28 -> Just Xid
138+ 29 -> Just Cid
139+ 142 -> Just Xml
140+ 600 -> Just Point
141+ 601 -> Just Lseg
142+ 602 -> Just Path
143+ 603 -> Just Box
144+ 604 -> Just Polygon
145+ 628 -> Just Line
146+ 650 -> Just Cidr
147+ 700 -> Just Float4
148+ 701 -> Just Float8
149+ 702 -> Just Abstime
150+ 703 -> Just Reltime
151+ 704 -> Just Tinterval
152+ 705 -> Just Unknown
153+ 718 -> Just Circle
154+ 790 -> Just Money
155+ 829 -> Just Macaddr
156+ 869 -> Just Inet
157+ 1042 -> Just Bpchar
158+ 1043 -> Just Varchar
159+ 1082 -> Just Date
160+ 1083 -> Just Time
161+ 1114 -> Just Timestamp
162+ 1184 -> Just TimestampWithTimeZone
163+ 1186 -> Just Interval
164+ 1266 -> Just TimeWithTimeZone
165+ 1560 -> Just Bit
166+ 1562 -> Just Varbit
167+ 1700 -> Just Numeric
168+ 1790 -> Just Refcursor
169+ 2249 -> Just Record
170+ 2278 -> Just Void
171+ _ -> Nothing
172+
173+builtin2typname :: BuiltinType -> ByteString
174+builtin2typname typ = case typ of
175+ Bool -> bool
176+ Bytea -> bytea
177+ Char -> char
178+ Name -> name
179+ Int8 -> int8
180+ Int2 -> int2
181+ Int4 -> int4
182+ Regproc -> regproc
183+ Text -> text
184+ Oid -> oid
185+ Tid -> tid
186+ Xid -> xid
187+ Cid -> cid
188+ Xml -> xml
189+ Point -> point
190+ Lseg -> lseg
191+ Path -> path
192+ Box -> box
193+ Polygon -> polygon
194+ Line -> line
195+ Cidr -> cidr
196+ Float4 -> float4
197+ Float8 -> float8
198+ Abstime -> abstime
199+ Reltime -> reltime
200+ Tinterval -> tinterval
201+ Unknown -> unknown
202+ Circle -> circle
203+ Money -> money
204+ Macaddr -> macaddr
205+ Inet -> inet
206+ Bpchar -> bpchar
207+ Varchar -> varchar
208+ Date -> date
209+ Time -> time
210+ Timestamp -> timestamp
211+ TimestampWithTimeZone -> timestamptz
212+ Interval -> interval
213+ TimeWithTimeZone -> timetz
214+ Bit -> bit
215+ Varbit -> varbit
216+ Numeric -> numeric
217+ Refcursor -> refcursor
218+ Record -> record
219+ Void -> void
220+
221+oid2typname :: PQ.Oid -> Maybe ByteString
222+oid2typname (PQ.Oid x) = case x of
223+ 16 -> Just bool
224+ 17 -> Just bytea
225+ 18 -> Just char
226+ 19 -> Just name
227+ 20 -> Just int8
228+ 21 -> Just int2
229+ 23 -> Just int4
230+ 24 -> Just regproc
231+ 25 -> Just text
232+ 26 -> Just oid
233+ 27 -> Just tid
234+ 28 -> Just xid
235+ 29 -> Just cid
236+ 142 -> Just xml
237+ 600 -> Just point
238+ 601 -> Just lseg
239+ 602 -> Just path
240+ 603 -> Just box
241+ 604 -> Just polygon
242+ 628 -> Just line
243+ 650 -> Just cidr
244+ 700 -> Just float4
245+ 701 -> Just float8
246+ 702 -> Just abstime
247+ 703 -> Just reltime
248+ 704 -> Just tinterval
249+ 705 -> Just unknown
250+ 718 -> Just circle
251+ 790 -> Just money
252+ 829 -> Just macaddr
253+ 869 -> Just inet
254+ 1042 -> Just bpchar
255+ 1043 -> Just varchar
256+ 1082 -> Just date
257+ 1083 -> Just time
258+ 1114 -> Just timestamp
259+ 1184 -> Just timestamptz
260+ 1186 -> Just interval
261+ 1266 -> Just timetz
262+ 1560 -> Just bit
263+ 1562 -> Just varbit
264+ 1700 -> Just numeric
265+ 1790 -> Just refcursor
266+ 2249 -> Just record
267+ 2278 -> Just void
268+ _ -> Nothing
269+
270+bool :: ByteString
271+bool = "bool"
272+
273+bytea :: ByteString
274+bytea = "bytea"
275+
276+char :: ByteString
277+char = "char"
278+
279+name :: ByteString
280+name = "name"
281+
282+int8 :: ByteString
283+int8 = "int8"
284+
285+int2 :: ByteString
286+int2 = "int2"
287+
288+int4 :: ByteString
289+int4 = "int4"
290+
291+regproc :: ByteString
292+regproc = "regproc"
293+
294+text :: ByteString
295+text = "text"
296+
297+oid :: ByteString
298+oid = "oid"
299+
300+tid :: ByteString
301+tid = "tid"
302+
303+xid :: ByteString
304+xid = "xid"
305+
306+cid :: ByteString
307+cid = "cid"
308+
309+xml :: ByteString
310+xml = "xml"
311+
312+point :: ByteString
313+point = "point"
314+
315+lseg :: ByteString
316+lseg = "lseg"
317+
318+path :: ByteString
319+path = "path"
320+
321+box :: ByteString
322+box = "box"
323+
324+polygon :: ByteString
325+polygon = "polygon"
326+
327+line :: ByteString
328+line = "line"
329+
330+cidr :: ByteString
331+cidr = "cidr"
332+
333+float4 :: ByteString
334+float4 = "float4"
335+
336+float8 :: ByteString
337+float8 = "float8"
338+
339+abstime :: ByteString
340+abstime = "abstime"
341+
342+reltime :: ByteString
343+reltime = "reltime"
344+
345+tinterval :: ByteString
346+tinterval = "tinterval"
347+
348+unknown :: ByteString
349+unknown = "unknown"
350+
351+circle :: ByteString
352+circle = "circle"
353+
354+money :: ByteString
355+money = "money"
356+
357+macaddr :: ByteString
358+macaddr = "macaddr"
359+
360+inet :: ByteString
361+inet = "inet"
362+
363+bpchar :: ByteString
364+bpchar = "bpchar"
365+
366+varchar :: ByteString
367+varchar = "varchar"
368+
369+date :: ByteString
370+date = "date"
371+
372+time :: ByteString
373+time = "time"
374+
375+timestamp :: ByteString
376+timestamp = "timestamp"
377+
378+timestamptz :: ByteString
379+timestamptz = "timestamptz"
380+
381+interval :: ByteString
382+interval = "interval"
383+
384+timetz :: ByteString
385+timetz = "timetz"
386+
387+bit :: ByteString
388+bit = "bit"
389+
390+varbit :: ByteString
391+varbit = "varbit"
392+
393+numeric :: ByteString
394+numeric = "numeric"
395+
396+refcursor :: ByteString
397+refcursor = "refcursor"
398+
399+record :: ByteString
400+record = "record"
401+
402+void :: ByteString
403+void = "void"
src/Database/PostgreSQL/Simple/Field.hsnew file+15
@@ -0,0 +1,15 @@
1+module Database.PostgreSQL.Simple.Field
2+ ( Field
3+ , typename
4+ , name
5+ , tableOid
6+ , tableColumn
7+ , format
8+ , typeOid
9+ , Oid
10+ , Format(..)
11+ , RawResult(..)
12+ ) where
13+
14+import Database.PostgreSQL.Simple.Implementation
15+import Database.PostgreSQL.LibPQ (Format(..), Oid)
src/Database/PostgreSQL/Simple/Implementation.hsnew file+187
@@ -0,0 +1,187 @@
1+{-# LANGUAGE OverloadedStrings, NamedFieldPuns, RecordWildCards #-}
2+{-# LANGUAGE DeriveDataTypeable, DeriveFunctor #-}
3+module Database.PostgreSQL.Simple.Implementation where
4+
5+import Prelude hiding (catch)
6+
7+import Control.Applicative
8+import Control.Exception
9+import Control.Monad.IO.Class (MonadIO(liftIO))
10+import Control.Concurrent.MVar
11+import Data.ByteString(ByteString)
12+import qualified Data.IntMap as IntMap
13+import Data.String
14+import Data.Typeable
15+import Data.Word
16+import Database.PostgreSQL.LibPQ(Oid(..))
17+import qualified Database.PostgreSQL.LibPQ as PQ
18+import Database.PostgreSQL.Simple.BuiltinTypes (BuiltinType)
19+import System.IO.Unsafe (unsafePerformIO)
20+
21+
22+data Status err a = Fail err | Success a deriving(Eq, Ord, Show, Functor)
23+
24+instance Applicative (Status err) where
25+ pure = Success
26+ (Success f) <*> (Success a) = Success (f a)
27+ (Success _) <*> (Fail err) = Fail err
28+ (Fail err) <*> _ = Fail err
29+
30+instance Monad (Status err) where
31+ return = pure
32+ (Success a) >>= f = f a
33+ (Fail err) >>= _ = Fail err
34+
35+
36+-- | A Field represents metadata about a particular field
37+--
38+-- You don't particularly want to retain these structures for a long
39+-- period of time, as they will retain the entire query result, not
40+-- just the field metadata
41+
42+data Field = Field {
43+ result :: PQ.Result
44+ , column :: PQ.Column
45+ , typename :: ByteString
46+ }
47+
48+name :: Field -> Maybe ByteString
49+name Field{..} = unsafePerformIO (PQ.fname result column)
50+
51+tableOid :: Field -> PQ.Oid
52+tableOid Field{..} = unsafePerformIO (PQ.ftable result column)
53+
54+tableColumn :: Field -> Int
55+tableColumn Field{..} = fromCol (unsafePerformIO (PQ.ftablecol result column))
56+ where
57+ fromCol (PQ.Col x) = fromIntegral x
58+
59+
60+format :: Field -> PQ.Format
61+format Field{..} = unsafePerformIO (PQ.fformat result column)
62+
63+typeOid :: Field -> PQ.Oid
64+typeOid Field{..} = unsafePerformIO (PQ.ftype result column)
65+
66+
67+data Connection = Connection {
68+ connectionHandle :: MVar (Maybe PQ.Connection)
69+ , connectionObjects :: MVar (IntMap.IntMap ByteString)
70+ }
71+
72+data SqlType
73+ = Builtin BuiltinType
74+ | Other Oid
75+
76+data SqlError = SqlError {
77+ sqlState :: ByteString
78+ , sqlNativeError :: Int
79+ , sqlErrorMsg :: ByteString
80+ } deriving (Show, Typeable)
81+
82+instance Exception SqlError
83+
84+data ConnectInfo = ConnectInfo {
85+ connectHost :: String
86+ , connectPort :: Word16
87+ , connectUser :: String
88+ , connectPassword :: String
89+ , connectDatabase :: String
90+ } deriving (Eq,Read,Show,Typeable)
91+
92+defaultConnectInfo :: ConnectInfo
93+defaultConnectInfo = ConnectInfo {
94+ connectHost = "127.0.0.1"
95+ , connectPort = 5432
96+ , connectUser = "postgres"
97+ , connectPassword = ""
98+ , connectDatabase = ""
99+ }
100+
101+-- | Connect with the given username to the given database. Will throw
102+-- an exception if it cannot connect.
103+connect :: MonadIO m => ConnectInfo -> m Connection
104+connect = connectPostgreSQL . postgreSQLConnectionString
105+
106+connectPostgreSQL :: MonadIO m => ByteString -> m Connection
107+connectPostgreSQL connstr = liftIO $ do
108+ conn <- PQ.connectdb connstr
109+ connectionHandle <- newMVar (Just conn)
110+ connectionObjects <- newMVar (IntMap.empty)
111+ return Connection{..}
112+
113+postgreSQLConnectionString :: ConnectInfo -> ByteString
114+postgreSQLConnectionString connectInfo = fromString connstr
115+ where
116+ connstr = str "host=" connectHost
117+ $ num "port=" connectPort
118+ $ str "user=" connectUser
119+ $ str "password=" connectPassword
120+ $ str "dbname=" connectDatabase
121+ $ []
122+
123+ str name field
124+ | null value = id
125+ | otherwise = (name ++) . quote value . space
126+ where value = field connectInfo
127+
128+ num name field
129+ | value <= 0 = id
130+ | otherwise = (name ++) . (show value ++) . space
131+ where value = field connectInfo
132+
133+ quote str rest = '\'' : foldr delta ('\'' : rest) str
134+ where
135+ delta c cs = case c of
136+ '\\' -> '\\' : '\\' : cs
137+ '\'' -> '\\' : '\'' : cs
138+ _ -> c : cs
139+
140+ space [] = []
141+ space xs = ' ':xs
142+
143+
144+
145+oid2int :: Oid -> Int
146+oid2int (Oid x) = fromIntegral x
147+{-# INLINE oid2int #-}
148+
149+exec :: Connection
150+ -> ByteString
151+ -> IO PQ.Result
152+exec conn sql =
153+ withConnection conn $ \h -> do
154+ mres <- PQ.exec h sql
155+ case mres of
156+ Nothing -> do
157+ msg <- PQ.errorMessage h
158+ -- FIXME better error message
159+ fail ("Postgres Error: " ++ show msg)
160+ Just res -> do
161+ return res
162+
163+disconnectedError = SqlError {
164+ sqlNativeError = -1,
165+ sqlErrorMsg = "connection disconnected",
166+ sqlState = ""
167+ }
168+
169+
170+-- | Atomically perform an action with the database handle, if there is one.
171+withConnection :: Connection -> (PQ.Connection -> IO a) -> IO a
172+withConnection Connection{..} m = do
173+ withMVar connectionHandle $ \h -> do
174+ case h of
175+ Just h -> m h
176+ -- TODO: Use extensible exceptions.
177+ Nothing -> throwIO disconnectedError
178+
179+close :: Connection -> IO ()
180+close Connection{..} = do
181+ mconn <- takeMVar connectionHandle
182+ case mconn of
183+ Just conn -> PQ.finish conn
184+ Nothing -> return ()
185+ `finally` putMVar connectionHandle Nothing
186+
187+data RawResult = RawResult { rawField :: Field, rawData :: Maybe ByteString }
src/Database/PostgreSQL/Simple/Param.hsnew file+205
@@ -0,0 +1,205 @@
1+{-# LANGUAGE DeriveDataTypeable, DeriveFunctor, FlexibleInstances,
2+ OverloadedStrings #-}
3+
4+-- |
5+-- Module: Database.PostgreSQL.Simple.Param
6+-- Copyright: (c) 2011 MailRank, Inc.
7+-- (c) 2011 Leon P Smith
8+-- License: BSD3
9+-- Maintainer: Leon P Smith <leon@melding-monads.com>
10+-- Stability: experimental
11+-- Portability: portable
12+--
13+-- The 'Param' typeclass, for rendering a parameter to a SQL query.
14+
15+module Database.PostgreSQL.Simple.Param
16+ (
17+ Action(..)
18+ , Param(..)
19+ , inQuotes
20+ ) where
21+
22+import Blaze.ByteString.Builder (Builder, fromByteString, fromLazyByteString,
23+ toByteString)
24+import Blaze.ByteString.Builder.Char8 (fromChar)
25+import Blaze.Text (integral, double, float)
26+import Data.ByteString (ByteString)
27+import qualified Data.ByteString.Base16 as B16
28+import qualified Data.ByteString.Base16.Lazy as L16
29+import Data.Int (Int8, Int16, Int32, Int64)
30+import Data.List (intersperse)
31+import Data.Monoid (mappend)
32+import Data.Time.Calendar (Day, showGregorian)
33+import Data.Time.Clock (UTCTime)
34+import Data.Time.Format (formatTime)
35+import Data.Time.LocalTime (TimeOfDay)
36+import Data.Typeable (Typeable)
37+import Data.Word (Word, Word8, Word16, Word32, Word64)
38+import Database.PostgreSQL.Simple.Types (Binary(..), In(..), Null)
39+import System.Locale (defaultTimeLocale)
40+import qualified Blaze.ByteString.Builder.Char.Utf8 as Utf8
41+import qualified Data.ByteString as SB
42+import qualified Data.ByteString.Lazy as LB
43+import qualified Data.Text as ST
44+import qualified Data.Text.Encoding as ST
45+import qualified Data.Text.Lazy as LT
46+import qualified Database.PostgreSQL.LibPQ as PQ
47+
48+-- | How to render an element when substituting it into a query.
49+data Action =
50+ Plain Builder
51+ -- ^ Render without escaping or quoting. Use for non-text types
52+ -- such as numbers, when you are /certain/ that they will not
53+ -- introduce formatting vulnerabilities via use of characters such
54+ -- as spaces or \"@'@\".
55+ | Escape ByteString
56+ -- ^ Escape and enclose in quotes before substituting. Use for all
57+ -- text-like types, and anything else that may contain unsafe
58+ -- characters when rendered.
59+ | Many [Action]
60+ -- ^ Concatenate a series of rendering actions.
61+ deriving (Typeable)
62+
63+instance Show Action where
64+ show (Plain b) = "Plain " ++ show (toByteString b)
65+ show (Escape b) = "Escape " ++ show b
66+ show (Many b) = "Many " ++ show b
67+
68+-- | A type that may be used as a single parameter to a SQL query.
69+class Param a where
70+ render :: a -> Action
71+ -- ^ Prepare a value for substitution into a query string.
72+
73+instance Param Action where
74+ render a = a
75+ {-# INLINE render #-}
76+
77+instance (Param a) => Param (Maybe a) where
78+ render Nothing = renderNull
79+ render (Just a) = render a
80+ {-# INLINE render #-}
81+
82+instance (Param a) => Param (In [a]) where
83+ render (In []) = Plain $ fromByteString "(null)"
84+ render (In xs) = Many $
85+ Plain (fromChar '(') :
86+ (intersperse (Plain (fromChar ',')) . map render $ xs) ++
87+ [Plain (fromChar ')')]
88+
89+instance Param (Binary SB.ByteString) where
90+ render (Binary bs) = Plain $ fromByteString "'\\x" `mappend`
91+ fromByteString (B16.encode bs) `mappend`
92+ fromChar '\''
93+
94+instance Param (Binary LB.ByteString) where
95+ render (Binary bs) = Plain $ fromByteString "'\\x" `mappend`
96+ fromLazyByteString (L16.encode bs) `mappend`
97+ fromChar '\''
98+
99+renderNull :: Action
100+renderNull = Plain (fromByteString "null")
101+
102+instance Param Null where
103+ render _ = renderNull
104+ {-# INLINE render #-}
105+
106+instance Param Bool where
107+ render = Plain . integral . fromEnum
108+ {-# INLINE render #-}
109+
110+instance Param Int8 where
111+ render = Plain . integral
112+ {-# INLINE render #-}
113+
114+instance Param Int16 where
115+ render = Plain . integral
116+ {-# INLINE render #-}
117+
118+instance Param Int32 where
119+ render = Plain . integral
120+ {-# INLINE render #-}
121+
122+instance Param Int where
123+ render = Plain . integral
124+ {-# INLINE render #-}
125+
126+instance Param Int64 where
127+ render = Plain . integral
128+ {-# INLINE render #-}
129+
130+instance Param Integer where
131+ render = Plain . integral
132+ {-# INLINE render #-}
133+
134+instance Param Word8 where
135+ render = Plain . integral
136+ {-# INLINE render #-}
137+
138+instance Param Word16 where
139+ render = Plain . integral
140+ {-# INLINE render #-}
141+
142+instance Param Word32 where
143+ render = Plain . integral
144+ {-# INLINE render #-}
145+
146+instance Param Word where
147+ render = Plain . integral
148+ {-# INLINE render #-}
149+
150+instance Param Word64 where
151+ render = Plain . integral
152+ {-# INLINE render #-}
153+
154+instance Param PQ.Oid where
155+ render = Plain . integral . \(PQ.Oid x) -> x
156+ {-# INLINE render #-}
157+
158+instance Param Float where
159+ render v | isNaN v || isInfinite v = renderNull
160+ | otherwise = Plain (float v)
161+ {-# INLINE render #-}
162+
163+instance Param Double where
164+ render v | isNaN v || isInfinite v = renderNull
165+ | otherwise = Plain (double v)
166+ {-# INLINE render #-}
167+
168+instance Param SB.ByteString where
169+ render = Escape
170+ {-# INLINE render #-}
171+
172+instance Param LB.ByteString where
173+ render = render . SB.concat . LB.toChunks
174+ {-# INLINE render #-}
175+
176+instance Param ST.Text where
177+ render = Escape . ST.encodeUtf8
178+ {-# INLINE render #-}
179+
180+instance Param [Char] where
181+ render = Escape . toByteString . Utf8.fromString
182+ {-# INLINE render #-}
183+
184+instance Param LT.Text where
185+ render = render . LT.toStrict
186+ {-# INLINE render #-}
187+
188+instance Param UTCTime where
189+ render = Plain . Utf8.fromString . formatTime defaultTimeLocale "'%F %T%Q+00'"
190+ {-# INLINE render #-}
191+
192+instance Param Day where
193+ render = Plain . inQuotes . Utf8.fromString . showGregorian
194+ {-# INLINE render #-}
195+
196+instance Param TimeOfDay where
197+ render = Plain . inQuotes . Utf8.fromString . show
198+ {-# INLINE render #-}
199+
200+-- | Surround a string with single-quote characters: \"@'@\"
201+--
202+-- This function /does not/ perform any other escaping.
203+inQuotes :: Builder -> Builder
204+inQuotes b = quote `mappend` b `mappend` quote
205+ where quote = Utf8.fromChar '\''
src/Database/PostgreSQL/Simple/QueryParams.hsnew file+85
@@ -0,0 +1,85 @@
1+-- |
2+-- Module: Database.PostgreSQL.Simple.QueryParams
3+-- Copyright: (c) 2011 MailRank, Inc.
4+-- (c) 2011 Leon P Smith
5+-- License: BSD3
6+-- Maintainer: Leon P Smith <leon@melding-monads.com>
7+-- Stability: experimental
8+-- Portability: portable
9+--
10+-- The 'QueryParams' typeclass, for rendering a collection of
11+-- parameters to a SQL query.
12+--
13+-- Predefined instances are provided for tuples containing up to ten
14+-- elements.
15+
16+module Database.PostgreSQL.Simple.QueryParams
17+ (
18+ QueryParams(..)
19+ ) where
20+
21+import Database.PostgreSQL.Simple.Param (Action(..), Param(..))
22+import Database.PostgreSQL.Simple.Types (Only(..))
23+
24+-- | A collection type that can be turned into a list of rendering
25+-- 'Action's.
26+--
27+-- Instances should use the 'render' method of the 'Param' class
28+-- to perform conversion of each element of the collection.
29+class QueryParams a where
30+ renderParams :: a -> [Action]
31+ -- ^ Render a collection of values.
32+
33+instance QueryParams () where
34+ renderParams _ = []
35+
36+instance (Param a) => QueryParams (Only a) where
37+ renderParams (Only v) = [render v]
38+
39+instance (Param a, Param b) => QueryParams (a,b) where
40+ renderParams (a,b) = [render a, render b]
41+
42+instance (Param a, Param b, Param c) => QueryParams (a,b,c) where
43+ renderParams (a,b,c) = [render a, render b, render c]
44+
45+instance (Param a, Param b, Param c, Param d) => QueryParams (a,b,c,d) where
46+ renderParams (a,b,c,d) = [render a, render b, render c, render d]
47+
48+instance (Param a, Param b, Param c, Param d, Param e)
49+ => QueryParams (a,b,c,d,e) where
50+ renderParams (a,b,c,d,e) =
51+ [render a, render b, render c, render d, render e]
52+
53+instance (Param a, Param b, Param c, Param d, Param e, Param f)
54+ => QueryParams (a,b,c,d,e,f) where
55+ renderParams (a,b,c,d,e,f) =
56+ [render a, render b, render c, render d, render e, render f]
57+
58+instance (Param a, Param b, Param c, Param d, Param e, Param f, Param g)
59+ => QueryParams (a,b,c,d,e,f,g) where
60+ renderParams (a,b,c,d,e,f,g) =
61+ [render a, render b, render c, render d, render e, render f, render g]
62+
63+instance (Param a, Param b, Param c, Param d, Param e, Param f, Param g,
64+ Param h)
65+ => QueryParams (a,b,c,d,e,f,g,h) where
66+ renderParams (a,b,c,d,e,f,g,h) =
67+ [render a, render b, render c, render d, render e, render f, render g,
68+ render h]
69+
70+instance (Param a, Param b, Param c, Param d, Param e, Param f, Param g,
71+ Param h, Param i)
72+ => QueryParams (a,b,c,d,e,f,g,h,i) where
73+ renderParams (a,b,c,d,e,f,g,h,i) =
74+ [render a, render b, render c, render d, render e, render f, render g,
75+ render h, render i]
76+
77+instance (Param a, Param b, Param c, Param d, Param e, Param f, Param g,
78+ Param h, Param i, Param j)
79+ => QueryParams (a,b,c,d,e,f,g,h,i,j) where
80+ renderParams (a,b,c,d,e,f,g,h,i,j) =
81+ [render a, render b, render c, render d, render e, render f, render g,
82+ render h, render i, render j]
83+
84+instance (Param a) => QueryParams [a] where
85+ renderParams = map render
src/Database/PostgreSQL/Simple/QueryResults.hsnew file+219
@@ -0,0 +1,219 @@
1+{-# LANGUAGE BangPatterns, OverloadedStrings #-}
2+
3+-- |
4+-- Module: Database.MySQL.Simpe.QueryResults
5+-- Copyright: (c) 2011 MailRank, Inc.
6+-- (c) 2011 Leon P Smith
7+-- License: BSD3
8+-- Maintainer: Leon P Smith <leon@melding-monads.com>
9+-- Stability: experimental
10+-- Portability: portable
11+--
12+-- The 'QueryResults' typeclass, for converting a row of results
13+-- returned by a SQL query into a more useful Haskell representation.
14+--
15+-- Predefined instances are provided for tuples containing up to ten
16+-- elements.
17+
18+module Database.PostgreSQL.Simple.QueryResults
19+ (
20+ QueryResults(..)
21+ , convertError
22+ ) where
23+
24+import Control.Exception (throw)
25+import Data.ByteString (ByteString)
26+import qualified Data.ByteString.Char8 as B
27+import Database.PostgreSQL.Simple.Implementation
28+import Database.PostgreSQL.Simple.Result (ResultError(..), Result(..),Status(..))
29+import Database.PostgreSQL.Simple.Types (Only(..))
30+
31+-- | A collection type that can be converted from a list of strings.
32+--
33+-- Instances should use the 'convert' method of the 'Result' class
34+-- to perform conversion of each element of the collection.
35+--
36+-- This example instance demonstrates how to convert a two-column row
37+-- into a Haskell pair. Each field in the metadata is paired up with
38+-- each value from the row, and the two are passed to 'convert'.
39+--
40+-- @
41+-- instance ('Result' a, 'Result' b) => 'QueryResults' (a,b) where
42+-- 'convertResults' [fa,fb] [va,vb] = (a,b)
43+-- where !a = 'convert' fa va
44+-- !b = 'convert' fb vb
45+-- 'convertResults' fs vs = 'convertError' fs vs 2
46+-- @
47+--
48+-- Notice that this instance evaluates each element to WHNF before
49+-- constructing the pair. By doing this, we guarantee two important
50+-- properties:
51+--
52+-- * Keep resource usage under control by preventing the construction
53+-- of potentially long-lived thunks.
54+--
55+-- * Ensure that any 'ResultError' that might arise is thrown
56+-- immediately, rather than some place later in application code
57+-- that cannot handle it.
58+--
59+-- You can also declare Haskell types of your own to be instances of
60+-- 'QueryResults'.
61+--
62+-- @
63+--data User { firstName :: String, lastName :: String }
64+--
65+--instance 'QueryResults' User where
66+-- 'convertResults' [fa,fb] [va,vb] = User a b
67+-- where !a = 'convert' fa va
68+-- !b = 'convert' fb vb
69+-- 'convertResults' fs vs = 'convertError' fs vs
70+-- @
71+
72+class QueryResults a where
73+ convertResults :: [Field] -> [Maybe ByteString] -> Status ResultError a
74+ -- ^ Convert values from a row into a Haskell collection.
75+ --
76+ -- This function will throw a 'ResultError' if conversion of the
77+ -- collection fails.
78+
79+instance (Result a) => QueryResults (Only a) where
80+ convertResults [fa] [va] = do
81+ !a <- convert fa va
82+ return (Only a)
83+ convertResults fs vs = convertError fs vs 1
84+
85+instance (Result a, Result b) => QueryResults (a,b) where
86+ convertResults [fa,fb] [va,vb] = do
87+ !a <- convert fa va
88+ !b <- convert fb vb
89+ return (a,b)
90+ convertResults fs vs = convertError fs vs 2
91+
92+instance (Result a, Result b, Result c) => QueryResults (a,b,c) where
93+ convertResults [fa,fb,fc] [va,vb,vc] = do
94+ !a <- convert fa va
95+ !b <- convert fb vb
96+ !c <- convert fc vc
97+ return (a,b,c)
98+ convertResults fs vs = convertError fs vs 3
99+
100+instance (Result a, Result b, Result c, Result d) =>
101+ QueryResults (a,b,c,d) where
102+ convertResults [fa,fb,fc,fd] [va,vb,vc,vd] = do
103+ !a <- convert fa va
104+ !b <- convert fb vb
105+ !c <- convert fc vc
106+ !d <- convert fd vd
107+ return (a,b,c,d)
108+ convertResults fs vs = convertError fs vs 4
109+
110+instance (Result a, Result b, Result c, Result d, Result e) =>
111+ QueryResults (a,b,c,d,e) where
112+ convertResults [fa,fb,fc,fd,fe] [va,vb,vc,vd,ve] = do
113+ !a <- convert fa va
114+ !b <- convert fb vb
115+ !c <- convert fc vc
116+ !d <- convert fd vd
117+ !e <- convert fe ve
118+ return (a,b,c,d,e)
119+ convertResults fs vs = convertError fs vs 5
120+
121+instance (Result a, Result b, Result c, Result d, Result e, Result f) =>
122+ QueryResults (a,b,c,d,e,f) where
123+ convertResults [fa,fb,fc,fd,fe,ff] [va,vb,vc,vd,ve,vf] = do
124+ !a <- convert fa va
125+ !b <- convert fb vb
126+ !c <- convert fc vc
127+ !d <- convert fd vd
128+ !e <- convert fe ve
129+ !f <- convert ff vf
130+ return (a,b,c,d,e,f)
131+ convertResults fs vs = convertError fs vs 6
132+
133+instance (Result a, Result b, Result c, Result d, Result e, Result f,
134+ Result g) =>
135+ QueryResults (a,b,c,d,e,f,g) where
136+ convertResults [fa,fb,fc,fd,fe,ff,fg] [va,vb,vc,vd,ve,vf,vg] = do
137+ !a <- convert fa va
138+ !b <- convert fb vb
139+ !c <- convert fc vc
140+ !d <- convert fd vd
141+ !e <- convert fe ve
142+ !f <- convert ff vf
143+ !g <- convert fg vg
144+ return (a,b,c,d,e,f,g)
145+ convertResults fs vs = convertError fs vs 7
146+
147+instance (Result a, Result b, Result c, Result d, Result e, Result f,
148+ Result g, Result h) =>
149+ QueryResults (a,b,c,d,e,f,g,h) where
150+ convertResults [fa,fb,fc,fd,fe,ff,fg,fh] [va,vb,vc,vd,ve,vf,vg,vh] = do
151+ !a <- convert fa va
152+ !b <- convert fb vb
153+ !c <- convert fc vc
154+ !d <- convert fd vd
155+ !e <- convert fe ve
156+ !f <- convert ff vf
157+ !g <- convert fg vg
158+ !h <- convert fh vh
159+ return (a,b,c,d,e,f,g,h)
160+ convertResults fs vs = convertError fs vs 8
161+
162+instance (Result a, Result b, Result c, Result d, Result e, Result f,
163+ Result g, Result h, Result i) =>
164+ QueryResults (a,b,c,d,e,f,g,h,i) where
165+ convertResults [fa,fb,fc,fd,fe,ff,fg,fh,fi] [va,vb,vc,vd,ve,vf,vg,vh,vi] =
166+ do
167+ !a <- convert fa va
168+ !b <- convert fb vb
169+ !c <- convert fc vc
170+ !d <- convert fd vd
171+ !e <- convert fe ve
172+ !f <- convert ff vf
173+ !g <- convert fg vg
174+ !h <- convert fh vh
175+ !i <- convert fi vi
176+ return (a,b,c,d,e,f,g,h,i)
177+ convertResults fs vs = convertError fs vs 9
178+
179+instance (Result a, Result b, Result c, Result d, Result e, Result f,
180+ Result g, Result h, Result i, Result j) =>
181+ QueryResults (a,b,c,d,e,f,g,h,i,j) where
182+ convertResults [fa,fb,fc,fd,fe,ff,fg,fh,fi,fj]
183+ [va,vb,vc,vd,ve,vf,vg,vh,vi,vj] =
184+ do
185+ !a <- convert fa va
186+ !b <- convert fb vb
187+ !c <- convert fc vc
188+ !d <- convert fd vd
189+ !e <- convert fe ve
190+ !f <- convert ff vf
191+ !g <- convert fg vg
192+ !h <- convert fh vh
193+ !i <- convert fi vi
194+ !j <- convert fj vj
195+ return (a,b,c,d,e,f,g,h,i,j)
196+ convertResults fs vs = convertError fs vs 10
197+
198+-- | Throw a 'ConversionFailed' exception, indicating a mismatch
199+-- between the number of columns in the 'Field' and row, and the
200+-- number in the collection to be converted to.
201+convertError :: [Field]
202+ -- ^ Descriptors of fields to be converted.
203+ -> [Maybe ByteString]
204+ -- ^ Contents of the row to be converted.
205+ -> Int
206+ -- ^ Number of columns expected for conversion. For
207+ -- instance, if converting to a 3-tuple, the number to
208+ -- provide here would be 3.
209+ -> Status ResultError a
210+convertError fs vs n = Fail $ ConversionFailed
211+ (show (length fs) ++ " values: " ++ show (zip (map typename fs)
212+ (map (fmap ellipsis) vs)))
213+ (show n ++ " slots in target type")
214+ "mismatch between number of columns to convert and number in target type"
215+
216+ellipsis :: ByteString -> ByteString
217+ellipsis bs
218+ | B.length bs > 15 = B.take 10 bs `B.append` "[...]"
219+ | otherwise = bs
src/Database/PostgreSQL/Simple/Result.hsnew file+252
@@ -0,0 +1,252 @@
1+{-# LANGUAGE CPP, DeriveDataTypeable, DeriveFunctor, FlexibleInstances #-}
2+{-# LANGUAGE PatternGuards, ScopedTypeVariables #-}
3+------------------------------------------------------------------------------
4+-- |
5+-- Module: Database.PostgreSQL.Simple.QueryResults
6+-- Copyright: (c) 2011 MailRank, Inc.
7+-- (c) 2011 Leon P Smith
8+-- License: BSD3
9+-- Maintainer: Leon P Smith <leon@melding-monads.com>
10+-- Stability: experimental
11+-- Portability: portable
12+--
13+-- The 'Result' typeclass, for converting a single value in a row
14+-- returned by a SQL query into a more useful Haskell representation.
15+--
16+-- A Haskell numeric type is considered to be compatible with all
17+-- MySQL numeric types that are less accurate than it. For instance,
18+-- the Haskell 'Double' type is compatible with the MySQL 'Long' type
19+-- because it can represent a 'Long' exactly. On the other hand, since
20+-- a 'Double' might lose precision if representing a 'LongLong', the
21+-- two are /not/ considered compatible.
22+--
23+------------------------------------------------------------------------------
24+
25+module Database.PostgreSQL.Simple.Result
26+ (
27+ Result(..)
28+ , ResultError(..)
29+ , Status(..)
30+ ) where
31+
32+#include "MachDeps.h"
33+
34+import Control.Applicative (Applicative, (<$>), (<*>), (<*), pure)
35+import Control.Exception (Exception, throw)
36+import Data.Attoparsec.Char8 hiding (Result)
37+import Data.Bits ((.&.), (.|.), shiftL)
38+import Data.ByteString (ByteString)
39+import qualified Data.ByteString.Char8 as B
40+import Data.Int (Int16, Int32, Int64)
41+import Data.List (foldl')
42+import Data.Ratio (Ratio)
43+import Data.Time.Calendar (Day, fromGregorian)
44+import Data.Time.Clock (UTCTime)
45+import Data.Time.Format (parseTime)
46+import Data.Time.LocalTime (TimeOfDay, makeTimeOfDayValid)
47+import Data.Typeable (TypeRep, Typeable, typeOf)
48+import Data.Word ({- Word, Word8, Word16, Word32, -} Word64)
49+-- import Database.MySQL.Base.Types (Field(..), Type(..))
50+import Database.PostgreSQL.Simple.Implementation
51+import Database.PostgreSQL.Simple.Field (Field(..), RawResult(..))
52+import Database.PostgreSQL.Simple.BuiltinTypes
53+import qualified Database.PostgreSQL.LibPQ as PQ
54+import System.Locale (defaultTimeLocale)
55+import qualified Data.ByteString as SB
56+import qualified Data.ByteString.Char8 as B8
57+import qualified Data.ByteString.Lazy as LB
58+import qualified Data.Text as ST
59+import qualified Data.Text.Encoding as ST
60+import qualified Data.Text.Lazy as LT
61+
62+-- | Exception thrown if conversion from a SQL value to a Haskell
63+-- value fails.
64+data ResultError = Incompatible { errSQLType :: String
65+ , errHaskellType :: String
66+ , errMessage :: String }
67+ -- ^ The SQL and Haskell types are not compatible.
68+ | UnexpectedNull { errSQLType :: String
69+ , errHaskellType :: String
70+ , errMessage :: String }
71+ -- ^ A SQL @NULL@ was encountered when the Haskell
72+ -- type did not permit it.
73+ | ConversionFailed { errSQLType :: String
74+ , errHaskellType :: String
75+ , errMessage :: String }
76+ -- ^ The SQL value could not be parsed, or could not
77+ -- be represented as a valid Haskell value, or an
78+ -- unexpected low-level error occurred (e.g. mismatch
79+ -- between metadata and actual data in a row).
80+ deriving (Eq, Show, Typeable)
81+
82+instance Exception ResultError
83+
84+-- | A type that may be converted from a SQL type.
85+class Result a where
86+ convert :: Field -> Maybe ByteString -> Status ResultError a
87+ -- ^ Convert a SQL value to a Haskell value.
88+ --
89+ -- Throws a 'ResultError' if conversion fails.
90+{--
91+instance (Result a) => Result (Maybe a) where
92+ convert _ Nothing = pure Nothing
93+ convert f bs = Just <$> convert f bs
94+
95+instance Result Bool where
96+ convert = atto ok8 ((/=(0::Int)) <$> decimal)
97+
98+instance Result Int8 where
99+ convert = atto ok8 $ signed decimal
100+--}
101+instance Result Int16 where
102+ convert = atto ok16 $ signed decimal
103+
104+instance Result Int32 where
105+ convert = atto ok32 $ signed decimal
106+
107+instance Result Int where
108+ convert = atto okInt $ signed decimal
109+
110+instance Result Int64 where
111+ convert = atto ok64 $ signed decimal
112+
113+instance Result Integer where
114+ convert = atto ok64 $ signed decimal
115+{--
116+instance Result Word8 where
117+ convert = atto ok8 decimal
118+
119+instance Result Word16 where
120+ convert = atto ok16 decimal
121+
122+instance Result Word32 where
123+ convert = atto ok32 decimal
124+
125+instance Result Word where
126+ convert = atto okWord decimal
127+
128+instance Result Word64 where
129+ convert = atto ok64 decimal
130+--}
131+{--
132+instance Result Float where
133+ convert = atto ok (realToFrac <$> double)
134+ where ok = mkCompats [Float,Double,Decimal,NewDecimal,Tiny,Short,Int24]
135+
136+instance Result Double where
137+ convert = atto ok double
138+ where ok = mkCompats [Float,Double,Decimal,NewDecimal,Tiny,Short,Int24,
139+ Long]
140+--}
141+{--
142+instance Result (Ratio Integer) where
143+ convert = atto ok rational
144+ where ok = mkCompats [Float,Double,Decimal,NewDecimal,Tiny,Short,Int24,
145+ Long,LongLong]
146+--}
147+instance Result SB.ByteString where
148+ convert f = doConvert f okText $ pure
149+
150+instance Result PQ.Oid where
151+ convert f dat = PQ.Oid <$> atto (mkCompat Oid) decimal f dat
152+
153+instance Result LB.ByteString where
154+ convert f dat = LB.fromChunks . (:[]) <$> convert f dat
155+
156+
157+instance Result ST.Text where
158+ convert f {- | isText f -} = doConvert f okText $ (Success . ST.decodeUtf8)
159+{- | otherwise = incompatible f (typeOf ST.empty)
160+ "attempt to mix binary and text" -}
161+ -- FIXME: return a "Fail someUnicodeError" instead of throwing it
162+ -- check character encoding
163+
164+instance Result LT.Text where
165+ convert f dat = LT.fromStrict <$> convert f dat
166+
167+instance Result [Char] where
168+ convert f dat = ST.unpack <$> convert f dat
169+
170+instance Result UTCTime where
171+ convert f = doConvert f ok $ \bs ->
172+ case parseTime defaultTimeLocale "%F %T%Q%z" (B8.unpack bs ++ "00") of
173+ Just t -> Success t
174+ Nothing -> conversionFailed f "UTCTime" "could not parse"
175+ where ok = mkCompats [TimestampWithTimeZone]
176+{--
177+instance Result Day where
178+ convert f = flip (atto ok) f $ case fieldType f of
179+ Year -> year
180+ _ -> date
181+ where ok = mkCompats [Year,Date,NewDate]
182+ year = fromGregorian <$> decimal <*> pure 1 <*> pure 1
183+ date = fromGregorian <$> (decimal <* char '-')
184+ <*> (decimal <* char '-')
185+ <*> decimal
186+
187+instance Result TimeOfDay where
188+ convert f = flip (atto ok) f $ do
189+ hours <- decimal <* char ':'
190+ mins <- decimal <* char ':'
191+ secs <- decimal :: Parser Int
192+ case makeTimeOfDayValid hours mins (fromIntegral secs) of
193+ Just t -> return t
194+ _ -> conversionFailed f "TimeOfDay" "could not parse"
195+ where ok = mkCompats [Time]
196+
197+isText :: Field -> Bool
198+isText f = fieldCharSet f /= 63
199+--}
200+newtype Compat = Compat Word64
201+
202+mkCompats :: [BuiltinType] -> Compat
203+mkCompats = foldl' f (Compat 0) . map mkCompat
204+ where f (Compat a) (Compat b) = Compat (a .|. b)
205+
206+mkCompat :: BuiltinType -> Compat
207+mkCompat = Compat . shiftL 1 . fromEnum
208+
209+compat :: Compat -> Compat -> Bool
210+compat (Compat a) (Compat b) = a .&. b /= 0
211+{--
212+okText, ok8, ok16, ok32, ok64, okWord :: Compat
213+--}
214+okText = mkCompats [Name,Text,Char,Bpchar,Varchar]
215+{--}
216+--ok8 = mkCompats [Tiny]
217+ok16 = mkCompats [Int2]
218+ok32 = mkCompats [Int2,Int4]
219+ok64 = mkCompats [Int2,Int4,Int8]
220+#if WORD_SIZE_IN_BITS < 64
221+okInt = ok32
222+#else
223+okInt = ok64
224+#endif
225+--}
226+doConvert :: forall a . (Typeable a)
227+ => Field -> Compat -> (ByteString -> Status ResultError a)
228+ -> Maybe ByteString -> Status ResultError a
229+doConvert f types cvt (Just bs)
230+ | Just typ <- oid2builtin (typeOid f)
231+ , mkCompat typ `compat` types = cvt bs
232+ | otherwise = incompatible f (typeOf (undefined::a)) "types incompatible"
233+doConvert f _ cvt _ = Fail $ UnexpectedNull (B.unpack (typename f))
234+ (show (typeOf (undefined::a))) ""
235+
236+incompatible :: Field -> TypeRep -> String -> Status ResultError a
237+incompatible f r = Fail . Incompatible (B.unpack (typename f)) (show r)
238+
239+conversionFailed :: Field -> String -> String -> Status ResultError a
240+conversionFailed f s = Fail . ConversionFailed (B.unpack (typename f)) s
241+
242+atto :: (Typeable a) => Compat -> Parser a -> Field -> Maybe ByteString -> Status ResultError a
243+atto types p0 f dat = doConvert f types (go undefined p0) dat
244+ where
245+ go :: (Typeable a) => a -> Parser a -> ByteString -> Status ResultError a
246+ go dummy p s =
247+ case parseOnly p s of
248+ Left err -> conversionFailed f (show (typeOf dummy)) err
249+ Right v -> Success v
250+
251+instance Result RawResult where
252+ convert field rawData = Success (RawResult field rawData)
src/Database/PostgreSQL/Simple/Types.hsnew file+108
@@ -0,0 +1,108 @@
1+{-# LANGUAGE DeriveDataTypeable, DeriveFunctor, GeneralizedNewtypeDeriving #-}
2+
3+-- |
4+-- Module: Database.PostgreSQL.Simple.Types
5+-- Copyright: (c) 2011 MailRank, Inc.
6+-- (c) 2011 Leon P Smith
7+-- License: BSD3
8+-- Maintainer: Leon P Smith <leon@melding-monads.com>
9+-- Stability: experimental
10+-- Portability: portable
11+--
12+-- Basic types.
13+
14+module Database.PostgreSQL.Simple.Types
15+ (
16+ Null(..)
17+ , Only(..)
18+ , In(..)
19+ , Binary(..)
20+ , Query(..)
21+ , Oid(..)
22+ ) where
23+
24+import Blaze.ByteString.Builder (toByteString)
25+import Control.Arrow (first)
26+import Data.ByteString (ByteString)
27+import Data.Monoid (Monoid(..))
28+import Data.String (IsString(..))
29+import Data.Typeable (Typeable)
30+import qualified Blaze.ByteString.Builder.Char.Utf8 as Utf8
31+import qualified Data.ByteString as B
32+import Database.PostgreSQL.LibPQ (Oid(..))
33+
34+-- | A placeholder for the SQL @NULL@ value.
35+data Null = Null
36+ deriving (Read, Show, Typeable)
37+
38+instance Eq Null where
39+ _ == _ = False
40+ _ /= _ = False
41+
42+-- | A query string. This type is intended to make it difficult to
43+-- construct a SQL query by concatenating string fragments, as that is
44+-- an extremely common way to accidentally introduce SQL injection
45+-- vulnerabilities into an application.
46+--
47+-- This type is an instance of 'IsString', so the easiest way to
48+-- construct a query is to enable the @OverloadedStrings@ language
49+-- extension and then simply write the query in double quotes.
50+--
51+-- > {-# LANGUAGE OverloadedStrings #-}
52+-- >
53+-- > import Database.MySQL.Simple
54+-- >
55+-- > q :: Query
56+-- > q = "select ?"
57+--
58+-- The underlying type is a 'ByteString', and literal Haskell strings
59+-- that contain Unicode characters will be correctly transformed to
60+-- UTF-8.
61+newtype Query = Query {
62+ fromQuery :: ByteString
63+ } deriving (Eq, Ord, Typeable)
64+
65+instance Show Query where
66+ show = show . fromQuery
67+
68+instance Read Query where
69+ readsPrec i = fmap (first Query) . readsPrec i
70+
71+instance IsString Query where
72+ fromString = Query . toByteString . Utf8.fromString
73+
74+instance Monoid Query where
75+ mempty = Query B.empty
76+ mappend (Query a) (Query b) = Query (B.append a b)
77+ {-# INLINE mappend #-}
78+
79+-- | A single-value \"collection\".
80+--
81+-- This is useful if you need to supply a single parameter to a SQL
82+-- query, or extract a single column from a SQL result.
83+--
84+-- Parameter example:
85+--
86+-- @query c \"select x from scores where x > ?\" ('Only' (42::Int))@
87+--
88+-- Result example:
89+--
90+-- @xs <- query_ c \"select id from users\"
91+--forM_ xs $ \\('Only' id) -> {- ... -}@
92+newtype Only a = Only {
93+ fromOnly :: a
94+ } deriving (Eq, Ord, Read, Show, Typeable, Functor)
95+
96+-- | Wrap a list of values for use in an @IN@ clause. Replaces a
97+-- single \"@?@\" character with a parenthesized list of rendered
98+-- values.
99+--
100+-- Example:
101+--
102+-- > query c "select * from whatever where id in ?" (In [3,4,5])
103+newtype In a = In a
104+ deriving (Eq, Ord, Read, Show, Typeable, Functor)
105+
106+-- | Wrap a mostly-binary string to be escaped in hexadecimal.
107+newtype Binary a = Binary a
108+ deriving (Eq, Ord, Read, Show, Typeable, Functor)
tools/GenBuiltinTypes.hsnew file+185
@@ -0,0 +1,185 @@
1+------------------------------------------------------------------------------
2+-- |
3+-- Module: GenBuiltinTypes
4+-- Copyright: (c) 2011 Leon P Smith
5+-- License: BSD3
6+-- Maintainer: Leon P Smith <leon@melding-monads.com>
7+-- Stability: experimental
8+--
9+-- Queries a PostgreSQL for the Object IDs associated with built-in typenames,
10+-- and generates Haskell boilerplate to handle this mapping. The subset
11+-- of types handled this way is easily modifiable, but note that
12+-- user-defined types do not have stable oids, and can differ between
13+-- databases or dumps/reloads.
14+------------------------------------------------------------------------------
15+
16+{-# LANGUAGE OverloadedStrings, QuasiQuotes, ViewPatterns #-}
17+module GenBuiltinTypes where
18+
19+import Prelude hiding ((++))
20+
21+import StringsQQ
22+import Database.PostgreSQL.Simple
23+import qualified Data.ByteString.Char8 as B
24+import qualified Data.ByteString.Lazy as L
25+import qualified Blaze.ByteString.Builder as Blaze
26+import qualified Blaze.ByteString.Builder.ByteString as Blaze
27+import qualified Blaze.ByteString.Builder.Char8 as Blaze
28+import Data.String
29+import Data.List ( sort, intersperse )
30+import qualified Data.Map as Map
31+
32+import Data.Monoid
33+
34+(++) :: Monoid a => a -> a -> a
35+(++) = mappend
36+infixr 5 ++
37+
38+type Map = Map.Map B.ByteString Int
39+builtins :: [(B.ByteString, B.ByteString)]
40+builtins = [strings|
41+bool
42+bytea
43+char
44+name
45+int8
46+int2
47+int4
48+regproc
49+text
50+oid
51+tid
52+xid
53+cid
54+xml
55+point
56+lseg
57+path
58+box
59+polygon
60+line
61+cidr
62+float4
63+float8
64+abstime
65+reltime
66+tinterval
67+unknown
68+circle
69+money
70+macaddr
71+inet
72+bpchar
73+varchar
74+date
75+time
76+timestamp
77+timestamptz TimestampWithTimeZone
78+interval
79+timetz TimeWithTimeZone
80+bit
81+varbit
82+numeric
83+refcursor
84+record
85+void
86+|]
87+
88+instance IsString Blaze.Builder where
89+ fromString = Blaze.fromByteString . fromString
90+
91+main = do
92+ conn <- connect ConnectInfo {
93+ connectHost = "127.0.0.1",
94+ connectPort = 5432,
95+ connectUser = "aliadmin",
96+ connectPassword = "aliadmin",
97+ connectDatabase = "postgres"
98+ }
99+ xs <- query conn "SELECT typname::text, oid::int\
100+ \ FROM pg_type\
101+ \ WHERE typname IN ?"
102+ (Only (In (sort (map fst builtins))))
103+ close conn
104+ let oidmap = Map.fromList xs :: Map
105+ L.writeFile "../src/Database/PostgreSQL/Simple/BuiltinTypes.hs"
106+ (Blaze.toLazyByteString (renderFile oidmap builtins))
107+
108+padCons str = fromString (replicate (len - B.length str) ' ')
109+ where
110+ len = maximum (map (B.length . snd) builtins)
111+
112+renderOid oidmap x
113+ = case Map.lookup x oidmap of
114+ Nothing -> error (B.unpack x)
115+ Just (show -> n) -> fromString (replicate (4 - length n) ' ')
116+ ++ fromString n
117+
118+renderOid' oidmap x
119+ = case Map.lookup x oidmap of
120+ Nothing -> error (B.unpack x)
121+ Just (show -> n) -> fromString n
122+ ++ fromString (replicate (4 - length n) ' ')
123+
124+bs = Blaze.fromByteString
125+
126+renderFile :: Map -> [(B.ByteString,B.ByteString)] -> Blaze.Builder
127+renderFile oidmap builtins = ([longstring|
128+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}
129+
130+------------------------------------------------------------------------------
131+-- |
132+-- Module: Database.PostgreSQL.Simple.BuiltinTypes
133+-- Copyright: (c) 2011 Leon P Smith
134+-- License: BSD3
135+-- Maintainer: Leon P Smith <leon@melding-monads.com>
136+-- Stability: experimental
137+--
138+------------------------------------------------------------------------------
139+
140+-- Note that this file is generated by tools/GenBuiltinTypes.hs, and should
141+-- not be edited directly
142+
143+module Database.PostgreSQL.Simple.BuiltinTypes
144+ ( BuiltinType (..)
145+ , builtin2oid
146+ , oid2builtin
147+ , builtin2typname
148+ , oid2typname
149+ ) where
150+
151+import Data.Typeable
152+import Data.ByteString (ByteString)
153+import qualified Database.PostgreSQL.LibPQ as PQ
154+
155+data BuiltinType
156+ = |] ++ mconcat (intersperse "\n | " (map (Blaze.fromByteString . snd) builtins)) ++ [longstring|
157+
158+ deriving (Eq, Ord, Enum, Bounded, Read, Show, Typeable)
159+
160+builtin2oid :: BuiltinType -> PQ.Oid
161+builtin2oid typ = PQ.Oid $ case typ of
162+|] ++ mconcat [mconcat [ " ", bs y, padCons y, " -> "
163+ , renderOid oidmap x, "\n" ]
164+ | (x,y) <- builtins ] ++ [longstring|
165+
166+oid2builtin :: PQ.Oid -> Maybe BuiltinType
167+oid2builtin (PQ.Oid x) = case x of
168+|] ++ mconcat [mconcat [ " ", renderOid' oidmap x, " -> Just ", bs y, "\n" ]
169+ | (x,y) <- builtins ] ++ [longstring|
170+ _ -> Nothing
171+
172+builtin2typname :: BuiltinType -> ByteString
173+builtin2typname typ = case typ of
174+|] ++ mconcat [mconcat [ " ", bs y, padCons y, " -> ", bs x, "\n"]
175+ | (x,y) <- builtins] ++ [longstring|
176+
177+oid2typname :: PQ.Oid -> Maybe ByteString
178+oid2typname (PQ.Oid x) = case x of
179+|] ++ mconcat [mconcat [ " ", renderOid' oidmap x, " -> Just ", bs x, "\n" ]
180+ | (x,y) <- builtins ]
181+ ++ " _ -> Nothing\n"
182+ ++ mconcat [mconcat [ "\n"
183+ , bs x, " :: ByteString\n"
184+ , bs x, " = \"", bs x, "\"\n"]
185+ | (x,_y) <- builtins ])
tools/StringsQQ.hsnew file+30
@@ -0,0 +1,30 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+
3+module StringsQQ (strings, longstring) where
4+
5+import Language.Haskell.TH
6+import Language.Haskell.TH.Quote
7+
8+import Data.Char (toUpper)
9+
10+strings = QuasiQuoter { quotePat = undefined,
11+ quoteType = undefined,
12+ quoteExp = stringsExp,
13+ quoteDec = undefined }
14+
15+-- stringE = return . LitE . StringL
16+
17+stringsExp :: String -> Q Exp
18+stringsExp = foldr (\x xs -> [e| $(x) : $(xs) |]) [e| [] |]
19+ . map delta . filter (not . null) . map words . lines
20+ where delta [(x:xs)] = [e| ( $(stringE $ x:xs )
21+ , $(stringE $ toUpper x : xs ) ) |]
22+ delta [xs,ys] = [e| ( $(stringE xs) , $(stringE ys) ) |]
23+
24+longstring = QuasiQuoter { quotePat = undefined,
25+ quoteType = undefined,
26+ quoteExp = longstringExp,
27+ quoteDec = undefined }
28+
29+longstringExp ('\n':xs) = stringE xs
30+longstringExp xs = stringE xs