preql-0.4: src/Preql/FromSql/TH.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Construct FromSql instances
module Preql.FromSql.TH where
import Preql.FromSql.Class
import Preql.QuasiQuoter.Common (alphabet)
import Preql.Wire.Internal
import GHC.TypeNats
import Language.Haskell.TH
deriveFromSqlTuple :: Int -> Q [Dec]
deriveFromSqlTuple n = do
names <- traverse newName (take n alphabet)
let tuple = foldl AppT (TupleT n) (map VarT names)
return [fromSqlDecl names tuple (tupleDataName n) n]
deriveFromSql :: Name -> Q [Dec]
deriveFromSql tyName = do
info <- reify tyName
case info of
TyConI (DataD _cxt typeN binders _kind constructors _deriving) ->
let
tyVars = map tyVarName binders
targetTy = foldl AppT (VarT typeN) (map VarT tyVars)
(conN, fieldCount) = case constructors of
[NormalC con elems] -> (con, length elems)
[RecC con fields] -> (con, length fields)
[InfixC _ con _] -> (con, 2)
[_] -> error "deriveFromSql does not handle GADTs or constructors with class constraints"
_ -> error "deriveFromSql does not handle sum types"
in return [fromSqlDecl tyVars targetTy conN fieldCount]
_ -> error ("deriveFromSql only handles type names, got: " ++ show tyName)
tyVarName :: TyVarBndr -> Name
tyVarName = \case
PlainTV name -> name
KindedTV name _k -> name
fromSqlDecl :: [Name] -> Type -> Name -> Int -> Dec
fromSqlDecl tyVars targetTy constructor fieldCount =
InstanceD Nothing context instanceHead [TySynInstD width, method] where
context = [ ConT ''FromSql `AppT` VarT n | n <- tyVars ]
instanceHead = ConT ''FromSql `AppT` targetTy
width = TySynEqn Nothing
(ConT ''Width `AppT` targetTy)
(foldl (\a b -> ConT ''(+) `AppT` a `AppT` b) (LitT (NumTyLit 0))
[ ConT ''Width `AppT` VarT n | n <- tyVars ])
method = ValD
(VarP 'fromSql)
(NormalB (foldl
(\rowDecoder field -> InfixE (Just rowDecoder) (VarE 'applyDecoder) (Just field))
(VarE 'pureDecoder `AppE` ConE constructor)
(replicate fieldCount (VarE 'fromSql))))
[] -- no where clause on the fromSql definition