typedquery-0.1.0.0: src/Database/TypedQuery/Types.hs
{-# Language DeriveDataTypeable
, RankNTypes
, CPP
, TemplateHaskell
#-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Database.TypedQuery.Types
( TypedQuery (..)
, TypeAction (..)
, RunDB (..)
, jsonPair
, convAction
, typeTuple
, typeTupleIn
, vanilaQuery
, genUncurry
, genJsonQuery
, genTypedQuery
)
where
import Control.Arrow (first)
import Data.Monoid (Monoid(..))
import Data.String (IsString(..))
import Data.Typeable (Typeable)
import Data.Aeson (object)
import Data.Aeson.Types (ToJSON(..),Value)
import Database.TypedQuery.SQLParser
import Data.Text (Text,unpack)
import Prelude
import Control.Monad
import Control.Applicative
import Data.Either
import Data.Maybe
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Syntax as TH
import Language.Haskell.Meta.Parse (parseType, parseExp)
#if DEVELOPMENT
import Language.Haskell.Exts (parseStmtWithMode, prettyPrint, ParseResult(..), defaultParseMode, Extension(..),KnownExtension(..), ParseMode(..))
import Control.Monad.IO.Class (liftIO)
import System.IO.Unsafe (unsafePerformIO)
import Data.String.QM
codeFormat = check . fmap reformat . parseStmtWithMode defaultParseMode {extensions=[EnableExtension BangPatterns, EnableExtension HereDocuments,EnableExtension OverloadedStrings]}
where
reformat = prettyPrint
check r = case r of
ParseOk a -> a
ParseFailed loc err -> error $ show (loc,err)
#endif
class RunDB q where
rdquery :: q -> TH.Name
rdquery_ :: q -> TH.Name
rdexecute :: q -> TH.Name
rdexecute_ :: q -> TH.Name
rdin :: q -> TH.Name
rdonly :: q -> TH.Name
rdconn :: q -> TH.Name
data RunDB q => TypedQuery q
-- ^ `TypedQuery` is a represent SQL query + all fields we could parse from it
-- the idea is to use the original SQL and use comments `--` to annotate it with some extra information
= TypedQuery
{ fromTypedQuery :: q -- SQL text of the query
, namesTypedQuery :: [Text] -- names from fields
, typesTypedQuery :: [TypeAction] -- types of fields
, typesTypedInput :: [TypeAction] -- types of "?" inputs
, typesTypedInputSource :: [TypeAction] -- functions feeding data
}
deriving (Eq, Ord, Typeable)
flat :: RunDB q => TypedQuery q -> (q, [Text], [TypeAction], [TypeAction], [TypeAction])
flat (TypedQuery a b c d e) = (a, b, c, d, e)
instance (RunDB q, TH.Lift q) => TH.Lift (TypedQuery q) where
lift = TH.lift . flat
instance TH.Lift Text where
lift = TH.lift . unpack
instance (Show q,RunDB q) => Show (TypedQuery q) where
show = show . fromTypedQuery
instance (RunDB q, IsString q, Monoid q) => Read (TypedQuery q) where
readsPrec i = fmap (first conv) . readsPrec i
where
conv x = case typedSQLtoSQL x of
Nothing -> mempty
Just (a, b, c, d, e) -> TypedQuery (fromString a) b c d e
instance (RunDB q, IsString q, Monoid q) => IsString (TypedQuery q) where
fromString x =
case typedSQLtoSQL x of
Nothing -> mempty
Just (a, b, c, d, e) -> TypedQuery (fromString a) b c d e
instance (RunDB q, Monoid q) => Monoid (TypedQuery q) where
mempty = TypedQuery mempty mempty mempty mempty mempty
TypedQuery a1 a2 a3 a4 a5 `mappend` TypedQuery b1 b2 b3 b4 b5
= TypedQuery
(mappend a1 b1)
(mappend a2 b2)
(mappend a3 b3)
(mappend a4 b4)
(mappend a5 b5)
{-# INLINE mappend #-}
convAction :: RunDB q => q -> TypeAction -> TH.Exp -> TH.Exp
convAction _ (TAUnknown) = id
convAction _ (TAConv s) = case parseExp s of
Right e -> TH.AppE e
Left er -> error $ "Could not find `convExp` type: " ++ er
convAction _ (TACast s) = case parseType s of
Right t1 -> (`TH.SigE` t1)
Left er -> error $ "Could not find `typeExp` type: " ++ er
convAction q (TAInUnknown) = TH.AppE (TH.ConE (rdonly q))
convAction q (TAInConv s) = case parseExp s of
Right ex -> TH.AppE (TH.ConE (rdin q)) . TH.AppE ex
Left er -> error $ "Could not find `convExp` type: " ++ er
convAction q (TAInCast s) = case parseType s of
Right t1 -> \ e -> TH.ConE (rdin q) `TH.AppE` (e `TH.SigE` t1)
Left er -> error $ "Could not find `typeExp` type: " ++ er
jsonPair :: RunDB q => TypedQuery q -> TH.Q TH.Exp
jsonPair (TypedQuery q li lq _ _) = do
let k = length li
nam <- mapM TH.lift li
nsa <- replicateM k (TH.newName "a")
let typ = zipWith (\a b -> convAction q a (TH.VarE b)) lq nsa
case k of
1 -> do
let n1 = head nam
let t1 = head typ
return $ TH.LamE
[TH.BangP $ TH.ConP (rdonly q) [ TH.VarP $ head nsa] ]
( TH.AppE (TH.VarE 'object) (TH.ListE [TH.TupE [n1, TH.AppE (TH.VarE 'toJSON) t1]]))
_ -> return $ TH.LamE
[TH.TupP $ map (TH.BangP . TH.VarP) nsa]
( TH.AppE (TH.VarE 'object) (TH.ListE $ zipWith (\a b-> TH.TupE [ a, TH.AppE (TH.VarE 'toJSON) b `TH.SigE` TH.ConT ''Value]) nam typ ) )
typeTuple :: RunDB q => TypedQuery q -> TH.Q TH.Exp
typeTuple (TypedQuery q _ lq _ _) = do
let k = length lq
nsa <- replicateM k (TH.newName "a")
let typ = zipWith (\a b -> convAction q a (TH.VarE b)) lq nsa
case k of
1 -> return $ TH.LamE [TH.BangP $ TH.ConP (rdonly q) [ TH.VarP $ head nsa] ] (head typ)
_ -> return $ TH.LamE [TH.TupP $ map (TH.BangP . TH.VarP) nsa ] (TH.TupE typ)
genVal :: RunDB q => q -> TypeAction -> Either a (Maybe TH.Exp)
genVal _ (TAInsIS a) = Right $ either (error "parse failed") Just (parseExp a)
genVal _ (TAInsISU a) = Right $ either (error "parse failed") Just (parseExp a)
genVal q (TAInSupply a) = Right $ either (error "parse failed") (Just . TH.AppE (TH.ConE (rdin q)) ) (parseExp a)
genVal _ _ = Right Nothing
handleOnly :: RunDB q => q -> [TH.Exp] -> TH.Exp
handleOnly q [a] = TH.ConE (rdonly q) `TH.AppE` a
handleOnly _ a = TH.TupE a
typeTupleIn :: RunDB q => TypedQuery q -> TH.Q(Bool,Bool,TH.Exp)
typeTupleIn (TypedQuery q _ _ lq lextra) = do
nsa <- replicateM (length lq) (TH.newName "i")
let initTyp = zipWith (\a b -> if b == TAInsQM || b == TAInsQMU then Left a else genVal q b ) nsa lextra
typ = zipWith (\a c -> case c of
Left v -> Just $ convAction q a (TH.VarE v)
Right v -> convAction q a <$> v
) lq initTyp
pat1 = lefts initTyp
reduce_params b = if b then id else TH.LamE [TH.TupP $ map (TH.BangP . TH.VarP) pat1]
return (null pat1, null typ , reduce_params (null pat1) (handleOnly q $ catMaybes typ))
genUncurry :: RunDB q => q -> Int -> TH.Q TH.Exp
genUncurry _ 0 = do
f <- TH.newName "f"
return (TH.VarE f)
genUncurry q 1 = do
f <- TH.newName "f"
x <- TH.newName "x"
return $
TH.LamE [TH.VarP f, (rdonly q) `TH.ConP` [TH.VarP x]] $ TH.VarE f `TH.AppE` TH.VarE x
genUncurry _ n
| n < 0 = fail "There are no negative count tuples"
| otherwise = do
f <- TH.newName "f"
xs <- replicateM n (TH.newName "x")
return $
TH.LamE [TH.VarP f, TH.TupP $ map (TH.BangP . TH.VarP) xs] $
foldl1 TH.AppE (map TH.VarE (f : xs))
vanilaQuery :: (RunDB q, TH.Lift q) => (TypedQuery q -> TH.Q TH.Exp) -> TypedQuery q -> TH.Q TH.Exp
-- ^ general query which can be parametrized depending on the needs
-- it takes a transformation function and some typed Query
vanilaQuery tran tq@(TypedQuery q _ lq _ _) = do
conn <- TH.newName "conn"
qp <- TH.newName "qp"
tmp1 <- TH.newName "res1"
q1 <- [| q |]
pa <- tran tq
(null_out, null_int, initial) <- typeTupleIn tq
let final_type_cast b = if b then id else TH.AppE (TH.AppE (TH.VarE 'fmap) (TH.VarE 'map `TH.AppE` pa))
type_connection x = TH.VarE x `TH.AppE` (TH.VarE conn `TH.SigE` TH.ConT (rdconn q)) `TH.AppE` q1
has_no_output = null lq
(qqqq, pat) =
case (has_no_output, null_int , null_out) of
(True , True , _ ) -> ( type_connection (rdexecute_ q), [])
(True , False, False) -> ( type_connection (rdexecute q) `TH.AppE` TH.AppE initial (TH.VarE qp) , [TH.VarP qp] )
(True , False, True ) -> ( type_connection (rdexecute q) `TH.AppE` initial , [] )
(False , True , _ ) -> ( type_connection (rdquery_ q), [])
(False , False, False) -> ( type_connection (rdquery q) `TH.AppE` TH.AppE initial (TH.VarE qp) , [TH.VarP qp] )
(False , False, True ) -> ( type_connection (rdquery q) `TH.AppE` initial , [] )
exp = TH.LamE (TH.VarP conn : pat) $ final_type_cast has_no_output qqqq
#if DEVELOPMENT
unsafePerformIO $ do
putStrLn $ codeFormat $ TH.pprint exp
liftIO $ return $ return $ exp
#else
return exp
#endif
genJsonQuery :: (RunDB q, TH.Lift q) => TypedQuery q -> TH.Q TH.Exp
genJsonQuery = vanilaQuery jsonPair
genTypedQuery :: (RunDB q, TH.Lift q) => TypedQuery q -> TH.Q TH.Exp
genTypedQuery = vanilaQuery typeTuple
{--
class SimpleQuery q where
query :: (ToRow q, FromRow r) => Connection -> Query -> q -> IO [r]
query_ :: FromRow r => Connection -> Query -> IO [r]
-- queryNamed :: FromRow r => Connection -> Query -> [NamedParam] -> IO [r]queryNamed :: FromRow r => Connection -> Query -> [NamedParam] -> IO [r]
execute :: ToRow q => Connection -> Query -> q -> IO ()
execute_ :: Connection -> Query -> IO ()
--}