module Database.PostgreSQL.Query.Entity.Functions
  ( -- * Work with entities
    pgInsertEntity
  , pgInsertManyEntities
  , pgInsertManyEntitiesId
  , pgSelectEntities
  , pgSelectJustEntities
  , pgSelectEntitiesBy
  , pgGetEntity
  , pgGetEntityBy
  , pgQueryEntities
  , pgDeleteEntity
  , pgUpdateEntity
  , pgSelectCount
  ) where

import Data.Int ( Int64 )
import Database.PostgreSQL.Query.Entity.Class
import Database.PostgreSQL.Query.Entity.Internal
import Database.PostgreSQL.Query.Functions
import Database.PostgreSQL.Query.Import
import Database.PostgreSQL.Query.SqlBuilder
import Database.PostgreSQL.Query.TH
    ( sqlExp )
import Database.PostgreSQL.Query.Types
import Database.PostgreSQL.Simple
import Database.PostgreSQL.Simple.FromField
import Database.PostgreSQL.Simple.ToField

import qualified Control.Monad.Fail as F
import qualified Data.List as L
import qualified Data.List.NonEmpty as NL


-- | Insert new entity and return it's id
pgInsertEntity
  :: forall a m
   . ( MonadPostgres m, MonadLogger m, Entity a
     , ToRow a, FromField (EntityId a), F.MonadFail m )
  => a
  -> m (EntityId a)
pgInsertEntity :: a -> m (EntityId a)
pgInsertEntity a
a = do
    SqlBuilder -> m [Only (EntityId a)]
forall (m :: * -> *) q r.
(MonadPostgres m, ToSqlBuilder q, FromRow r, HasCallStack) =>
q -> m [r]
pgQuery [sqlExp|^{insertEntity a} RETURNING id|] m [Only (EntityId a)]
-> ([Only (EntityId a)] -> m (EntityId a)) -> m (EntityId a)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
        ((Only EntityId a
ret):[Only (EntityId a)]
_) -> EntityId a -> m (EntityId a)
forall (m :: * -> *) a. Monad m => a -> m a
return EntityId a
ret
        [Only (EntityId a)]
_       -> String -> m (EntityId a)
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"Query did not return any response"


{- | Select entities as pairs of (id, entity).

@
handler :: Handler [Ent a]
handler = do
    now <- liftIO getCurrentTime
    let back = addUTCTime (days  (-7)) now
    pgSelectEntities id
        [sqlExp|WHERE created BETWEEN \#{now} AND \#{back}
               ORDER BY created|]

handler2 :: Text -> Handler [Ent Foo]
handler2 fvalue = do
    pgSelectEntities ("t"<>)
        [sqlExp|AS t INNER JOIN table2 AS t2
                ON t.t2_id = t2.id
                WHERE t.field = \#{fvalue}
                ORDER BY t2.field2|]
   -- Here the query will be: SELECT ... FROM tbl AS t INNER JOIN ...
@

-}

pgSelectEntities
  :: forall m a q
   . ( Functor m, MonadPostgres m, MonadLogger m, Entity a
     , FromRow a, ToSqlBuilder q, FromField (EntityId a) )
  => (FN -> FN)
   -- ^ Entity fields name modifier, e.g. ("tablename"<>). Each field of entity
   -- will be processed by this modifier before pasting to the query
  -> q
   -- ^ part of query just after __SELECT .. FROM table__.
  -> m [Ent a]
pgSelectEntities :: (FN -> FN) -> q -> m [Ent a]
pgSelectEntities FN -> FN
fpref q
q = do
    let p :: Proxy a
p = Proxy a
forall k (t :: k). Proxy t
Proxy :: Proxy a
    SqlBuilder -> m [Ent a]
forall q (m :: * -> *) a.
(ToSqlBuilder q, MonadPostgres m, MonadLogger m, Entity a,
 FromRow a, FromField (EntityId a)) =>
q -> m [Ent a]
pgQueryEntities [sqlExp|^{selectEntity (entityFieldsId fpref) p} ^{q}|]


-- | Same as 'pgSelectEntities' but do not select id
pgSelectJustEntities
  :: forall m a q
   . ( Functor m, MonadPostgres m, MonadLogger m, Entity a
     , FromRow a, ToSqlBuilder q )
  => (FN -> FN)
  -> q
  -> m [a]
pgSelectJustEntities :: (FN -> FN) -> q -> m [a]
pgSelectJustEntities FN -> FN
fpref q
q = do
    let p :: Proxy a
p = Proxy a
forall k (t :: k). Proxy t
Proxy :: Proxy a
    SqlBuilder -> m [a]
forall (m :: * -> *) q r.
(MonadPostgres m, ToSqlBuilder q, FromRow r, HasCallStack) =>
q -> m [r]
pgQuery [sqlExp|^{selectEntity (entityFields id fpref) p} ^{q}|]

{- | Select entities by condition formed from 'MarkedRow'. Usefull function when
you know

-}

pgSelectEntitiesBy
  :: forall a m b
   . ( Functor m, MonadPostgres m, MonadLogger m, Entity a, ToMarkedRow b
     , FromRow a, FromField (EntityId a) )
  => b
  -> m [Ent a]
pgSelectEntitiesBy :: b -> m [Ent a]
pgSelectEntitiesBy b
b =
    let p :: Proxy a
p = Proxy a
forall k (t :: k). Proxy t
Proxy :: Proxy a
    in SqlBuilder -> m [Ent a]
forall q (m :: * -> *) a.
(ToSqlBuilder q, MonadPostgres m, MonadLogger m, Entity a,
 FromRow a, FromField (EntityId a)) =>
q -> m [Ent a]
pgQueryEntities (SqlBuilder -> m [Ent a]) -> SqlBuilder -> m [Ent a]
forall a b. (a -> b) -> a -> b
$ ([FN] -> [FN]) -> Proxy a -> b -> SqlBuilder
forall a b.
(Entity a, ToMarkedRow b) =>
([FN] -> [FN]) -> Proxy a -> b -> SqlBuilder
selectEntitiesBy (FN
"id"FN -> [FN] -> [FN]
forall a. a -> [a] -> [a]
:) Proxy a
p b
b


-- | Select entity by id
--
-- @
-- getUser :: EntityId User ->  Handler User
-- getUser uid = do
--     pgGetEntity uid
--         >>= maybe notFound return
-- @
pgGetEntity
  :: forall m a
   . ( ToField (EntityId a), Entity a, FromRow a
     , MonadPostgres m, MonadLogger m, Functor m)
  => EntityId a
  -> m (Maybe a)
pgGetEntity :: EntityId a -> m (Maybe a)
pgGetEntity EntityId a
eid = do
    [a] -> Maybe a
forall a. [a] -> Maybe a
listToMaybe ([a] -> Maybe a) -> m [a] -> m (Maybe a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (FN -> FN) -> SqlBuilder -> m [a]
forall (m :: * -> *) a q.
(Functor m, MonadPostgres m, MonadLogger m, Entity a, FromRow a,
 ToSqlBuilder q) =>
(FN -> FN) -> q -> m [a]
pgSelectJustEntities FN -> FN
forall a. a -> a
id [sqlExp|WHERE id = #{eid} LIMIT 1|]


{- | Get entity by some fields constraint

@
getUser :: UserName -> Handler User
getUser name = do
    pgGetEntityBy
        (MR [("name", mkValue name),
             ("active", mkValue True)])
        >>= maybe notFound return
@

The query here will be like

@
pgQuery [sqlExp|SELECT id, name, phone ... FROM users WHERE name = #{name} AND active = #{True}|]
@

-}

pgGetEntityBy
  :: forall m a b
   . ( Entity a, MonadPostgres m, MonadLogger m, ToMarkedRow b
     , FromField (EntityId a), FromRow a, Functor m )
  => b               -- ^ uniq constrained list of fields and values
  -> m (Maybe (Ent a))
pgGetEntityBy :: b -> m (Maybe (Ent a))
pgGetEntityBy b
b =
    let p :: Proxy a
p = Proxy a
forall k (t :: k). Proxy t
Proxy :: Proxy a
    in ([Ent a] -> Maybe (Ent a)) -> m [Ent a] -> m (Maybe (Ent a))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [Ent a] -> Maybe (Ent a)
forall a. [a] -> Maybe a
listToMaybe
       (m [Ent a] -> m (Maybe (Ent a))) -> m [Ent a] -> m (Maybe (Ent a))
forall a b. (a -> b) -> a -> b
$ SqlBuilder -> m [Ent a]
forall q (m :: * -> *) a.
(ToSqlBuilder q, MonadPostgres m, MonadLogger m, Entity a,
 FromRow a, FromField (EntityId a)) =>
q -> m [Ent a]
pgQueryEntities
       [sqlExp|^{selectEntitiesBy ("id":) p b} LIMIT 1|]


-- | Same as 'pgInsertEntity' but insert many entities at one
-- action. Returns list of id's of inserted entities
pgInsertManyEntitiesId
  :: forall a m
   . ( Entity a, MonadPostgres m, MonadLogger m
     , ToRow a, FromField (EntityId a))
  => [a]
  -> m [EntityId a]
pgInsertManyEntitiesId :: [a] -> m [EntityId a]
pgInsertManyEntitiesId [] = [EntityId a] -> m [EntityId a]
forall (m :: * -> *) a. Monad m => a -> m a
return []
pgInsertManyEntitiesId [a]
ents' =
    let ents :: NonEmpty a
ents = [a] -> NonEmpty a
forall a. [a] -> NonEmpty a
NL.fromList [a]
ents'
        q :: SqlBuilder
q = [sqlExp|^{insertManyEntities ents} RETURNING id|]
    in (Only (EntityId a) -> EntityId a)
-> [Only (EntityId a)] -> [EntityId a]
forall a b. (a -> b) -> [a] -> [b]
map Only (EntityId a) -> EntityId a
forall a. Only a -> a
fromOnly ([Only (EntityId a)] -> [EntityId a])
-> m [Only (EntityId a)] -> m [EntityId a]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> SqlBuilder -> m [Only (EntityId a)]
forall (m :: * -> *) q r.
(MonadPostgres m, ToSqlBuilder q, FromRow r, HasCallStack) =>
q -> m [r]
pgQuery SqlBuilder
q

-- | Insert many entities without returning list of id like
-- 'pgInsertManyEntitiesId' does
pgInsertManyEntities
  :: forall a m
   . (Entity a, MonadPostgres m, MonadLogger m, ToRow a)
  => [a]
  -> m Int64
pgInsertManyEntities :: [a] -> m Int64
pgInsertManyEntities [] = Int64 -> m Int64
forall (m :: * -> *) a. Monad m => a -> m a
return Int64
0
pgInsertManyEntities [a]
ents' =
    let ents :: NonEmpty a
ents = [a] -> NonEmpty a
forall a. [a] -> NonEmpty a
NL.fromList [a]
ents'
    in SqlBuilder -> m Int64
forall (m :: * -> *) q.
(MonadPostgres m, ToSqlBuilder q, HasCallStack) =>
q -> m Int64
pgExecute (SqlBuilder -> m Int64) -> SqlBuilder -> m Int64
forall a b. (a -> b) -> a -> b
$ NonEmpty a -> SqlBuilder
forall a. (Entity a, ToRow a) => NonEmpty a -> SqlBuilder
insertManyEntities NonEmpty a
ents


{- | Delete entity.

@
rmUser :: EntityId User -> Handler ()
rmUser uid = do
    pgDeleteEntity uid
@

Return 'True' if row was actually deleted.
-}

pgDeleteEntity
  :: forall a m
   . (Entity a, MonadPostgres m, MonadLogger m, ToField (EntityId a), Functor m)
  => EntityId a
  -> m Bool
pgDeleteEntity :: EntityId a -> m Bool
pgDeleteEntity EntityId a
eid =
    let p :: Proxy a
p = Proxy a
forall k (t :: k). Proxy t
Proxy :: Proxy a
    in (Int64 -> Bool) -> m Int64 -> m Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Int64
1 Int64 -> Int64 -> Bool
forall a. Eq a => a -> a -> Bool
==)
       (m Int64 -> m Bool) -> m Int64 -> m Bool
forall a b. (a -> b) -> a -> b
$ SqlBuilder -> m Int64
forall (m :: * -> *) q.
(MonadPostgres m, ToSqlBuilder q, HasCallStack) =>
q -> m Int64
pgExecute [sqlExp|DELETE FROM ^{tableName p}
                           WHERE id = #{eid}|]


{- | Update entity using 'ToMarkedRow' instanced value. Requires 'Proxy'
while 'EntityId' is not a data type.

@
fixUser :: Text -> EntityId User -> Handler ()
fixUser username uid = do
    pgGetEntity uid
        >>= maybe notFound run
  where
    run user =
        pgUpdateEntity uid
        $ MR [("active", mkValue True)
              ("name", mkValue username)]
@

Returns 'True' if record was actually updated and 'False' if there was
not row with such id (or was more than 1, in fact)
-}

pgUpdateEntity
  :: forall a b m
   . ( ToMarkedRow b, Entity a, MonadPostgres m, MonadLogger m
     , ToField (EntityId a), Functor m, Typeable a, Typeable b)
  => EntityId a
  -> b
  -> m Bool
pgUpdateEntity :: EntityId a -> b -> m Bool
pgUpdateEntity EntityId a
eid b
b =
    let p :: Proxy a
p = Proxy a
forall k (t :: k). Proxy t
Proxy :: Proxy a
        mr :: MarkedRow
mr = b -> MarkedRow
forall a. ToMarkedRow a => a -> MarkedRow
toMarkedRow b
b
    in if [(FN, SqlBuilder)] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
L.null ([(FN, SqlBuilder)] -> Bool) -> [(FN, SqlBuilder)] -> Bool
forall a b. (a -> b) -> a -> b
$ MarkedRow -> [(FN, SqlBuilder)]
unMR MarkedRow
mr
       then Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
       else (Int64 -> Bool) -> m Int64 -> m Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Int64
1 Int64 -> Int64 -> Bool
forall a. Eq a => a -> a -> Bool
==)
            (m Int64 -> m Bool) -> m Int64 -> m Bool
forall a b. (a -> b) -> a -> b
$ SqlBuilder -> m Int64
forall (m :: * -> *) q.
(MonadPostgres m, ToSqlBuilder q, HasCallStack) =>
q -> m Int64
pgExecute [sqlExp|UPDATE ^{tableName p}
                                SET ^{mrToBuilder ", " mr}
                                WHERE id = #{eid}|]

{- | Select count of entities with given query

@
activeUsers :: Handler Integer
activeUsers = do
    pgSelectCount (Proxy :: Proxy User)
        [sqlExp|WHERE active = #{True}|]
@

-}


-- | Executes arbitrary query and parses it as entities and their ids
pgQueryEntities
  :: ( ToSqlBuilder q, MonadPostgres m, MonadLogger m, Entity a
     , FromRow a, FromField (EntityId a))
  => q
  -> m [Ent a]
pgQueryEntities :: q -> m [Ent a]
pgQueryEntities q
q =
    ((Only (EntityId a) :. a) -> Ent a)
-> [Only (EntityId a) :. a] -> [Ent a]
forall a b. (a -> b) -> [a] -> [b]
map (Only (EntityId a) :. a) -> Ent a
forall a b. (Only a :. b) -> (a, b)
toTuples ([Only (EntityId a) :. a] -> [Ent a])
-> m [Only (EntityId a) :. a] -> m [Ent a]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> q -> m [Only (EntityId a) :. a]
forall (m :: * -> *) q r.
(MonadPostgres m, ToSqlBuilder q, FromRow r, HasCallStack) =>
q -> m [r]
pgQuery q
q
  where
    toTuples :: (Only a :. b) -> (a, b)
toTuples ((Only a
eid) :. b
entity) = (a
eid, b
entity)

pgSelectCount
  :: forall m a q
   . ( Entity a, MonadPostgres m, MonadLogger m, ToSqlBuilder q )
  => Proxy a
  -> q
  -> m Integer
pgSelectCount :: Proxy a -> q -> m Integer
pgSelectCount Proxy a
p q
q = do
    [[Integer]]
r <- SqlBuilder -> m [[Integer]]
forall (m :: * -> *) q r.
(MonadPostgres m, ToSqlBuilder q, FromRow r, HasCallStack) =>
q -> m [r]
pgQuery [sqlExp|SELECT count(id) FROM ^{tableName p} ^{q}|]
    case [[Integer]]
r of
      [[Integer
c]] -> Integer -> m Integer
forall (m :: * -> *) a. Monad m => a -> m a
return Integer
c
      [[Integer]]
_ -> String -> m Integer
forall a. HasCallStack => String -> a
error String
"this should not happen"