packages feed

preql-0.6: src/Preql/FromSql/TH.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase      #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Construct FromSql instances

module Preql.FromSql.TH where

import Preql.FromSql.Class
import Preql.FromSql.Tuple
import Preql.QuasiQuoter.Common (alphabet)
import Preql.Wire.Errors (PgType(Oid))
import Preql.Wire.Internal
import qualified Preql.Wire.TypeInfo.Static as OID

import GHC.TypeNats
import Language.Haskell.TH
import qualified PostgreSQL.Binary.Decoding as PGB

-- | instance (FromSql a, FromSql b) => FromSql (a, b)
deriveFromSqlTuple :: Int -> Q [Dec]
deriveFromSqlTuple n = do
    names <- traverse newName (take n alphabet)
    let
      fields = map VarT names
      tuple = foldl AppT (TupleT n) fields
    return [fromSqlDecl tuple (tupleDataName n) fields]

-- | derive a 'FromSql' instance for a record type
-- (field names are not required, but there must be only one constructor)
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 (ConT typeN) (map VarT tyVars)
                (conN, fieldTypes) = case constructors of
                    [NormalC con elems] -> (con, [ty | (_, ty) <- elems])
                    [RecC con fields] -> (con, [ty | (_, _, ty) <- fields])
                    [InfixC (_, t1) con (_, t2)] -> (con, [t1, t2])
                    [_] -> error "deriveFromSql does not handle GADTs or constructors with class constraints"
                    _ -> error "deriveFromSql does not handle sum types"
            in return [fromSqlDecl targetTy conN fieldTypes]
        _ -> error ("deriveFromSql only handles type names, got: " ++ show tyName)

#if MIN_VERSION_template_haskell(2,17,0)
tyVarName :: TyVarBndr () -> Name
tyVarName = \case
    PlainTV name () -> name
    KindedTV name () _k -> name
#else
tyVarName :: TyVarBndr -> Name
tyVarName = \case
    PlainTV name -> name
    KindedTV name _k -> name
#endif


fromSqlDecl :: Type -> Name -> [Type] -> Dec
fromSqlDecl targetTy constructor fields =
    InstanceD Nothing context instanceHead [TySynInstD width, method] where
        context = [ ConT ''FromSql `AppT` ty | ty <- fields, hasTyVar ty ]
        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` ty | ty <- fields ])
        method = ValD
            (VarP 'fromSql)
            (NormalB (foldl
                      (\rowDecoder field -> InfixE (Just rowDecoder) (VarE 'applyDecoder) (Just field))
                      (VarE 'pureDecoder `AppE` ConE constructor)
                      (replicate (length fields) (VarE 'fromSql))))
            [] -- no where clause on the fromSql definition

-- instance (FromSqlField a, FromSqlField b) => FromSqlField (Tuple (a, b))
-- instance (FromSqlField a, FromSqlField b) => FromSql (Tuple (a, b))
deriveFromSqlFieldTuple :: Int -> Q [Dec]
deriveFromSqlFieldTuple n = do
  names <- traverse newName (take n alphabet)
  fieldOid <- [e| Oid OID.recordOid OID.array_recordOid |]
  let
    fields = map VarT names
    tuple = ConT ''Tuple `AppT` foldl AppT (TupleT n) fields
    context = [ ConT ''FromSqlField `AppT` ty | ty <- fields ]
    width = TySynEqn Nothing (ConT ''Width `AppT` tuple) (LitT (NumTyLit 1))
    tupleSizeE = LitE  (IntegerL (toInteger n))
    parser = VarE 'fmap `AppE` ConE 'Tuple `AppE` (VarE 'composite `AppE` tupleSizeE `AppE` foldl
             (\parser field -> VarE '(<*>) `AppE` parser `AppE` field)
             (VarE 'pure `AppE` ConE (tupleDataName n))
             (replicate n (VarE 'valueComposite `AppE` VarE 'fromSqlField)))
    method = ValD
      (VarP 'fromSqlField)
      (NormalB (ConE 'FieldDecoder `AppE` fieldOid `AppE` parser))
      []
  return [ InstanceD Nothing context (ConT ''FromSqlField `AppT` tuple) [method]
    , InstanceD Nothing context (ConT ''FromSql `AppT` tuple) [ ] ]


hasTyVar :: Type -> Bool
hasTyVar = \case
  VarT _ -> True
  ForallT _ _ ty -> hasTyVar ty
#if MIN_VERSION_template_haskell(2,16,0)
  ForallVisT _ ty -> hasTyVar ty
#endif
  AppT t1 t2 -> hasTyVar t1 || hasTyVar t2
  AppKindT ty _ -> hasTyVar ty
  SigT ty _ -> hasTyVar ty
  InfixT t1 _ t2 -> hasTyVar t1 || hasTyVar t2
  UInfixT t1 _ t2 -> hasTyVar t1 || hasTyVar t2
  ParensT ty -> hasTyVar ty
  ImplicitParamT _ ty -> hasTyVar ty
  _ -> False