module Database.PostgreSQL.Query.Entity.Internal
  ( -- * Entity functions
    entityFields
  , entityFieldsId
  , selectEntity
  , selectEntitiesBy
  , insertEntity
  , insertManyEntities
  , entityToMR
  ) where

import Database.PostgreSQL.Query.Entity.Class
import Database.PostgreSQL.Query.Import
import Database.PostgreSQL.Query.Internal
import Database.PostgreSQL.Query.SqlBuilder
    ( SqlBuilder, ToSqlBuilder(..), mkValue )
import Database.PostgreSQL.Query.TH
    ( sqlExp )
import Database.PostgreSQL.Query.Types
    ( FN(..), MarkedRow(..),
      ToMarkedRow(..), mrToBuilder )
import Database.PostgreSQL.Simple.ToRow
    ( ToRow(..) )

import qualified Data.List.NonEmpty as NL
import qualified Data.List as L


{- | Build entity fields

>>> data Foo = Foo { fName :: Text, fSize :: Int }
>>> instance Entity Foo where {newtype EntityId Foo = FooId Int ; fieldNames _ = ["name", "size"] ; tableName _ = "foo"}
>>> runSqlBuilder con $ entityFields id id (Proxy :: Proxy Foo)
"\"name\", \"size\""

>>> runSqlBuilder con $ entityFields ("id":) id (Proxy :: Proxy Foo)
"\"id\", \"name\", \"size\""

>>> runSqlBuilder con $ entityFields (\l -> ("id":l) ++ ["created"]) id (Proxy :: Proxy Foo)
"\"id\", \"name\", \"size\", \"created\""

>>> runSqlBuilder con $ entityFields id ("f"<>) (Proxy :: Proxy Foo)
"\"f\".\"name\", \"f\".\"size\""

>>> runSqlBuilder con $ entityFields ("f.id":) ("f"<>) (Proxy :: Proxy Foo)
"\"f\".\"id\", \"f\".\"name\", \"f\".\"size\""

-}

entityFields :: (Entity a)
             => ([FN] -> [FN])    -- ^ modify list of fields. Applied second
             -> (FN -> FN)        -- ^ modify each field name,
                                -- e.g. prepend each field with
                                -- prefix, like ("t"<>). Applied first
             -> Proxy a
             -> SqlBuilder
entityFields :: ([FN] -> [FN]) -> (FN -> FN) -> Proxy a -> SqlBuilder
entityFields [FN] -> [FN]
xpref FN -> FN
fpref Proxy a
p =
    [FN] -> SqlBuilder
buildFields
    ([FN] -> SqlBuilder) -> [FN] -> SqlBuilder
forall a b. (a -> b) -> a -> b
$ [FN] -> [FN]
xpref
    ([FN] -> [FN]) -> [FN] -> [FN]
forall a b. (a -> b) -> a -> b
$ (FN -> FN) -> [FN] -> [FN]
forall a b. (a -> b) -> [a] -> [b]
map FN -> FN
fpref
    ([FN] -> [FN]) -> [FN] -> [FN]
forall a b. (a -> b) -> a -> b
$ Proxy a -> [FN]
forall a. Entity a => Proxy a -> [FN]
fieldNames Proxy a
p

{- | Same as 'entityFields' but prefixes list of names with __id__
field. This is shorthand function for often usage.

>>> data Foo = Foo { fName :: Text, fSize :: Int }
>>> instance Entity Foo where {newtype EntityId Foo = FooId Int ; fieldNames _ = ["name", "size"] ; tableName _ = "foo"}
>>> runSqlBuilder con $ entityFieldsId id (Proxy :: Proxy Foo)
"\"id\", \"name\", \"size\""

>>> runSqlBuilder con $ entityFieldsId ("f"<>) (Proxy :: Proxy Foo)
"\"f\".\"id\", \"f\".\"name\", \"f\".\"size\""

-}

entityFieldsId :: (Entity a)
               => (FN -> FN)
               -> Proxy a
               -> SqlBuilder
entityFieldsId :: (FN -> FN) -> Proxy a -> SqlBuilder
entityFieldsId FN -> FN
fpref Proxy a
p =
    let xpref :: [FN] -> [FN]
xpref = ((FN -> FN
fpref FN
"id")FN -> [FN] -> [FN]
forall a. a -> [a] -> [a]
:)
    in ([FN] -> [FN]) -> (FN -> FN) -> Proxy a -> SqlBuilder
forall a.
Entity a =>
([FN] -> [FN]) -> (FN -> FN) -> Proxy a -> SqlBuilder
entityFields [FN] -> [FN]
xpref FN -> FN
fpref Proxy a
p

{- | Generate SELECT query string for entity

>>> data Foo = Foo { fName :: Text, fSize :: Int }
>>> instance Entity Foo where {newtype EntityId Foo = FooId Int ; fieldNames _ = ["name", "size"] ; tableName _ = "foo"}
>>> runSqlBuilder con $ selectEntity (entityFieldsId id) (Proxy :: Proxy Foo)
"SELECT \"id\", \"name\", \"size\" FROM \"foo\""

>>> runSqlBuilder con $ selectEntity (entityFieldsId ("f"<>)) (Proxy :: Proxy Foo)
"SELECT \"f\".\"id\", \"f\".\"name\", \"f\".\"size\" FROM \"foo\""

>>> runSqlBuilder con $ selectEntity (entityFields id id) (Proxy :: Proxy Foo)
"SELECT \"name\", \"size\" FROM \"foo\""

-}

selectEntity :: (Entity a)
             => (Proxy a -> SqlBuilder) -- ^ build fields part from proxy
             -> Proxy a
             -> SqlBuilder
selectEntity :: (Proxy a -> SqlBuilder) -> Proxy a -> SqlBuilder
selectEntity Proxy a -> SqlBuilder
bld Proxy a
p =
    [sqlExp|SELECT ^{bld p} FROM ^{tableName p}|]


{- | Generates SELECT FROM WHERE query with most used conditions

>>> data Foo = Foo { fName :: Text, fSize :: Int }
>>> instance Entity Foo where {newtype EntityId Foo = FooId Int ; fieldNames _ = ["name", "size"] ; tableName _ = "foo"}
>>> runSqlBuilder con $ selectEntitiesBy id (Proxy :: Proxy Foo) $ MR []
"SELECT \"name\", \"size\" FROM \"foo\""

>>> runSqlBuilder con $ selectEntitiesBy id (Proxy :: Proxy Foo) $ MR [("name", mkValue "fooname")]
"SELECT \"name\", \"size\" FROM \"foo\" WHERE  \"name\" = 'fooname' "

>>> runSqlBuilder con $ selectEntitiesBy id (Proxy :: Proxy Foo) $ MR [("name", mkValue "fooname"), ("size", mkValue 10)]
"SELECT \"name\", \"size\" FROM \"foo\" WHERE  \"name\" = 'fooname' AND \"size\" = 10 "

-}

selectEntitiesBy :: (Entity a, ToMarkedRow b)
                 => ([FN] -> [FN])
                 -> Proxy a
                 -> b
                 -> SqlBuilder
selectEntitiesBy :: ([FN] -> [FN]) -> Proxy a -> b -> SqlBuilder
selectEntitiesBy [FN] -> [FN]
xpref Proxy a
p b
b =
    let mr :: MarkedRow
mr = b -> MarkedRow
forall a. ToMarkedRow a => a -> MarkedRow
toMarkedRow b
b
        cond :: SqlBuilder
cond = 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 SqlBuilder
forall a. Monoid a => a
mempty
               else [sqlExp| WHERE ^{mrToBuilder "AND" mr}|]
        q :: SqlBuilder
q = (Proxy a -> SqlBuilder) -> Proxy a -> SqlBuilder
forall a.
Entity a =>
(Proxy a -> SqlBuilder) -> Proxy a -> SqlBuilder
selectEntity (([FN] -> [FN]) -> (FN -> FN) -> Proxy a -> SqlBuilder
forall a.
Entity a =>
([FN] -> [FN]) -> (FN -> FN) -> Proxy a -> SqlBuilder
entityFields [FN] -> [FN]
xpref FN -> FN
forall a. a -> a
id) Proxy a
p
    in SqlBuilder
q SqlBuilder -> SqlBuilder -> SqlBuilder
forall a. Semigroup a => a -> a -> a
<> SqlBuilder
cond



{- | Convert entity instance to marked row to perform inserts updates
and same stuff

>>> data Foo = Foo { fName :: Text, fSize :: Int }
>>> instance Entity Foo where {newtype EntityId Foo = FooId Int ; fieldNames _ = ["name", "size"] ; tableName _ = "foo"}
>>> instance ToRow Foo where { toRow Foo{..} = [toField fName, toField fSize] }
>>> runSqlBuilder con $ mrToBuilder ", " $ entityToMR $ Foo "Enterprise" 610
" \"name\" = 'Enterprise' ,  \"size\" = 610 "

-}

entityToMR :: forall a. (Entity a, ToRow a) => a -> MarkedRow
entityToMR :: a -> MarkedRow
entityToMR a
a =
    let p :: Proxy a
p = Proxy a
forall k (t :: k). Proxy t
Proxy :: Proxy a
        names :: [FN]
names = Proxy a -> [FN]
forall a. Entity a => Proxy a -> [FN]
fieldNames Proxy a
p
        values :: [SqlBuilder]
values = (Action -> SqlBuilder) -> [Action] -> [SqlBuilder]
forall a b. (a -> b) -> [a] -> [b]
map Action -> SqlBuilder
forall a. ToField a => a -> SqlBuilder
mkValue ([Action] -> [SqlBuilder]) -> [Action] -> [SqlBuilder]
forall a b. (a -> b) -> a -> b
$ a -> [Action]
forall a. ToRow a => a -> [Action]
toRow a
a
    in [(FN, SqlBuilder)] -> MarkedRow
MR ([(FN, SqlBuilder)] -> MarkedRow)
-> [(FN, SqlBuilder)] -> MarkedRow
forall a b. (a -> b) -> a -> b
$ [FN] -> [SqlBuilder] -> [(FN, SqlBuilder)]
forall a b. [a] -> [b] -> [(a, b)]
zip [FN]
names [SqlBuilder]
values


{- | Generates __INSERT INTO__ query for any instance of 'Entity' and 'ToRow'

>>> data Foo = Foo { fName :: Text, fSize :: Int }
>>> instance Entity Foo where {newtype EntityId Foo = FooId Int ; fieldNames _ = ["name", "size"] ; tableName _ = "foo"}
>>> instance ToRow Foo where { toRow Foo{..} = [toField fName, toField fSize] }
>>> runSqlBuilder con $ insertEntity $ Foo "Enterprise" 910
"INSERT INTO \"foo\" (\"name\", \"size\") VALUES ('Enterprise', 910)"

-}

insertEntity :: forall a. (Entity a, ToRow a) => a -> SqlBuilder
insertEntity :: a -> SqlBuilder
insertEntity a
a =
    let p :: Proxy a
p = Proxy a
forall k (t :: k). Proxy t
Proxy :: Proxy a
        mr :: MarkedRow
mr = a -> MarkedRow
forall a. (Entity a, ToRow a) => a -> MarkedRow
entityToMR a
a
    in FN -> MarkedRow -> SqlBuilder
forall b. ToMarkedRow b => FN -> b -> SqlBuilder
insertInto (Proxy a -> FN
forall a. Entity a => Proxy a -> FN
tableName Proxy a
p) MarkedRow
mr

{- | Same as 'insertEntity' but generates query to insert many queries at same time

>>> data Foo = Foo { fName :: Text, fSize :: Int }
>>> instance Entity Foo where {newtype EntityId Foo = FooId Int ; fieldNames _ = ["name", "size"] ; tableName _ = "foo"}
>>> instance ToRow Foo where { toRow Foo{..} = [toField fName, toField fSize] }
>>> runSqlBuilder con $ insertManyEntities $ NL.fromList [Foo "meter" 1, Foo "table" 2, Foo "earth" 151930000000]
"INSERT INTO \"foo\" (\"name\",\"size\") VALUES ('meter',1),('table',2),('earth',151930000000)"

-}

insertManyEntities :: forall a. (Entity a, ToRow a)
                   => NonEmpty a
                   -> SqlBuilder
insertManyEntities :: NonEmpty a -> SqlBuilder
insertManyEntities NonEmpty a
rows =
    let p :: Proxy a
p = Proxy a
forall k (t :: k). Proxy t
Proxy :: Proxy a
        names :: SqlBuilder
names = [SqlBuilder] -> SqlBuilder
forall a. Monoid a => [a] -> a
mconcat
                ([SqlBuilder] -> SqlBuilder) -> [SqlBuilder] -> SqlBuilder
forall a b. (a -> b) -> a -> b
$ SqlBuilder -> [SqlBuilder] -> [SqlBuilder]
forall a. a -> [a] -> [a]
L.intersperse SqlBuilder
","
                ([SqlBuilder] -> [SqlBuilder]) -> [SqlBuilder] -> [SqlBuilder]
forall a b. (a -> b) -> a -> b
$ (FN -> SqlBuilder) -> [FN] -> [SqlBuilder]
forall a b. (a -> b) -> [a] -> [b]
map FN -> SqlBuilder
forall a. ToSqlBuilder a => a -> SqlBuilder
toSqlBuilder
                ([FN] -> [SqlBuilder]) -> [FN] -> [SqlBuilder]
forall a b. (a -> b) -> a -> b
$ Proxy a -> [FN]
forall a. Entity a => Proxy a -> [FN]
fieldNames Proxy a
p
        values :: SqlBuilder
values = [SqlBuilder] -> SqlBuilder
forall a. Monoid a => [a] -> a
mconcat
                 ([SqlBuilder] -> SqlBuilder) -> [SqlBuilder] -> SqlBuilder
forall a b. (a -> b) -> a -> b
$ SqlBuilder -> [SqlBuilder] -> [SqlBuilder]
forall a. a -> [a] -> [a]
L.intersperse SqlBuilder
","
                 ([SqlBuilder] -> [SqlBuilder]) -> [SqlBuilder] -> [SqlBuilder]
forall a b. (a -> b) -> a -> b
$ (a -> SqlBuilder) -> [a] -> [SqlBuilder]
forall a b. (a -> b) -> [a] -> [b]
map a -> SqlBuilder
rValue
                 ([a] -> [SqlBuilder]) -> [a] -> [SqlBuilder]
forall a b. (a -> b) -> a -> b
$ NonEmpty a -> [a]
forall a. NonEmpty a -> [a]
NL.toList NonEmpty a
rows

    in [sqlExp|INSERT INTO ^{tableName p}
               (^{names}) VALUES ^{values}|]
  where
    rValue :: a -> SqlBuilder
    rValue :: a -> SqlBuilder
rValue a
row =
        let values :: SqlBuilder
values = [SqlBuilder] -> SqlBuilder
forall a. Monoid a => [a] -> a
mconcat
                     ([SqlBuilder] -> SqlBuilder) -> [SqlBuilder] -> SqlBuilder
forall a b. (a -> b) -> a -> b
$ SqlBuilder -> [SqlBuilder] -> [SqlBuilder]
forall a. a -> [a] -> [a]
L.intersperse SqlBuilder
","
                     ([SqlBuilder] -> [SqlBuilder]) -> [SqlBuilder] -> [SqlBuilder]
forall a b. (a -> b) -> a -> b
$ (Action -> SqlBuilder) -> [Action] -> [SqlBuilder]
forall a b. (a -> b) -> [a] -> [b]
map Action -> SqlBuilder
forall a. ToField a => a -> SqlBuilder
mkValue
                     ([Action] -> [SqlBuilder]) -> [Action] -> [SqlBuilder]
forall a b. (a -> b) -> a -> b
$ a -> [Action]
forall a. ToRow a => a -> [Action]
toRow a
row
        in [sqlExp|(^{values})|]