{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-| Module : Servant.DB.PostgreSQL.HasDB Description : Deriving DB client from API Portability : Not portable -} module Servant.DB.PostgreSQL.HasDB( deriveDB , HasDB(..) , module Reexport ) where import Control.Applicative import Control.Monad (replicateM_) import Data.Monoid import Data.Proxy import Data.Text (pack) import Database.PostgreSQL.Query import Database.PostgreSQL.Simple.FromRow import Database.PostgreSQL.Simple.Types (Null) import GHC.TypeLits import Servant.API import Servant.API.DB import Servant.DB.PostgreSQL.Context import Servant.DB.PostgreSQL.Default import Servant.DB.PostgreSQL.Variadic import Database.PostgreSQL.Simple as Reexport (Only (..)) -- | Derive DB client from API deriveDB :: HasDB layout m => Proxy layout -- ^ API layout -> Proxy m -- ^ PostgreSQL monad we operate in -> DB layout m -- ^ Derived functions deriveDB layout m = deriveDBWithCtx layout m newQueryContext -- | Derive DB client from API class HasDB layout (m :: * -> *) where -- | Associated type of deriving result type DB layout m :: * -- | Derive DB client from API layout deriveDBWithCtx :: Proxy layout -> Proxy m -> QueryContext ToField -> DB layout m -- | Deriving several procedures to query DB API -- -- > type API = Procedure "time" Integer -- > :<|> ArgNamed "a" Int :> Procedure "square" (Only Int) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > time :: MyMonad (Only Integer) -- > square :: Int -> MyMonad (Only Int) -- > (time, square) = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive separate endpoints with the following SQL calls: -- -- >>> SELECT time(); -- -- >>> SELECT square("a" => ?); instance (HasDB api1 m, HasDB api2 m) => HasDB (api1 :<|> api2) m where type DB (api1 :<|> api2) m = DB api1 m :<|> DB api2 m deriveDBWithCtx _ m ctx = deriveDBWithCtx (Proxy :: Proxy api1) m ctx :<|> deriveDBWithCtx (Proxy :: Proxy api2) m ctx {-# INLINE deriveDBWithCtx #-} -- | Deriving several procedures to query DB API -- -- > type API = "public" :> Procedure "time" (Only Integer) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > time :: MyMonad (Only Integer) -- > time = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- The `time` function will call DB with the: -- -- >>> SELECT public.time(); -- -- Note that there could be only one schema marker. If there are more than one -- the later (righter) will override previous ones. instance (KnownSymbol n, HasDB api m) => HasDB (n :> api) m where type DB (n :> api) m = DB api m deriveDBWithCtx _ m ctx = deriveDBWithCtx (Proxy :: Proxy api) m ctx' where n = pack $ symbolVal (Proxy :: Proxy n) ctx' = ctx { querySchema = Just n } {-# INLINE deriveDBWithCtx #-} -- | Deriving call to DB procedure with named arguments -- -- > type API = ArgNamed "a" Int :> ArgNamed "b" Int :> Procedure "sum" (Only Int) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > dbSum :: Int -> Int -> MyMonad (Only Int) -- > dbSum = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT * FROM sum("a" => ?, "b" => ?) AS t; instance {-# OVERLAPPABLE #-} (KnownSymbol n, ToField a, HasDB api m) => HasDB (ArgNamed n a :> api) m where type DB (ArgNamed n a :> api) m = a -> DB api m deriveDBWithCtx _ m ctx a = deriveDBWithCtx (Proxy :: Proxy api) m ctx' where n = pack $ symbolVal (Proxy :: Proxy n) ctx' = addQueryArgument (Just n) (ArgSimple a) ctx {-# INLINE deriveDBWithCtx #-} -- | Deriving call to DB procedure with named variadic arguments -- -- > type API = ArgNamed "arr" (Variadic Int) :> Procedure "mleast" (Only Int) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > dbMleast :: Variadic Int -> MyMonad (Only Int) -- > dbMleast = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT * FROM mleast(VARIADIC "arr" => ?) AS t; instance {-# OVERLAPPING #-} (KnownSymbol n, ToField a, HasDB api m) => HasDB (ArgNamed n (Variadic a) :> api) m where type DB (ArgNamed n (Variadic a) :> api) m = Variadic a -> DB api m deriveDBWithCtx _ m ctx a = deriveDBWithCtx (Proxy :: Proxy api) m ctx' where n = pack $ symbolVal (Proxy :: Proxy n) ctx' = addQueryArgument (Just n) (ArgVariadic a) ctx {-# INLINE deriveDBWithCtx #-} -- | Deriving call to DB procedure with named default arguments -- -- > type API = ArgNamed "a" (Defaultable Int) :> Procedure "foo" (Only Int) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > dbFoo :: Defaultable Int -> MyMonad (Only Int) -- > dbFoo = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT * FROM default(DEFAULT) AS t; instance {-# OVERLAPPING #-} (KnownSymbol n, ToField a, HasDB api m) => HasDB (ArgNamed n (Default a) :> api) m where type DB (ArgNamed n (Default a) :> api) m = Default a -> DB api m deriveDBWithCtx _ m ctx a = deriveDBWithCtx (Proxy :: Proxy api) m ctx' where n = pack $ symbolVal (Proxy :: Proxy n) ctx' = addQueryArgument (Just n) (ArgDefault $ unDefault a) ctx {-# INLINE deriveDBWithCtx #-} -- | Deriving call to DB procedure with positional arguments -- -- > type API = Arg Int :> Arg Int :> Procedure "sum" (Only Int) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > dbSum :: Int -> Int -> MyMonad (Only Int) -- > dbSum = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT * FROM sum(?, ?) AS t; instance {-# OVERLAPPABLE #-} (ToField a, HasDB api m) => HasDB (ArgPos a :> api) m where type DB (ArgPos a :> api) m = a -> DB api m deriveDBWithCtx _ m ctx a = deriveDBWithCtx (Proxy :: Proxy api) m ctx' where ctx' = addQueryArgument Nothing (ArgSimple a) ctx {-# INLINE deriveDBWithCtx #-} -- | Deriving call to DB procedure with positional variadic arguments -- -- > type API = ArgPos (Variadic Int) :> Procedure "mleast" (Only Int) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > dbMleast :: Variadic Int -> MyMonad (Only Int) -- > dbMleast = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT * FROM mleast(VARIADIC ?) AS t; instance {-# OVERLAPPING #-} (ToField a, HasDB api m) => HasDB (ArgPos (Variadic a) :> api) m where type DB (ArgPos (Variadic a) :> api) m = Variadic a -> DB api m deriveDBWithCtx _ m ctx a = deriveDBWithCtx (Proxy :: Proxy api) m ctx' where ctx' = addQueryArgument Nothing (ArgVariadic a) ctx {-# INLINE deriveDBWithCtx #-} -- | Deriving call to DB procedure with positional default arguments -- -- > type API = ArgPos (Default Int) :> Procedure "foo" (Only Int) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > dbFoo :: Default Int -> MyMonad (Only Int) -- > dbFoo = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT * FROM foo(DEFAULT) AS t; instance {-# OVERLAPPING #-} (ToField a, HasDB api m) => HasDB (ArgPos (Default a) :> api) m where type DB (ArgPos (Default a) :> api) m = Default a -> DB api m deriveDBWithCtx _ m ctx a = deriveDBWithCtx (Proxy :: Proxy api) m ctx' where ctx' = addQueryArgument Nothing (ArgDefault $ unDefault a) ctx {-# INLINE deriveDBWithCtx #-} -- | Deriving call to DB procedure with no return type -- -- > data User -- user data -- > instance ToRow User -- > -- > type API = Arg "user" User :> Procedure "registerUser" () -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > getUsers :: User -> MyMonad () -- > getUsers = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT registerUser("user" => ?); -- -- And the instance expects that `users` function return type is `SETOF user`. instance {-# OVERLAPPING #-} (KnownSymbol n, MonadPostgres m) => HasDB (Procedure n ()) m where type DB (Procedure n ()) m = m () deriveDBWithCtx _ _ ctx = do (_ :: [Only ()]) <- pgQuery q return () where n = pack $ symbolVal (Proxy :: Proxy n) q = queryStoredFunction n ctx { queryVoid = True } {-# INLINE deriveDBWithCtx #-} -- | Deriving call to DB procedure with multiple result -- -- > data User -- user data -- > instance FromRow User -- > -- > type API = Procedure "users" [User] -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > getUsers :: MyMonad [User] -- > getUsers = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT * FROM users() AS t; -- -- And the instance expects that `users` function return type is `SETOF user`. instance {-# OVERLAPPING #-} (KnownSymbol n, FromRow a, MonadPostgres m) => HasDB (Procedure n [a]) m where type DB (Procedure n [a]) m = m [a] deriveDBWithCtx _ _ ctx = pgQuery q where n = pack $ symbolVal (Proxy :: Proxy n) q = queryStoredFunction n ctx {-# INLINE deriveDBWithCtx #-} nullRow :: RowParser Null nullRow = field instance {-# OVERLAPPABLE #-} FromRow a => FromRow (Maybe a) where fromRow = do n <- numFieldsRemaining (replicateM_ n nullRow *> pure Nothing) <|> (Just <$> fromRow) -- | Deriving call to DB procedure with optional result -- -- > data User -- user data -- > instance FromRow User -- > -- > type API = ArgPos Int :> Procedure "user" (Maybe User) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > getUsers :: MyMonad (Maybe User) -- > getUsers = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT * FROM user(?) AS t; instance {-# OVERLAPPING #-} (KnownSymbol n, FromRow a, MonadPostgres m) => HasDB (Procedure n (Maybe a)) m where type DB (Procedure n (Maybe a)) m = m (Maybe a) deriveDBWithCtx _ _ ctx = do res <- pgQuery q case res of x : _ -> return x _ -> return Nothing where n = pack $ symbolVal (Proxy :: Proxy n) q = queryStoredFunction n ctx {-# INLINE deriveDBWithCtx #-} -- | Deriving call to DB procedure with single result -- -- > type API = Arg "a" Int -> Procedure "squareReturning" (Int, Int) -- > -- > data MyMonad m a -- Your application monad with connection pool and logger -- > instance HasPostgres m -- > instance MonadLogger m -- > -- > square :: Int -> MyMonad (Int, Int) -- > square = deriveDB (Proxy :: Proxy API) (Proxy :: Proxy MyMonad) -- -- Upper example will derive the following SQL call: -- -- >>> SELECT * FROM squareReturning() AS t; -- -- The instance expects that return type of SQL stored function is a single row. instance {-# OVERLAPPABLE #-} (KnownSymbol n, FromRow a, MonadPostgres m) => HasDB (Procedure n a) m where type DB (Procedure n a) m = m a deriveDBWithCtx _ _ ctx = do mv <- pgQuery q case mv of [] -> fail $ "deriveDBWithCtx: received zero results when expected" <> " exactly one. PG Function: " <> n' (v : _) -> return v where n' = symbolVal (Proxy :: Proxy n){--} n = pack n' q = queryStoredFunction n ctx {-# INLINE deriveDBWithCtx #-}