postgresql-simple-bind-0.1.0.0: src/Database/PostgreSQL/Simple/Bind/Implementation.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}
{-|
Module: Database.PostgreSQL.Simple.Bind.Implementation
Copyright: (c) 2016 Al Zohali
License: GPL3
Maintainer: Al Zohali <zohl@fmap.me>
Stability: experimental
-}
module Database.PostgreSQL.Simple.Bind.Implementation (
bindFunction
, PostgresType
) where
import Control.Applicative
import Data.List
import Data.Text (Text, toLower, pack, unpack)
import Data.Time
import Data.Maybe
import Database.PostgreSQL.Simple
import Database.PostgreSQL.Simple.FromField
import Database.PostgreSQL.Simple.ToField
import Database.PostgreSQL.Simple.Types
import GHC.TypeLits
import Language.Haskell.TH.Syntax
import qualified Data.ByteString.Char8 as BS
import Database.PostgreSQL.Simple.Bind.Util (unwrapRow, unwrapColumn, mkFunctionName, Options(..))
import Database.PostgreSQL.Simple.Bind.Representation
-- | Mapping from PostgreSQL types to Haskell types.
type family PostgresType (a :: Symbol)
-- | Function that constructs binding for PostgreSQL stored function by it's signature.
bindFunction :: Options -> Text -> Q [Dec]
bindFunction opt = (mkFunction opt) . parsePGFunction
data Argument = forall a . (ToField a) => MandatoryArg String a
| forall a . (ToField a) => OptionalArg String (Maybe a)
instance ToField Argument where
toField (MandatoryArg _ x) = toField x
toField (OptionalArg _ (Just x)) = toField x
toField (OptionalArg _ Nothing) = error "OptionalArg::toField: encountered Nothing"
formatArgument :: Argument -> String
formatArgument (MandatoryArg name _) = "?"
formatArgument (OptionalArg name (Just _)) = name ++ " => ?"
formatArguments :: [Argument] -> String
formatArguments = concat . (intersperse ",") . (map formatArgument)
isActual :: Argument -> Bool
isActual (OptionalArg _ Nothing) = False
isActual _ = True
filterArguments :: [Argument] -> [Argument]
filterArguments = filter isActual
mkFunction :: Options -> PGFunction -> Q [Dec]
mkFunction opt f = sequence $ (($ f) . ($ opt)) <$> [mkFunctionT, mkFunctionE]
postgresT t = AppT (ConT ''PostgresType) (LitT (StrTyLit t))
mkContextT :: Name -> String -> Name -> [Type]
mkContextT c t n = [
EqualityT `AppT` (postgresT t) `AppT` (VarT n)
, (ConT c) `AppT` (VarT n)] where
mkResultT :: PGResult -> Q ([Name], [Type], Type)
mkResultT (PGSingle t) = do
name <- newName "y"
return ([name], mkContextT ''FromField t name, VarT name)
mkResultT (PGSetOf t) = do
(names, context, clause) <- mkResultT (PGSingle t)
return (names, context, AppT ListT clause)
mkResultT (PGTable cs) = do
names <- sequence $ replicate (length cs) (newName "y")
let context = concat $ zipWith (\(PGColumn _ t) n -> mkContextT ''FromField t n) cs names
let clause = AppT ListT $ foldl AppT (TupleT (length cs)) $ map VarT names
return (names, context, clause)
mkArgsT :: [PGArgument] -> Q ([Name], [Type], [Type])
mkArgsT cs = do
names <- sequence $ replicate (length cs) (newName "x")
let context = concat $ zipWith (\(PGArgument _ t _) n -> mkContextT ''ToField t n) cs names
let defWrap d = case d of
True -> AppT (ConT ''Maybe)
False -> id
let clause = zipWith (\(PGArgument _ _ d) -> (defWrap d) . VarT) cs names
return (names, context, clause)
mkFunctionT :: Options -> PGFunction -> Q Dec
mkFunctionT opt f@(PGFunction schema name args ret) = do
(argNames, argContext, argClause) <- mkArgsT args
(retNames, retContext, retClause) <- mkResultT ret
let vars = map PlainTV (argNames ++ retNames)
let context = argContext ++ retContext
let chain x = AppT (AppT ArrowT x)
let clause = foldr1 chain $ (ConT ''Connection):(argClause ++ [AppT (ConT ''IO) retClause])
return $ SigD (mkName (mkFunctionName opt f)) $ ForallT vars context clause
mkSqlQuery :: PGFunction -> Name -> Exp
mkSqlQuery (PGFunction schema name args ret) argsName = toQuery $ foldr1 AppE [
(AppE (VarE '(++)) (mkStrLit $ concat [prefix, " ", functionName, "("]))
, (AppE (VarE '(++)) ((VarE 'formatArguments) `AppE` (VarE argsName)))
, (mkStrLit ")")] where
prefix = case ret of
PGTable _ -> "select * from"
_ -> "select"
functionName = case schema of
"" -> name
_ -> schema ++ "." ++ name
mkStrLit s = LitE (StringL s)
toQuery = AppE (ConE 'Query) . AppE (VarE 'BS.pack)
unwrapE :: PGResult -> Exp -> Exp
unwrapE (PGSingle _) q = (VarE 'fmap) `AppE` (VarE 'unwrapRow) `AppE` q
unwrapE (PGSetOf _) q = (VarE 'fmap) `AppE` (VarE 'unwrapColumn) `AppE` q
unwrapE (PGTable _) q = q
mkFunctionE :: Options -> PGFunction -> Q Dec
mkFunctionE opt f@(PGFunction schema name args ret) = do
names <- sequence $ replicate (length args) (newName "x")
connName <- newName "conn"
argsName <- newName "args"
let funcName = mkName $ mkFunctionName opt f
let funcArgs = (VarP connName):(map VarP names)
let funcBody = NormalB $ unwrapE ret $ foldl1 AppE [
(VarE 'query)
, (VarE connName)
, (mkSqlQuery f argsName)
, (VarE argsName)]
let wrapArg (PGArgument n _ d) argName = foldl1 AppE $ case d of
False -> [(ConE 'MandatoryArg), (LitE (StringL n)), (VarE argName)]
True -> [(ConE 'OptionalArg), (LitE (StringL n)), (VarE argName)]
--True -> [(ConE 'OptionalArg), (LitE (StringL n)), ((ConE 'Just) `AppE` (VarE argName))]
let argsBody = NormalB $ (VarE 'filterArguments) `AppE` (ListE $ zipWith wrapArg args names)
return $ FunD funcName [Clause funcArgs funcBody [ValD (VarP argsName) argsBody []]]
-- sqlGetVal :: forall x_acMJ y_acMK.
-- (PostgresType "bigint" ~ x_acMJ, ToField x_acMJ,
-- PostgresType "bigint" ~ y_acMK, FromField y_acMK)
-- => Connection -> Maybe x_acMJ -> IO y_acMK
--
-- sqlGetVal conn_acMM x_acML =
-- fmap unwrapRow
-- (query
-- conn_acMM
-- (Query (pack ((++) "select get_val(" ((++) (formatArguments args_acMN) ")")))) args_acMN)
-- where
-- args_acMN = filterArguments [OptionalArg "p_number" (Just x_acML)]