{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE Strict #-}
{-|
  Module      : Database.PostgreSQL.Entity
  Copyright   : © Clément Delafargue, 2018
                  Théophile Choutri, 2021
  License     : MIT
  Maintainer  : theophile@choutri.eu
  Stability   : stable

  A PostgreSQL database layer that does not get in your way.


  See the "Database.PostgreSQL.Entity.Internal.BlogPost" module for an example of a data-type implementing the 'Entity' typeclass.
-}
module Database.PostgreSQL.Entity
  (
    -- * The /Entity/ Typeclass
    Entity (..)

    -- * Associated Types
  , Field

    -- * High-level API
    -- $highlevel
    -- ** Selection
  , selectById
  , selectOneByField
  , selectManyByField
  , selectWhereNotNull
  , selectWhereNull
  , joinSelectById
    -- ** Insertion
  , insert
    -- ** Update
  , update
  , updateFieldsBy
    -- ** Deletion
  , delete
  , deleteByField

    -- * SQL Combinators API
    -- ** Selection
  , _select
  , _selectWithFields
  , _where
  , _selectWhere
  , _selectWhereNotNull
  , _selectWhereNull
  , _joinSelect
  , _innerJoin
  , _joinSelectWithFields
    -- ** Insertion
  , _insert
    -- ** Update
  , _update
  , _updateBy
  , _updateFields
  , _updateFieldsBy
    -- ** Deletion
  , _delete
  , _deleteWhere
  ) where

import Control.Monad (void)
import Control.Monad.IO.Class (MonadIO)
import Data.Foldable (fold)
import Data.Int
import Data.Vector (Vector)
import qualified Data.Vector as V
import Database.PostgreSQL.Simple (Only (..))
import Database.PostgreSQL.Simple.FromRow (FromRow)
import Database.PostgreSQL.Simple.ToField (ToField)
import Database.PostgreSQL.Simple.ToRow (ToRow (..))
import Database.PostgreSQL.Simple.Types (Query (..))
import Database.PostgreSQL.Transact (DBT)

import Database.PostgreSQL.Entity.DBT (QueryNature (..), execute, query, queryOne, query_)
import Database.PostgreSQL.Entity.Internal
import Database.PostgreSQL.Entity.Types

-- $setup
-- >>> :set -XQuasiQuotes
-- >>> :set -XOverloadedStrings
-- >>> :set -XOverloadedLists
-- >>> :set -XTypeApplications
-- >>> import Database.PostgreSQL.Entity
-- >>> import Database.PostgreSQL.Entity.Internal.BlogPost
-- >>> import Database.PostgreSQL.Entity.Internal.QQ

-- $highlevel
-- Glossary / Tips’n’Tricks
--
-- * @e@, @e1@, @e2@: Represents an @Entity@
-- * @value@: Represents a Haskell value that can be serialised to PostgreSQL
-- * @Field@: Parameters of type @Field@ can most often be passed in their textual form inside the 'field' quasi-quoter,
--   like @[field| author_id :: uuid|]@. This metaprogramming technique is here to better prevent empty fields from being passed.
--   The PostgreSQL type annotation is optional, but necessary for arrays of UUIDs and of custom enums.
--
-- Consult the [test suite](https://github.com/tchoutri/pg-entity/tree/main/test) to see those functions in action.

-- | Select an entity by its primary key.
--
-- @since 0.0.1.0
selectById :: forall e value m.
           (Entity e, FromRow e, MonadIO m, ToRow value)
           => value -> DBT m (Maybe e)
selectById :: value -> DBT m (Maybe e)
selectById value
value = Field -> value -> DBT m (Maybe e)
forall e value (m :: * -> *).
(Entity e, FromRow e, MonadIO m, ToRow value) =>
Field -> value -> DBT m (Maybe e)
selectOneByField (Entity e => Field
forall e. Entity e => Field
primaryKey @e) value
value

-- | Select precisely __one__ entity by a provided field.
--
-- @since 0.0.1.0
selectOneByField :: forall e value m.
                 (Entity e, FromRow e, MonadIO m, ToRow value)
                 => Field -> value -> DBT m (Maybe e)
selectOneByField :: Field -> value -> DBT m (Maybe e)
selectOneByField Field
f value
value = QueryNature -> Query -> value -> DBT m (Maybe e)
forall params result (m :: * -> *).
(ToRow params, FromRow result, MonadIO m) =>
QueryNature -> Query -> params -> DBT m (Maybe result)
queryOne QueryNature
Select (Vector Field -> Query
forall e. Entity e => Vector Field -> Query
_selectWhere @e [Item (Vector Field)
Field
f]) value
value

-- | Select potentially many entities by a provided field.
--
-- @since 0.0.1.0
selectManyByField :: forall e value m.
                  (Entity e, FromRow e, MonadIO m, ToRow value)
                  => Field -> value -> DBT m (Vector e)
selectManyByField :: Field -> value -> DBT m (Vector e)
selectManyByField Field
f value
value = QueryNature -> Query -> value -> DBT m (Vector e)
forall params result (m :: * -> *).
(ToRow params, FromRow result, MonadIO m) =>
QueryNature -> Query -> params -> DBT m (Vector result)
query QueryNature
Select (Vector Field -> Query
forall e. Entity e => Vector Field -> Query
_selectWhere @e [Item (Vector Field)
Field
f]) value
value

-- | Select statement with a non-null condition
--
-- See '_selectWhereNotNull' for the generated query.
--
-- @since 0.0.1.0
selectWhereNotNull :: forall e m.
                   (Entity e, FromRow e, MonadIO m)
                   => Vector Field -> DBT m (Vector e)
selectWhereNotNull :: Vector Field -> DBT m (Vector e)
selectWhereNotNull Vector Field
fs = QueryNature -> Query -> DBT m (Vector e)
forall result (m :: * -> *).
(FromRow result, MonadIO m) =>
QueryNature -> Query -> DBT m (Vector result)
query_ QueryNature
Select (Vector Field -> Query
forall e. Entity e => Vector Field -> Query
_selectWhereNotNull @e Vector Field
fs)

-- | Select statement with a null condition
--
-- See '_selectWhereNull' for the generated query.
--
-- @since 0.0.1.0
selectWhereNull :: forall e m.
                   (Entity e, FromRow e, MonadIO m)
                   => Vector Field -> DBT m (Vector e)
selectWhereNull :: Vector Field -> DBT m (Vector e)
selectWhereNull Vector Field
fs = QueryNature -> Query -> DBT m (Vector e)
forall result (m :: * -> *).
(FromRow result, MonadIO m) =>
QueryNature -> Query -> DBT m (Vector result)
query_ QueryNature
Select (Vector Field -> Query
forall e. Entity e => Vector Field -> Query
_selectWhereNull @e Vector Field
fs)

-- | Perform a INNER JOIN between two entities
--
-- @since 0.0.1.0
joinSelectById :: forall e1 e2 m.
                (Entity e1, Entity e2, FromRow e1, MonadIO m)
                => DBT m (Vector e1)
joinSelectById :: DBT m (Vector e1)
joinSelectById = QueryNature -> Query -> DBT m (Vector e1)
forall result (m :: * -> *).
(FromRow result, MonadIO m) =>
QueryNature -> Query -> DBT m (Vector result)
query_ QueryNature
Select ((Entity e1, Entity e2) => Query
forall e1 e2. (Entity e1, Entity e2) => Query
_joinSelect @e1 @e2)

-- | Insert an entity.
--
-- @since 0.0.1.0
insert :: forall e values m.
       (Entity e, ToRow values, MonadIO m)
       => values -> DBT m ()
insert :: values -> DBT m ()
insert values
fs = DBT m Int64 -> DBT m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (DBT m Int64 -> DBT m ()) -> DBT m Int64 -> DBT m ()
forall a b. (a -> b) -> a -> b
$ QueryNature -> Query -> values -> DBT m Int64
forall params (m :: * -> *).
(ToRow params, MonadIO m) =>
QueryNature -> Query -> params -> DBT m Int64
execute QueryNature
Insert (Entity e => Query
forall e. Entity e => Query
_insert @e) values
fs

-- | Update an entity.
--
-- The Id of the entity is put at the end of the query automatically through the use of 'UpdateRow'.
-- __Examples__
--
-- > let newAuthor = oldAuthor{…}
-- > update @Author newAuthor
--
-- @since 0.0.1.0
update :: forall e newValue m.
       (Entity e, ToRow newValue, MonadIO m)
       => newValue -> DBT m ()
update :: newValue -> DBT m ()
update newValue
fs = DBT m Int64 -> DBT m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (DBT m Int64 -> DBT m ()) -> DBT m Int64 -> DBT m ()
forall a b. (a -> b) -> a -> b
$ QueryNature -> Query -> UpdateRow newValue -> DBT m Int64
forall params (m :: * -> *).
(ToRow params, MonadIO m) =>
QueryNature -> Query -> params -> DBT m Int64
execute QueryNature
Update (Entity e => Query
forall e. Entity e => Query
_update @e) (newValue -> UpdateRow newValue
forall a. a -> UpdateRow a
UpdateRow newValue
fs)

-- | Update rows of an entity matching the given value
--
-- == Example
--
-- > let newName = "Tiberus McElroy" :: Text
-- > let oldName = "Johnson McElroy" :: Text
-- > updateFieldsBy @Author [[field| name |]] ([field| name |], oldName) (Only newName)
--
-- @since 0.0.1.0
updateFieldsBy :: forall e v1 v2 m.
           (Entity e, MonadIO m, ToRow v2, ToField v1)
           => Vector Field -- ^ Fields to change
           -> (Field, v1)  -- ^ Field on which to match and its value
           -> v2    -- ^ New values of those fields
           -> DBT m Int64
updateFieldsBy :: Vector Field -> (Field, v1) -> v2 -> DBT m Int64
updateFieldsBy Vector Field
fs (Field
f, v1
oldValue) v2
newValue = QueryNature -> Query -> [Action] -> DBT m Int64
forall params (m :: * -> *).
(ToRow params, MonadIO m) =>
QueryNature -> Query -> params -> DBT m Int64
execute QueryNature
Update (Vector Field -> Field -> Query
forall e. Entity e => Vector Field -> Field -> Query
_updateFieldsBy @e Vector Field
fs Field
f) (v2 -> [Action]
forall a. ToRow a => a -> [Action]
toRow v2
newValue [Action] -> [Action] -> [Action]
forall a. [a] -> [a] -> [a]
++ Only v1 -> [Action]
forall a. ToRow a => a -> [Action]
toRow (v1 -> Only v1
forall a. a -> Only a
Only v1
oldValue))

-- | Delete an entity according to its primary key.
--
-- @since 0.0.1.0
delete :: forall e value m.
       (Entity e, ToRow value, MonadIO m)
       => value -> DBT m ()
delete :: value -> DBT m ()
delete value
value = Vector Field -> value -> DBT m ()
forall e values (m :: * -> *).
(Entity e, ToRow values, MonadIO m) =>
Vector Field -> values -> DBT m ()
deleteByField @e [Entity e => Field
forall e. Entity e => Field
primaryKey @e] value
value

-- | Delete rows according to the given fields
--
-- == Example
--
-- > deleteByField @BlogPost [[field| title |]] (Only "Echoes from the other world")
--
-- @since 0.0.1.0
deleteByField :: forall e values m.
       (Entity e, ToRow values, MonadIO m)
       => Vector Field -> values -> DBT m ()
deleteByField :: Vector Field -> values -> DBT m ()
deleteByField Vector Field
fs values
values = DBT m Int64 -> DBT m ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (DBT m Int64 -> DBT m ()) -> DBT m Int64 -> DBT m ()
forall a b. (a -> b) -> a -> b
$ QueryNature -> Query -> values -> DBT m Int64
forall params (m :: * -> *).
(ToRow params, MonadIO m) =>
QueryNature -> Query -> params -> DBT m Int64
execute QueryNature
Delete (Vector Field -> Query
forall e. Entity e => Vector Field -> Query
_deleteWhere @e Vector Field
fs) values
values

-- * SQL combinators API

-- | Produce a SELECT statement for a given entity.
--
-- __Examples__
--
-- >>> _select @BlogPost
-- "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\" FROM \"blogposts\""
--
-- @since 0.0.1.0
_select :: forall e. Entity e => Query
_select :: Query
_select = Text -> Query
textToQuery (Text -> Query) -> Text -> Query
forall a b. (a -> b) -> a -> b
$ Text
"SELECT " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e => Text
forall e. Entity e => Text
expandQualifiedFields @e Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" FROM " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e => Text
forall e. Entity e => Text
getTableName @e

-- | Produce a SELECT statement with explicit fields for a given entity
--
-- __Examples__
--
-- >>> _selectWithFields @BlogPost [ [field| blogpost_id |], [field| created_at |] ]
-- "SELECT blogposts.\"blogpost_id\", blogposts.\"created_at\" FROM \"blogposts\""
--
-- @since 0.0.1.0
_selectWithFields :: forall e. Entity e => Vector Field -> Query
_selectWithFields :: Vector Field -> Query
_selectWithFields Vector Field
fs = Text -> Query
textToQuery (Text -> Query) -> Text -> Query
forall a b. (a -> b) -> a -> b
$ Text
"SELECT " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Text -> Text
expandQualifiedFields' Vector Field
fs Text
tn Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" FROM " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
quoteName Text
tn
  where tn :: Text
tn = Entity e => Text
forall e. Entity e => Text
tableName @e

-- | Produce a WHERE clause, given a vector of fields.
--
-- It is most useful composed with a '_select' or '_delete', which is why these two combinations have their dedicated functions,
-- but the user is free to compose their own queries.
--
-- The 'Entity' constraint is required for '_where' in order to get any type annotation that was given in the schema, as well as to
-- filter out unexisting fields.
--
-- __Examples__
--
-- >>> _select @BlogPost <> _where @BlogPost [[field| blogpost_id |]]
-- "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\" FROM \"blogposts\" WHERE \"blogpost_id\" = ?"
--
-- >>> _select @BlogPost <> _where @BlogPost [ [field| uuid_list |] ]
-- "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\" FROM \"blogposts\" WHERE \"uuid_list\" = ?::uuid[]"
--
-- @since 0.0.1.0
_where :: forall e. Entity e => Vector Field -> Query
_where :: Vector Field -> Query
_where Vector Field
fs' = Text -> Query
textToQuery (Text -> Query) -> Text -> Query
forall a b. (a -> b) -> a -> b
$ Text
" WHERE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
clauseFields
  where
    fieldNames :: Vector Text
fieldNames = (Field -> Text) -> Vector Field -> Vector Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Field -> Text
fieldName Vector Field
fs'
    fs :: Vector Field
fs = (Field -> Bool) -> Vector Field -> Vector Field
forall a. (a -> Bool) -> Vector a -> Vector a
V.filter (\Field
f -> Field -> Text
fieldName Field
f Text -> Vector Text -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Vector Text
fieldNames) (Entity e => Vector Field
forall e. Entity e => Vector Field
fields @e)
    clauseFields :: Text
clauseFields = Vector Text -> Text
forall (t :: * -> *) m. (Foldable t, Monoid m) => t m -> m
fold (Vector Text -> Text) -> Vector Text -> Text
forall a b. (a -> b) -> a -> b
$ Text -> Vector Text -> Vector Text
intercalateVector Text
" AND " ((Field -> Text) -> Vector Field -> Vector Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Field -> Text
placeholder Vector Field
fs)

-- | Produce a SELECT statement for a given entity and fields.
--
-- __Examples__
--
-- >>> _selectWhere @BlogPost [ [field| author_id |] ]
-- "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\" FROM \"blogposts\" WHERE \"author_id\" = ?"
--
-- @since 0.0.1.0
_selectWhere :: forall e. Entity e => Vector Field -> Query
_selectWhere :: Vector Field -> Query
_selectWhere Vector Field
fs = Entity e => Query
forall e. Entity e => Query
_select @e Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Query
forall e. Entity e => Vector Field -> Query
_where @e Vector Field
fs

-- | Produce a SELECT statement where the provided fields are checked for being non-null.
-- r
--
-- >>> _selectWhereNotNull @BlogPost [ [field| author_id |] ]
-- "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\" FROM \"blogposts\" WHERE \"author_id\" IS NOT NULL"
--
-- @since 0.0.1.0
_selectWhereNotNull :: forall e. Entity e => Vector Field -> Query
_selectWhereNotNull :: Vector Field -> Query
_selectWhereNotNull Vector Field
fs = Entity e => Query
forall e. Entity e => Query
_select @e Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Text -> Query
textToQuery (Text
" WHERE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Text
isNotNull Vector Field
fs)

-- | Produce a SELECT statement where the provided fields are checked for being null.
--
-- >>> _selectWhereNull @BlogPost [ [field| author_id |] ]
-- "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\" FROM \"blogposts\" WHERE \"author_id\" IS NULL"
--
-- @since 0.0.1.0
_selectWhereNull :: forall e. Entity e => Vector Field -> Query
_selectWhereNull :: Vector Field -> Query
_selectWhereNull Vector Field
fs = Entity e => Query
forall e. Entity e => Query
_select @e Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Text -> Query
textToQuery (Text
" WHERE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Text
isNull Vector Field
fs)

-- | Produce a "SELECT FROM" over two entities.
--
-- __Examples__
--
-- >>> _joinSelect @BlogPost @Author
-- "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\", authors.\"author_id\", authors.\"name\", authors.\"created_at\" FROM \"blogposts\" INNER JOIN \"authors\" USING(author_id)"
--
-- @since 0.0.1.0
_joinSelect :: forall e1 e2. (Entity e1, Entity e2) => Query
_joinSelect :: Query
_joinSelect = Text -> Query
textToQuery (Text -> Query) -> Text -> Query
forall a b. (a -> b) -> a -> b
$ Text
"SELECT " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e1 => Text
forall e. Entity e => Text
expandQualifiedFields @e1 Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", "
                                    Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e2 => Text
forall e. Entity e => Text
expandQualifiedFields @e2 Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
                           Text
" FROM " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e1 => Text
forall e. Entity e => Text
getTableName @e1
                           Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Query -> Text
queryToText (Field -> Query
forall e. Entity e => Field -> Query
_innerJoin @e2 (Entity e2 => Field
forall e. Entity e => Field
primaryKey @e2))

-- | Produce a "INNER JOIN … USING(…)" fragment.
--
-- __Examples__
--
-- >>> _innerJoin @BlogPost [field| author_id |]
-- " INNER JOIN \"blogposts\" USING(author_id)"
--
-- @since 0.0.1.0
_innerJoin :: forall e. (Entity e) => Field -> Query
_innerJoin :: Field -> Query
_innerJoin Field
f = Text -> Query
textToQuery (Text -> Query) -> Text -> Query
forall a b. (a -> b) -> a -> b
$ Text
" INNER JOIN " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e => Text
forall e. Entity e => Text
getTableName @e
                          Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" USING(" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Field -> Text
fieldName Field
f Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"

-- | Produce a "SELECT [table1_fields, table2_fields] FROM table1 INNER JOIN table2 USING(table2_pk)" statement.
-- The primary is used as the join point between the two tables.
--
-- __Examples__
--
-- >>> _joinSelectWithFields @BlogPost @Author [ [field| title |] ] [ [field| name |] ]
-- "SELECT blogposts.\"title\", authors.\"name\" FROM \"blogposts\" INNER JOIN \"authors\" USING(author_id)"
--
-- @since 0.0.1.0
_joinSelectWithFields :: forall e1 e2. (Entity e1, Entity e2)
                   => Vector Field -> Vector Field -> Query
_joinSelectWithFields :: Vector Field -> Vector Field -> Query
_joinSelectWithFields Vector Field
fs1 Vector Field
fs2 =
  Text -> Query
textToQuery (Text -> Query) -> Text -> Query
forall a b. (a -> b) -> a -> b
$ Text
"SELECT " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Text -> Text
expandQualifiedFields' Vector Field
fs1 Text
tn1
    Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Text -> Text
expandQualifiedFields' Vector Field
fs2 Text
tn2
    Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" FROM " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e1 => Text
forall e. Entity e => Text
getTableName @e1
    Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Query -> Text
queryToText (Field -> Query
forall e. Entity e => Field -> Query
_innerJoin @e2 (Entity e2 => Field
forall e. Entity e => Field
primaryKey @e2))
  where
    tn1 :: Text
tn1 = Entity e1 => Text
forall e. Entity e => Text
tableName @e1
    tn2 :: Text
tn2 = Entity e2 => Text
forall e. Entity e => Text
tableName @e2

-- | Produce an INSERT statement for the given entity.
--
-- __Examples__
--
-- >>> _insert @BlogPost
-- "INSERT INTO \"blogposts\" (\"blogpost_id\", \"author_id\", \"uuid_list\", \"title\", \"content\", \"created_at\") VALUES (?, ?, ?::uuid[], ?, ?, ?)"
--
-- @since 0.0.1.0
_insert :: forall e. Entity e => Query
_insert :: Query
_insert = Text -> Query
textToQuery (Text -> Query) -> Text -> Query
forall a b. (a -> b) -> a -> b
$ Text
"INSERT INTO " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e => Text
forall e. Entity e => Text
getTableName @e Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
fs Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" VALUES " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
ps
  where
    fs :: Text
fs = Text -> Text
inParens (Entity e => Text
forall e. Entity e => Text
expandFields @e)
    ps :: Text
ps = Text -> Text
inParens (Vector Field -> Text
generatePlaceholders (Vector Field -> Text) -> Vector Field -> Text
forall a b. (a -> b) -> a -> b
$ Entity e => Vector Field
forall e. Entity e => Vector Field
fields @e)

-- | Produce an UPDATE statement for the given entity by primary key
--
-- __Examples__
--
-- >>> _update @Author
-- "UPDATE \"authors\" SET (\"name\", \"created_at\") = ROW(?, ?) WHERE \"author_id\" = ?"
--
-- >>> _update @BlogPost
-- "UPDATE \"blogposts\" SET (\"author_id\", \"uuid_list\", \"title\", \"content\", \"created_at\") = ROW(?, ?::uuid[], ?, ?, ?) WHERE \"blogpost_id\" = ?"
--
-- @since 0.0.1.0

_update :: forall e. Entity e => Query
_update :: Query
_update = Field -> Query
forall e. Entity e => Field -> Query
_updateBy @e (Entity e => Field
forall e. Entity e => Field
primaryKey @e)

-- | Produce an UPDATE statement for the given entity by the given field.
--
-- __Examples__
--
-- >>> _updateBy @Author [field| name |]
-- "UPDATE \"authors\" SET (\"name\", \"created_at\") = ROW(?, ?) WHERE \"name\" = ?"
--
-- @since 0.0.1.0
_updateBy :: forall e. Entity e => Field -> Query
_updateBy :: Field -> Query
_updateBy Field
f = Vector Field -> Field -> Query
forall e. Entity e => Vector Field -> Field -> Query
_updateFieldsBy @e (Entity e => Vector Field
forall e. Entity e => Vector Field
fields @e) Field
f

-- | Produce an UPDATE statement for the given entity and fields, by primary key.
--
-- >>> _updateFields @Author [ [field| name |] ]
-- "UPDATE \"authors\" SET (\"name\") = ROW(?) WHERE \"author_id\" = ?"
--
-- @since 0.0.1.0
_updateFields :: forall e. Entity e => Vector Field -> Query
_updateFields :: Vector Field -> Query
_updateFields Vector Field
fs = Vector Field -> Field -> Query
forall e. Entity e => Vector Field -> Field -> Query
_updateFieldsBy @e Vector Field
fs (Entity e => Field
forall e. Entity e => Field
primaryKey @e)

-- | Produce an UPDATE statement for the given entity and fields, by the specified field.
--
-- >>> _updateFieldsBy @Author [ [field| name |] ] [field| name |]
-- "UPDATE \"authors\" SET (\"name\") = ROW(?) WHERE \"name\" = ?"
--
-- >>> _updateFieldsBy @BlogPost [[field| author_id |], [field| title |]] [field| title |]
-- "UPDATE \"blogposts\" SET (\"author_id\", \"title\") = ROW(?, ?) WHERE \"title\" = ?"
--
-- @since 0.0.1.0
_updateFieldsBy :: forall e. Entity e
                => Vector Field -- ^ Field names to update
                -> Field        -- ^ Field on which to match
                -> Query
_updateFieldsBy :: Vector Field -> Field -> Query
_updateFieldsBy Vector Field
fs' Field
f = Text -> Query
textToQuery (
    Text
"UPDATE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e => Text
forall e. Entity e => Text
getTableName @e
              Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" SET " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
updatedFields Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" = " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
newValues)
              Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Query
forall e. Entity e => Vector Field -> Query
_where @e [Item (Vector Field)
Field
f]
  where
    fs :: Vector Field
fs = (Field -> Bool) -> Vector Field -> Vector Field
forall a. (a -> Bool) -> Vector a -> Vector a
V.filter (Field -> Field -> Bool
forall a. Eq a => a -> a -> Bool
/= (Entity e => Field
forall e. Entity e => Field
primaryKey @e)) Vector Field
fs'
    newValues :: Text
newValues = Text
"ROW" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
inParens (Vector Field -> Text
generatePlaceholders Vector Field
fs)
    updatedFields :: Text
updatedFields = Text -> Text
inParens (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$
      (Text -> Text -> Text) -> Vector Text -> Text
forall a. (a -> a -> a) -> Vector a -> a
V.foldl1' (\Text
element Text
acc -> Text
element Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
acc) (Text -> Text
quoteName (Text -> Text) -> (Field -> Text) -> Field -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Field -> Text
fieldName (Field -> Text) -> Vector Field -> Vector Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Vector Field
fs)

-- | Produce a DELETE statement for the given entity, with a match on the Primary Key
--
-- __Examples__
--
-- >>> _delete @BlogPost
-- "DELETE FROM \"blogposts\" WHERE \"blogpost_id\" = ?"
--
-- @since 0.0.1.0
_delete :: forall e. Entity e => Query
_delete :: Query
_delete = Text -> Query
textToQuery (Text
"DELETE FROM " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Entity e => Text
forall e. Entity e => Text
getTableName @e) Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Query
forall e. Entity e => Vector Field -> Query
_where @e [Entity e => Field
forall e. Entity e => Field
primaryKey @e]

-- | Produce a DELETE statement for the given entity and fields
--
-- __Examples__
--
-- >>> _deleteWhere @BlogPost [[field| title |], [field| created_at |]]
-- "DELETE FROM blogposts WHERE \"title\" = ? AND \"created_at\" = ?"
--
-- @since 0.0.1.0
_deleteWhere :: forall e. Entity e => Vector Field -> Query
_deleteWhere :: Vector Field -> Query
_deleteWhere Vector Field
fs = Text -> Query
textToQuery (Text
"DELETE FROM " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (Entity e => Text
forall e. Entity e => Text
tableName @e)) Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Query
forall e. Entity e => Vector Field -> Query
_where @e Vector Field
fs