postgresql-query-3.0.1: src/Database/PostgreSQL/Query/TH/Enum.hs
-- | Helps to map enum types to postgresql enums.
module Database.PostgreSQL.Query.TH.Enum
( derivePgEnum
, InflectorFunc
) where
import Data.FileEmbed
import Database.PostgreSQL.Query.TH.Common
import Database.PostgreSQL.Simple.FromField
import Database.PostgreSQL.Simple.ToField
import Language.Haskell.TH
#if !MIN_VERSION_base(4,8,0)
import Data.Traversable
import Control.Applicative
#endif
import qualified Data.Text.Encoding as T
import qualified Data.Text as T
-- | Function to transform constructor name into its PG enum conterpart.
type InflectorFunc = String -> String
{-| derives 'FromField' and 'ToField' instances for a sum-type enum like
@
data Entity = Red | Green | Blue
@
-}
derivePgEnum
:: InflectorFunc
-- ^ mapping function from haskell constructor name to PG enum label
-> Name
-- ^ type to derive instances for
-> DecsQ
derivePgEnum infl typeName = do
constructors <- dataConstructors <$> reify typeName
tfInstance <- makeToField infl typeName constructors
ffInstance <- makeFromField infl typeName constructors
pure [tfInstance, ffInstance]
makeToField :: InflectorFunc
-> Name
-> [Con]
-> DecQ
makeToField i typeName constr = do
clauses <- traverse (makeToFieldClause i) constr
instanceD
(pure [])
(appT (conT ''ToField) (conT typeName))
[funD 'toField $ fmap pure clauses]
makeFromField :: InflectorFunc
-> Name
-> [Con]
-> Q Dec
makeFromField i typeName enumCons = do
f <- newName "f"
mb <- newName "mb"
byteSt <- newName "bs"
hName <- newName "h"
let
otherw = (,)
<$> normalG [|otherwise|]
<*> [|returnError ConversionFailed $(varE f) (show $(varE mb))|]
guards = map (makeFromFieldGuard i hName) enumCons ++ [otherw]
helper =
funD
hName
[clause
[varP byteSt]
(normalB [|((Just True) ==) (fmap (== $(varE byteSt)) $(varE mb))|])
[]
]
instanceD
(pure [])
(appT (conT ''FromField) (conT typeName))
[funD 'fromField [clause [varP f, varP mb] (guardedB guards) [helper]]]
makeFromFieldGuard :: InflectorFunc
-> Name -- ^ shared helper function
-> Con -- ^ constructor name
-> Q (Guard, Exp)
makeFromFieldGuard i typeName con =
flip (withEnumConstructor i) con $ \nam ec -> do
let constr = conE nam
guard <- normalG $ appE (varE typeName) ec
expr <- appE (varE 'pure) constr
pure (guard, expr)
makeToFieldClause :: InflectorFunc
-> Con
-> ClauseQ
makeToFieldClause i con =
flip (withEnumConstructor i) con $ \nam ec -> do
clause [conP nam []] (normalB [|Escape $ec|]) []
-- | Takes constructor w/o arguments and apply callback function.
-- Ejects with 'error' if called with wrong type of constructor.
withEnumConstructor :: InflectorFunc
-- ^ function to transform the constructor name
-> (Name -> ExpQ -> Q a)
-- ^ callback function from:
-- 1. haskell constructor name and
-- 2. PG enum option (ByteString)
-> Con
-- ^ constructor to decompose
-> Q a
withEnumConstructor i f = \case
(NormalC _ (_:_)) ->
error "constructors with arguments are not supported in makeToFieldClause"
(NormalC nam [] ) -> f nam inflectedBs
where inflectedT = T.pack $ i $ nameBase nam
inflectedBs = bsToExp $ T.encodeUtf8 inflectedT
_ ->
error "unsupported constructor in makeFromFieldClause"