postgresql-typed 0.3.1 → 0.3.2
raw patch · 11 files changed
+196/−98 lines, 11 filesdep ~containersdep ~time
Dependency ranges changed: containers, time
Files
- Database/PostgreSQL/Typed/Array.hs +6/−6
- Database/PostgreSQL/Typed/Dynamic.hs +12/−12
- Database/PostgreSQL/Typed/Enum.hs +36/−17
- Database/PostgreSQL/Typed/Inet.hs +47/−0
- Database/PostgreSQL/Typed/Protocol.hs +19/−19
- Database/PostgreSQL/Typed/Query.hs +43/−21
- Database/PostgreSQL/Typed/Range.hs +2/−3
- Database/PostgreSQL/Typed/TH.hs +3/−3
- Database/PostgreSQL/Typed/Types.hs +16/−12
- postgresql-typed.cabal +8/−3
- test/Main.hs +4/−2
Database/PostgreSQL/Typed/Array.hs view
@@ -10,9 +10,10 @@ module Database.PostgreSQL.Typed.Array where -import Control.Applicative ((<$>), (<$))+import Control.Applicative ((<$>)) import qualified Data.ByteString.Builder as BSB import qualified Data.ByteString.Char8 as BSC+import Data.Char (toLower) import Data.List (intersperse) import Data.Monoid ((<>), mconcat) import qualified Text.Parsec as P@@ -38,15 +39,14 @@ el Nothing = BSB.string7 "null" el (Just e) = pgDQuote (pgArrayDelim ta : "{}") $ pgEncode (pgArrayElementType ta) e instance (PGArrayType ta t, PGColumn t a) => PGColumn ta (PGArray a) where- pgDecode ta = either (error . ("pgDecode array: " ++) . show) id . P.parse pa "array" where+ pgDecode ta a = either (error . ("pgDecode array: " ++) . show) id $ P.parse pa (BSC.unpack a) a where pa = do l <- P.between (P.char '{') (P.char '}') $- P.sepBy nel (P.char (pgArrayDelim ta))+ P.sepBy el (P.char (pgArrayDelim ta)) _ <- P.eof return l- nel = P.between P.spaces P.spaces $ Nothing <$ nul P.<|> Just <$> el- nul = P.oneOf "Nn" >> P.oneOf "Uu" >> P.oneOf "Ll" >> P.oneOf "Ll"- el = pgDecode (pgArrayElementType ta) . BSC.pack <$> parsePGDQuote (pgArrayDelim ta : "{}")+ el = P.between P.spaces P.spaces $ fmap (pgDecode (pgArrayElementType ta) . BSC.pack) <$>+ parsePGDQuote (pgArrayDelim ta : "{}") (("null" ==) . map toLower) -- Just a dump of pg_type: instance PGType "boolean" => PGType "boolean[]"
Database/PostgreSQL/Typed/Dynamic.hs view
@@ -62,17 +62,17 @@ pgDecodeRep PGNullValue = Nothing pgDecodeRep v = Just (pgDecodeRep v) -instance PGRep "boolean" Bool where-instance PGRep "oid" OID where-instance PGRep "smallint" Int16 where-instance PGRep "integer" Int32 where-instance PGRep "bigint" Int64 where-instance PGRep "real" Float where-instance PGRep "double precision" Double where-instance PGRep "\"char\"" Char where-instance PGRep "text" String where+instance PGRep "boolean" Bool+instance PGRep "oid" OID+instance PGRep "smallint" Int16+instance PGRep "integer" Int32+instance PGRep "bigint" Int64+instance PGRep "real" Float+instance PGRep "double precision" Double+instance PGRep "\"char\"" Char+instance PGRep "text" String #ifdef USE_TEXT-instance PGRep "text" T.Text where+instance PGRep "text" T.Text #endif instance PGRep "date" Time.Day instance PGRep "time without time zone" Time.TimeOfDay@@ -81,10 +81,10 @@ instance PGRep "interval" Time.DiffTime instance PGRep "numeric" Rational #ifdef USE_SCIENTIFIC-instance PGRep "numeric" Scientific where+instance PGRep "numeric" Scientific #endif #ifdef USE_UUID-instance PGRep "uuid" UUID.UUID where+instance PGRep "uuid" UUID.UUID #endif -- |Create an expression that literally substitutes each instance of @${expr}@ for the result of @pgSafeLiteral expr@.
Database/PostgreSQL/Typed/Enum.hs view
@@ -6,12 +6,16 @@ -- Support for PostgreSQL enums. module Database.PostgreSQL.Typed.Enum - ( makePGEnum+ ( PGEnum+ , pgEnumValues+ , makePGEnum ) where import Control.Monad (when)+import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BSC import qualified Data.ByteString.UTF8 as U+import Data.Typeable (Typeable) import qualified Language.Haskell.TH as TH import Database.PostgreSQL.Typed.Protocol@@ -19,17 +23,26 @@ import Database.PostgreSQL.Typed.Types import Database.PostgreSQL.Typed.Dynamic +-- |A type based on a PostgreSQL enum. Automatically instantiated by 'makePGEnum'.+class (Eq a, Ord a, Enum a, Bounded a, Show a) => PGEnum a++-- |List of all the values in the enum along with their database names.+pgEnumValues :: PGEnum a => [(a, String)]+pgEnumValues = map (\e -> (e, show e)) $ enumFromTo minBound maxBound+ -- |Create a new enum type corresponding to the given PostgreSQL enum type. -- For example, if you have @CREATE TYPE foo AS ENUM (\'abc\', \'DEF\');@, then -- @makePGEnum \"foo\" \"Foo\" (\"Foo_\"++)@ will be equivalent to: -- --- @--- data Foo = Foo_abc | Foo_DEF deriving (Eq, Ord, Enum, Bounded, Show, Read)--- instance PGType Foo where ...--- registerPGType \"foo\" (ConT ''Foo)--- @+-- > data Foo = Foo_abc | Foo_DEF deriving (Eq, Ord, Enum, Bounded, Typeable)+-- > instance Show Foo where show Foo_abc = "abc" ...+-- > instance PGType "foo"+-- > instance PGParameter "foo" Foo where ...+-- > instance PGColumn "foo" Foo where ...+-- > instance PGRep "foo" Foo+-- > instance PGEnum Foo where pgEnumValues = [(Foo_abc, "abc"), (Foo_DEF, "DEF")] ----- Requires language extensions: TemplateHaskell, FlexibleInstances, MultiParamTypeClasses, DataKinds+-- Requires language extensions: TemplateHaskell, FlexibleInstances, MultiParamTypeClasses, DeriveDataTypeable, DataKinds makePGEnum :: String -- ^ PostgreSQL enum type name -> String -- ^ Haskell type to create -> (String -> String) -- ^ How to generate constructor names from enum values, e.g. @(\"Type_\"++)@@@ -39,24 +52,30 @@ pgSimpleQuery c $ "SELECT enumlabel FROM pg_catalog.pg_enum JOIN pg_catalog.pg_type t ON enumtypid = t.oid WHERE typtype = 'e' AND format_type(t.oid, -1) = " ++ pgQuote name ++ " ORDER BY enumsortorder" when (null vals) $ fail $ "makePGEnum: enum " ++ name ++ " not found" let - valn = map (\[PGTextValue v] -> (TH.StringL (BSC.unpack v), TH.mkName $ valnf (U.toString v))) vals+ valn = map (\[PGTextValue v] -> let u = U.toString v in (TH.mkName $ valnf u, map (TH.IntegerL . fromIntegral) $ BS.unpack v, TH.StringL u)) vals dv <- TH.newName "x"- ds <- TH.newName "s" return- [ TH.DataD [] typn [] (map (\(_, n) -> TH.NormalC n []) valn) [''Eq, ''Ord, ''Enum, ''Bounded, ''Show, ''Read]+ [ TH.DataD [] typn [] (map (\(n, _, _) -> TH.NormalC n []) valn) [''Eq, ''Ord, ''Enum, ''Bounded, ''Typeable]+ , TH.InstanceD [] (TH.ConT ''Show `TH.AppT` typt)+ [ TH.FunD 'show $ map (\(n, _, v) -> TH.Clause [TH.ConP n []]+ (TH.NormalB $ TH.LitE v) []) valn+ ] , TH.InstanceD [] (TH.ConT ''PGType `TH.AppT` typl) [] , TH.InstanceD [] (TH.ConT ''PGParameter `TH.AppT` typl `TH.AppT` typt)- [ TH.FunD 'pgEncode $ map (\(l, n) -> TH.Clause [TH.WildP, TH.ConP n []]- (TH.NormalB $ TH.VarE 'BSC.pack `TH.AppE` TH.LitE l) []) valn ]+ [ TH.FunD 'pgEncode $ map (\(n, l, _) -> TH.Clause [TH.WildP, TH.ConP n []]+ (TH.NormalB $ TH.VarE 'BS.pack `TH.AppE` TH.ListE (map TH.LitE l)) []) valn+ ] , TH.InstanceD [] (TH.ConT ''PGColumn `TH.AppT` typl `TH.AppT` typt) [ TH.FunD 'pgDecode [TH.Clause [TH.WildP, TH.VarP dv]- (TH.NormalB $ TH.CaseE (TH.VarE 'BSC.unpack `TH.AppE` TH.VarE dv) $ map (\(l, n) ->- TH.Match (TH.LitP l) (TH.NormalB $ TH.ConE n) []) valn ++- [TH.Match (TH.VarP ds) (TH.NormalB $ TH.AppE (TH.VarE 'error) $- TH.InfixE (Just $ TH.LitE (TH.StringL ("pgDecode " ++ name ++ ": "))) (TH.VarE '(++)) (Just $ TH.VarE ds))+ (TH.NormalB $ TH.CaseE (TH.VarE 'BS.unpack `TH.AppE` TH.VarE dv) $ map (\(n, l, _) ->+ TH.Match (TH.ListP (map TH.LitP l)) (TH.NormalB $ TH.ConE n) []) valn +++ [TH.Match TH.WildP (TH.NormalB $ TH.AppE (TH.VarE 'error) $+ TH.InfixE (Just $ TH.LitE (TH.StringL ("pgDecode " ++ name ++ ": "))) (TH.VarE '(++)) (Just $ TH.VarE 'BSC.unpack `TH.AppE` TH.VarE dv)) []])- []] ]+ []] + ] , TH.InstanceD [] (TH.ConT ''PGRep `TH.AppT` typl `TH.AppT` typt) []+ , TH.InstanceD [] (TH.ConT ''PGEnum `TH.AppT` typt) [] ] where typn = TH.mkName typs
+ Database/PostgreSQL/Typed/Inet.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies, DataKinds #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- |+-- Module: Database.PostgreSQL.Typed.Inet+-- Copyright: 2015 Dylan Simon+-- +-- Representaion of PostgreSQL's inet/cidr types using "Network.Socket".+-- We don't (yet) supply PGColumn (parsing) instances.++module Database.PostgreSQL.Typed.Inet where++import qualified Data.ByteString.Char8 as BSC+import Data.Maybe (fromJust)+import qualified Network.Socket as Net+import System.IO.Unsafe (unsafeDupablePerformIO)++import Database.PostgreSQL.Typed.Types++data PGInet + = PGInet+ { pgInetAddr :: !Net.HostAddress+ , pgInetMask :: !Int+ }+ | PGInet6+ { pgInetAddr6 :: !Net.HostAddress6+ , pgInetMask :: !Int+ }++sockAddrPGInet :: Net.SockAddr -> Maybe PGInet+sockAddrPGInet (Net.SockAddrInet _ a) = Just $ PGInet a 32+sockAddrPGInet (Net.SockAddrInet6 _ _ a _) = Just $ PGInet6 a 128+sockAddrPGInet _ = Nothing++instance Show PGInet where+ -- This is how Network.Socket's Show SockAddr does it:+ show (PGInet a 32) = unsafeDupablePerformIO $ Net.inet_ntoa a+ show (PGInet a m) = show (PGInet a 32) ++ '/' : show m+ show (PGInet6 a 128) = fromJust $ fst $ unsafeDupablePerformIO $+ Net.getNameInfo [Net.NI_NUMERICHOST] True False (Net.SockAddrInet6 0 0 a 0)+ show (PGInet6 a m) = show (PGInet6 a 128) ++ '/' : show m++instance PGType "inet"+instance PGType "cidr"+instance PGParameter "inet" PGInet where+ pgEncode _ = BSC.pack . show+instance PGParameter "cidr" PGInet where+ pgEncode _ = BSC.pack . show
Database/PostgreSQL/Typed/Protocol.hs view
@@ -540,14 +540,14 @@ pgFlush h go start where go = (pgReceive h >>=)- start (RowDescription rd) = go (row (map colBinary rd))- start (CommandComplete c) = got c+ start (RowDescription rd) = go $ row (map colBinary rd) id+ start (CommandComplete c) = got c [] start EmptyQueryResponse = return (0, []) start m = fail $ "pgSimpleQuery: unexpected response: " ++ show m- row bc (DataRow fs) = second (fixBinary bc fs :) <$> go (row bc)- row _ (CommandComplete c) = got c- row _ m = fail $ "pgSimpleQuery: unexpected row: " ++ show m- got c = return (rowsAffected c, [])+ row bc r (DataRow fs) = go $ row bc (r . (fixBinary bc fs :))+ row _ r (CommandComplete c) = got c (r [])+ row _ _ m = fail $ "pgSimpleQuery: unexpected row: " ++ show m+ got c r = return (rowsAffected c, r) pgPreparedBind :: PGConnection -> String -> [OID] -> PGValues -> [Bool] -> IO (IO ()) pgPreparedBind c@PGConnection{ connPreparedStatements = psr } sql types bind bc = do@@ -583,13 +583,13 @@ pgSend c Sync pgFlush c start- go+ go id where- go = pgReceive c >>= row- row (DataRow fs) = second (fixBinary bc fs :) <$> go- row (CommandComplete r) = return (rowsAffected r, [])- row EmptyQueryResponse = return (0, [])- row m = fail $ "pgPreparedQuery: unexpected row: " ++ show m+ go r = pgReceive c >>= row r+ row r (DataRow fs) = go (r . (fixBinary bc fs :))+ row r (CommandComplete d) = return (rowsAffected d, r [])+ row r EmptyQueryResponse = return (0, r [])+ row _ m = fail $ "pgPreparedQuery: unexpected row: " ++ show m -- |Like 'pgPreparedQuery' but requests results lazily in chunks of the given size. -- Does not use a named portal, so other requests may not intervene.@@ -600,18 +600,18 @@ unsafeInterleaveIO $ do execute start- go+ go id where execute = do pgSend c $ Execute count pgSend c $ Flush pgFlush c- go = pgReceive c >>= row- row (DataRow fs) = (fixBinary bc fs :) <$> go- row PortalSuspended = unsafeInterleaveIO (execute >> go)- row (CommandComplete _) = return []- row EmptyQueryResponse = return []- row m = fail $ "pgPreparedLazyQuery: unexpected row: " ++ show m+ go r = pgReceive c >>= row r+ row r (DataRow fs) = go (r . (fixBinary bc fs :))+ row r PortalSuspended = r <$> unsafeInterleaveIO (execute >> go id)+ row r (CommandComplete _) = return (r [])+ row r EmptyQueryResponse = return (r [])+ row _ m = fail $ "pgPreparedLazyQuery: unexpected row: " ++ show m -- |Close a previously prepared query (if necessary). pgCloseStatement :: PGConnection -> String -> [OID] -> IO ()
Database/PostgreSQL/Typed/Query.hs view
@@ -7,6 +7,7 @@ , rawPGPreparedQuery , QueryFlags(..) , simpleQueryFlags+ , parseQueryFlags , makePGQuery , pgSQL , pgExecute@@ -20,8 +21,10 @@ import Control.Monad (when, mapAndUnzipM) import Data.Array (listArray, (!), inRange) import Data.Char (isDigit, isSpace)+import qualified Data.Foldable as Fold import Data.List (dropWhileEnd) import Data.Maybe (fromMaybe, isNothing)+import Data.String (IsString(..)) import Data.Word (Word32) import Language.Haskell.Meta.Parse (parseExp) import qualified Language.Haskell.TH as TH@@ -37,6 +40,10 @@ class PGQuery q a | q -> a where -- |Execute a query and return the number of rows affected (or -1 if not known) and a list of results. pgRunQuery :: PGConnection -> q -> IO (Int, [a])+ -- |Change the raw SQL query stored within this query.+ -- This is unsafe because the query has already been type-checked, so any change must not change the number or type of results or placeholders (so adding additional static WHERE or ORDER BY clauses is generally safe).+ -- This is useful in cases where you need to construct some part of the query dynamically, but still want to infer the result types.+ unsafeModifyQuery :: q -> (String -> String) -> q class PGQuery q PGValues => PGRawQuery q -- |Execute a query that does not return results.@@ -48,22 +55,27 @@ pgQuery :: PGQuery q a => PGConnection -> q -> IO [a] pgQuery c q = snd <$> pgRunQuery c q +instance PGQuery String PGValues where+ pgRunQuery c sql = pgSimpleQuery c sql+ unsafeModifyQuery q f = f q -data SimpleQuery = SimpleQuery String+newtype SimpleQuery = SimpleQuery String instance PGQuery SimpleQuery PGValues where pgRunQuery c (SimpleQuery sql) = pgSimpleQuery c sql-instance PGRawQuery SimpleQuery where-+ unsafeModifyQuery (SimpleQuery sql) f = SimpleQuery $ f sql+instance PGRawQuery SimpleQuery data PreparedQuery = PreparedQuery String [OID] PGValues [Bool] instance PGQuery PreparedQuery PGValues where pgRunQuery c (PreparedQuery sql types bind bc) = pgPreparedQuery c sql types bind bc-instance PGRawQuery PreparedQuery where+ unsafeModifyQuery (PreparedQuery sql types bind bc) f = PreparedQuery (f sql) types bind bc+instance PGRawQuery PreparedQuery data QueryParser q a = QueryParser (PGTypeEnv -> q) (PGTypeEnv -> PGValues -> a) instance PGRawQuery q => PGQuery (QueryParser q a) a where pgRunQuery c (QueryParser q p) = second (fmap $ p e) <$> pgRunQuery c (q e) where e = pgTypeEnv c+ unsafeModifyQuery (QueryParser q p) f = QueryParser (\e -> unsafeModifyQuery (q e) f) p instance Functor (QueryParser q) where fmap f (QueryParser q p) = QueryParser q (\e -> f . p e)@@ -80,6 +92,9 @@ rawPGSimpleQuery :: String -> PGSimpleQuery PGValues rawPGSimpleQuery = rawParser . SimpleQuery +instance IsString (PGSimpleQuery PGValues) where+ fromString = rawPGSimpleQuery+ -- |Make a prepared query directly from a query string and bind parameters, with no type inference rawPGPreparedQuery :: String -> PGValues -> PGPreparedQuery PGValues rawPGPreparedQuery sql bind = rawParser $ PreparedQuery sql [] bind []@@ -133,20 +148,20 @@ -- |Flags affecting how and what type of query to build with 'makePGQuery'. data QueryFlags = QueryFlags- { flagQuery :: Bool -- ^ Create a query -- otherwise just call 'pgSubstituteLiterals' to create a string (SQL fragment)- , flagNullable :: Bool -- ^ Assume all results are nullable and don't try to guess.+ { flagQuery :: Bool -- ^ Create a query -- otherwise just call 'pgSubstituteLiterals' to create a string (SQL fragment).+ , flagNullable :: Maybe Bool -- ^ Disable nullability inference, treating all values as nullable (if 'True') or not (if 'False'). , flagPrepare :: Maybe [String] -- ^ Prepare and re-use query, binding parameters of the given types (inferring the rest, like PREPARE). } -- |'QueryFlags' for a default (simple) query. simpleQueryFlags :: QueryFlags-simpleQueryFlags = QueryFlags True False Nothing+simpleQueryFlags = QueryFlags True Nothing Nothing -- |Construct a 'PGQuery' from a SQL string. makePGQuery :: QueryFlags -> String -> TH.ExpQ makePGQuery QueryFlags{ flagQuery = False } sqle = pgSubstituteLiterals sqle makePGQuery QueryFlags{ flagNullable = nulls, flagPrepare = prep } sqle = do- (pt, rt) <- TH.runIO $ tpgDescribe sqlp (fromMaybe [] prep) (not nulls)+ (pt, rt) <- TH.runIO $ tpgDescribe sqlp (fromMaybe [] prep) (isNothing nulls) when (length pt < length exprs) $ fail "Not all expression placeholders were recognized by PostgreSQL; literal occurrences of '${' may need to be escaped with '$${'" e <- TH.newName "_tenv"@@ -160,7 +175,7 @@ v <- TH.newName $ 'c':tpgValueName t return ( TH.VarP v- , tpgTypeDecoder t e `TH.AppE` TH.VarE v+ , tpgTypeDecoder (Fold.and nulls) t e `TH.AppE` TH.VarE v , tpgTypeBinary t e )) rt foldl TH.AppE (TH.LamE vars $ TH.ConE 'QueryParser@@ -184,17 +199,23 @@ (sqlp, exprs) = sqlPlaceholders sqle parse e = either (fail . (++) ("Failed to parse expression {" ++ e ++ "}: ")) return $ parseExp e -qqQuery :: QueryFlags -> String -> TH.ExpQ-qqQuery f@QueryFlags{ flagQuery = True, flagPrepare = Nothing } ('#':q) = qqQuery f{ flagQuery = False } q-qqQuery f@QueryFlags{ flagQuery = True, flagNullable = False } ('?':q) = qqQuery f{ flagNullable = True } q-qqQuery f@QueryFlags{ flagQuery = True, flagPrepare = Nothing } ('$':q) = qqQuery f{ flagPrepare = Just [] } q-qqQuery f@QueryFlags{ flagQuery = True, flagPrepare = Just [] } ('(':s) = qqQuery f{ flagPrepare = Just args } =<< sql r where- args = map trim $ splitCommas arg- (arg, r) = break (')' ==) s- sql (')':q) = return q- sql _ = fail "pgSQL: unterminated argument list" -qqQuery f q = makePGQuery f q+-- |Parse flags off the beginning of a query string, returning the flags and the remaining string.+parseQueryFlags :: String -> (QueryFlags, String)+parseQueryFlags = pqf simpleQueryFlags where+ pqf f@QueryFlags{ flagQuery = True, flagPrepare = Nothing } ('#':q) = pqf f{ flagQuery = False } q+ pqf f@QueryFlags{ flagQuery = True, flagNullable = Nothing } ('?':q) = pqf f{ flagNullable = Just True } q+ pqf f@QueryFlags{ flagQuery = True, flagNullable = Nothing } ('!':q) = pqf f{ flagNullable = Just False } q+ pqf f@QueryFlags{ flagQuery = True, flagPrepare = Nothing } ('$':q) = pqf f{ flagPrepare = Just [] } q+ pqf f@QueryFlags{ flagQuery = True, flagPrepare = Just [] } ('(':s) = pqf f{ flagPrepare = Just args } (sql r) where+ args = map trim $ splitCommas arg+ (arg, r) = break (')' ==) s+ sql (')':q) = q+ sql _ = error "pgSQL: unterminated argument list" + pqf f q = (f, q) +qqQuery :: String -> TH.ExpQ+qqQuery = uncurry makePGQuery . parseQueryFlags+ qqTop :: Bool -> String -> TH.DecsQ qqTop True ('!':sql) = qqTop False sql qqTop err sql = do@@ -215,7 +236,8 @@ -- -- The statement may start with one of more special flags affecting the interpretation: ----- [@?@] To disable nullability inference, treating all result values as nullable, thus returning 'Maybe' values regardless of inferred nullability.+-- [@?@] To disable nullability inference, treating all result values as nullable, thus returning 'Maybe' values regardless of inferred nullability. This makes unexpected NULL errors impossible.+-- [@!@] To disable nullability inference, treating all result values as /not/ nullable, thus only returning 'Maybe' where requested. This is makes unexpected NULL errors more likely. -- [@$@] To create a 'PGPreparedQuery' rather than a 'PGSimpleQuery', by default inferring parameter types. -- [@$(type,...)@] To specify specific types to a prepared query (see <http://www.postgresql.org/docs/current/static/sql-prepare.html> for details). -- [@#@] Only do literal @${}@ substitution using 'pgSubstituteLiterals' and return a string, not a query.@@ -224,7 +246,7 @@ -- Here the query can only be prefixed with @!@ to make errors non-fatal. pgSQL :: QuasiQuoter pgSQL = QuasiQuoter- { quoteExp = qqQuery simpleQueryFlags+ { quoteExp = qqQuery , quoteType = const $ fail "pgSQL not supported in types" , quotePat = const $ fail "pgSQL not supported in patterns" , quoteDec = qqTop True
Database/PostgreSQL/Typed/Range.hs view
@@ -167,12 +167,11 @@ pb (Just b) = pgDQuote "(),[]" $ pgEncode (pgRangeElementType tr) b pc c o b = BSB.char7 $ if boundClosed b then c else o instance (PGRangeType tr t, PGColumn t a) => PGColumn tr (Range a) where- pgDecode tr = either (error . ("pgDecode range: " ++) . show) id . P.parse per "range" where+ pgDecode tr a = either (error . ("pgDecode range: " ++) . show) id $ P.parse per (BSC.unpack a) a where per = Empty <$ pe P.<|> pr pe = P.oneOf "Ee" >> P.oneOf "Mm" >> P.oneOf "Pp" >> P.oneOf "Tt" >> P.oneOf "Yy"- pp = pgDecode (pgRangeElementType tr) . BSC.pack <$> parsePGDQuote "(),[]"+ pb = fmap (pgDecode (pgRangeElementType tr) . BSC.pack) <$> parsePGDQuote "(),[]" null pc c o = True <$ P.char c P.<|> False <$ P.char o- pb = P.optionMaybe $ P.between P.spaces P.spaces $ pp mb = maybe Unbounded . Bounded pr = do lc <- pc '[' '('
Database/PostgreSQL/Typed/TH.hs view
@@ -169,9 +169,9 @@ else 'pgEncodeParameter -- |TH expression to decode a 'Maybe' 'L.ByteString' to a ('Maybe') 'PGColumn' value.-tpgTypeDecoder :: TPGValueInfo -> TH.Name -> TH.Exp-tpgTypeDecoder v = typeApply (tpgValueType v) $- if tpgValueNullable v+tpgTypeDecoder :: Bool -> TPGValueInfo -> TH.Name -> TH.Exp+tpgTypeDecoder nulls v = typeApply (tpgValueType v) $+ if nulls && tpgValueNullable v then 'pgDecodeColumn else 'pgDecodeColumnNotNull
Database/PostgreSQL/Typed/Types.hs view
@@ -14,6 +14,7 @@ , PGTypeName(..) , PGTypeEnv(..) , unknownPGTypeEnv+ , PGRecord(..) -- * Marshalling classes , PGType(..)@@ -192,11 +193,14 @@ bs = BSBP.liftFixedToBounded $ ((,) '\\') BSBP.>$< (BSBP.char7 BSBP.>*< BSBP.word8) -- |Parse double-quoted values ala 'pgDQuote'.-parsePGDQuote :: P.Stream s m Char => String -> P.ParsecT s u m String-parsePGDQuote unsafe = (q P.<|> uq) where+parsePGDQuote :: P.Stream s m Char => String -> (String -> Bool) -> P.ParsecT s u m (Maybe String)+parsePGDQuote unsafe isnul = (Just <$> q P.<|> mnul <$> uq) where q = P.between (P.char '"') (P.char '"') $ P.many $ (P.char '\\' >> P.anyChar) P.<|> P.noneOf "\\\""- uq = P.many1 (P.noneOf ('"':'\\':unsafe))+ uq = P.many (P.noneOf ('"':'\\':unsafe))+ mnul s+ | isnul s = Nothing+ | otherwise = Just s #ifdef USE_BINARY binDec :: PGType t => BinD.D a -> PGTypeName t -> PGBinaryValue -> a@@ -455,7 +459,7 @@ -- PostgreSQL stores months and days separately in intervals, but DiffTime does not. -- We collapse all interval fields into seconds instance PGColumn "interval" Time.DiffTime where- pgDecode _ = either (error . ("pgDecode interval: " ++) . show) id . P.parse ps "interval" where+ pgDecode _ a = either (error . ("pgDecode interval: " ++) . show) id $ P.parse ps (BSC.unpack a) a where ps = do _ <- P.char 'P' d <- units [('Y', 12*month), ('M', month), ('W', 7*day), ('D', day)]@@ -540,21 +544,21 @@ #endif -- |Generic class of composite (row or record) types.+newtype PGRecord = PGRecord [Maybe PGTextValue] class PGType t => PGRecordType t-instance PGRecordType t => PGParameter t [Maybe PGTextValue] where- pgEncode _ l =+instance PGRecordType t => PGParameter t PGRecord where+ pgEncode _ (PGRecord l) = buildPGValue $ BSB.char7 '(' <> mconcat (intersperse (BSB.char7 ',') $ map (maybe mempty (pgDQuote "(),")) l) <> BSB.char7 ')' where- pgLiteral _ l =+ pgLiteral _ (PGRecord l) = "ROW(" ++ intercalate "," (map (maybe "NULL" (pgQuote . BSU.toString)) l) ++ ")" where-instance PGRecordType t => PGColumn t [Maybe PGTextValue] where- pgDecode _ = either (error . ("pgDecode record: " ++) . show) id . P.parse pa "record" where+instance PGRecordType t => PGColumn t PGRecord where+ pgDecode _ a = either (error . ("pgDecode record: " ++) . show) PGRecord $ P.parse pa (BSC.unpack a) a where pa = do l <- P.between (P.char '(') (P.char ')') $- P.sepBy nel (P.char ',')+ P.sepBy el (P.char ',') _ <- P.eof return l- nel = P.optionMaybe $ P.between P.spaces P.spaces el- el = BSC.pack <$> parsePGDQuote "(),"+ el = fmap BSC.pack <$> parsePGDQuote "()," null instance PGType "record" -- |The generic anonymous record type, as created by @ROW@.
postgresql-typed.cabal view
@@ -1,11 +1,11 @@ Name: postgresql-typed-Version: 0.3.1+Version: 0.3.2 Cabal-Version: >= 1.8 License: BSD3 License-File: COPYING Copyright: 2010-2013 Chris Forno, 2014-2015 Dylan Simon Author: Dylan Simon-Maintainer: dylan@dylex.net+Maintainer: Dylan Simon <dylan-pgtyped@dylex.net> Stability: alpha Bug-Reports: https://github.com/dylex/postgresql-typed/issues Homepage: https://github.com/dylex/postgresql-typed@@ -46,7 +46,11 @@ Library Build-Depends: base >= 4.7 && < 5,- array, binary, containers, old-locale, time,+ array,+ binary,+ containers < 0.5.6,+ old-locale,+ time < 1.5, bytestring >= 0.10.2, template-haskell, haskell-src-meta,@@ -62,6 +66,7 @@ Database.PostgreSQL.Typed.Enum Database.PostgreSQL.Typed.Array Database.PostgreSQL.Typed.Range+ Database.PostgreSQL.Typed.Inet Database.PostgreSQL.Typed.Dynamic Database.PostgreSQL.Typed.TemplatePG Other-Modules:
test/Main.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, DataKinds #-}+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, DataKinds, DeriveDataTypeable #-} -- {-# OPTIONS_GHC -ddump-splices #-} module Main (main) where @@ -45,7 +45,7 @@ d = Time.localDay t p = -34881559 :: Time.DiffTime s = "\"hel\\o'"- l = [Just "a\\\"b,c", Nothing]+ l = [Just "a\\\"b,c", Nothing, Just "null", Just "nullish"] r = Range.normal (Just (-2 :: Int32)) Nothing e = MyEnum_XX_ye [(Just b', Just i', Just f', Just s', Just d', Just t', Just z', Just p', Just l', Just r', Just e')] <- pgQuery c@@ -60,6 +60,8 @@ ["line"] <- preparedApply c 628 assert $ [pgSQL|#abc${f}def|] == "abc3.14::realdef"++ assert $ pgEnumValues == [(MyEnum_abc, "abc"), (MyEnum_DEF, "DEF"), (MyEnum_XX_ye, "XX_ye")] pgDisconnect c exitSuccess