{-# 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
  , selectById
  , selectOneByField
  , selectManyByField
  , selectWhereNotNull
  , selectWhereNull
  , selectOneWhereIn
  , joinSelectById
  , joinSelectOneByField
  , selectOrderBy

    -- ** Insertion
  , insert
  , insertMany
  , upsert

    -- ** Update
  , update
  , updateFieldsBy

    -- ** Deletion
  , delete
  , deleteByField

    -- * SQL Combinators API

    -- ** Selection
  , _select
  , _selectWithFields
  , _where
  , _selectWhere
  , _selectWhereNotNull
  , _selectWhereNull
  , _selectWhereIn
  , _joinSelect
  , _innerJoin
  , _joinSelectWithFields
  , _joinSelectOneByField

    -- ** Insertion
  , _insert
  , _onConflictDoUpdate

    -- ** Update
  , _update
  , _updateBy
  , _updateFields
  , _updateFieldsBy

    -- ** Deletion
  , _delete
  , _deleteWhere
  , _orderBy
  , _orderByMany
  )
where

import Control.Monad (void)
import Control.Monad.IO.Class (MonadIO)
import Data.Foldable (fold)
import Data.Int (Int64)
import Data.Text.Display (display)
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 Data.Text (Text)
import Database.PostgreSQL.Entity.DBT (QueryNature (..), execute, executeMany, query, queryOne, 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.Types
 >>> import Database.PostgreSQL.Entity.Internal
 >>> import Database.PostgreSQL.Entity.Internal.BlogPost
 >>> import Database.PostgreSQL.Entity.Internal.QQ
 >>> import Database.PostgreSQL.Simple.Types (Query (..))
 >>> import Data.Vector (Vector)
 >>> import qualified Data.Vector as V
-}

{- $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 :: forall e value (m :: * -> *).
(Entity e, FromRow e, MonadIO m, ToRow value) =>
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 (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 :: forall e value (m :: * -> *).
(Entity e, FromRow e, MonadIO m, ToRow value) =>
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 (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 :: forall e value (m :: * -> *).
(Entity e, FromRow e, MonadIO m, ToRow value) =>
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 (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 :: forall e (m :: * -> *).
(Entity e, FromRow e, MonadIO m) =>
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 (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 :: forall e (m :: * -> *).
(Entity e, FromRow e, MonadIO m) =>
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 (forall e. Entity e => Vector Field -> Query
_selectWhereNull @e Vector Field
fs)

{-| Select statement when for an entity where the field is one of the options passed

 @since 0.0.2.0
-}
selectOneWhereIn
  :: forall e m
   . (Entity e, FromRow e, MonadIO m)
  => Field
  -> Vector Text
  -> DBT m (Maybe e)
selectOneWhereIn :: forall e (m :: * -> *).
(Entity e, FromRow e, MonadIO m) =>
Field -> Vector Text -> DBT m (Maybe e)
selectOneWhereIn Field
f Vector Text
values = QueryNature -> Query -> DBT m (Maybe e)
forall result (m :: * -> *).
(FromRow result, MonadIO m) =>
QueryNature -> Query -> DBT m (Maybe result)
queryOne_ QueryNature
Select (forall e. Entity e => Field -> Vector Text -> Query
_selectWhereIn @e Field
f Vector Text
values)

{-| 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 :: forall e1 e2 (m :: * -> *).
(Entity e1, Entity e2, FromRow e1, MonadIO m) =>
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 (forall e1 e2. (Entity e1, Entity e2) => Query
_joinSelect @e1 @e2)

{-| Perform a @INNER JOIN ON field1 WHERE field2 = value@ between two entities

 @since 0.0.2.0
-}
joinSelectOneByField
  :: forall e1 e2 value m
   . (Entity e1, Entity e2, FromRow e1, MonadIO m, ToField value)
  => Field
  -- ^ The field over which the two tables will be joined
  -> Field
  -- ^ The field in the where clause
  -> value
  -- ^ The value of the where clause
  -> DBT m (Vector e1)
joinSelectOneByField :: forall e1 e2 value (m :: * -> *).
(Entity e1, Entity e2, FromRow e1, MonadIO m, ToField value) =>
Field -> Field -> value -> DBT m (Vector e1)
joinSelectOneByField Field
pivot Field
whereClause value
value =
  QueryNature -> Query -> Only value -> DBT m (Vector e1)
forall params result (m :: * -> *).
(ToRow params, FromRow result, MonadIO m) =>
QueryNature -> Query -> params -> DBT m (Vector result)
query QueryNature
Select (forall e1 e2. (Entity e1, Entity e2) => Field -> Field -> Query
_joinSelectOneByField @e1 @e2 Field
pivot Field
whereClause) (value -> Only value
forall a. a -> Only a
Only value
value)

--

{-| Perform a SELECT + ORDER BY query on an entity

 @since 0.0.2.0
-}
selectOrderBy
  :: forall e m
   . (Entity e, FromRow e, MonadIO m)
  => Vector (Field, SortKeyword)
  -> DBT m (Vector e)
selectOrderBy :: forall e (m :: * -> *).
(Entity e, FromRow e, MonadIO m) =>
Vector (Field, SortKeyword) -> DBT m (Vector e)
selectOrderBy Vector (Field, SortKeyword)
sortSpec = QueryNature -> Query -> DBT m (Vector e)
forall result (m :: * -> *).
(FromRow result, MonadIO m) =>
QueryNature -> Query -> DBT m (Vector result)
query_ QueryNature
Select (forall e. Entity e => Query
_select @e Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Vector (Field, SortKeyword) -> Query
_orderByMany Vector (Field, SortKeyword)
sortSpec)

{-| Insert an entity.

 @since 0.0.1.0
-}
insert
  :: forall e values m
   . (Entity e, ToRow values, MonadIO m)
  => values
  -> DBT m ()
insert :: forall e values (m :: * -> *).
(Entity e, ToRow values, MonadIO m) =>
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 (forall e. Entity e => Query
_insert @e) values
fs

{-| Insert an entity with a "ON CONFLICT DO UPDATE" clause on the primary key as the conflict target

 @since 0.0.2.0
-}
upsert
  :: forall e values m
   . (Entity e, ToRow values, MonadIO m)
  => values
  -- ^ Entity to insert
  -> Vector Field
  -- ^ Fields to replace in case of conflict
  -> DBT m ()
upsert :: forall e values (m :: * -> *).
(Entity e, ToRow values, MonadIO m) =>
values -> Vector Field -> DBT m ()
upsert values
entity Vector Field
fieldsToReplace = 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 (forall e. Entity e => Query
_insert @e Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Vector Field -> Query
_onConflictDoUpdate Vector Field
conflictTarget Vector Field
fieldsToReplace) values
entity
  where
    conflictTarget :: Vector Field
conflictTarget = Field -> Vector Field
forall a. a -> Vector a
V.singleton (Field -> Vector Field) -> Field -> Vector Field
forall a b. (a -> b) -> a -> b
$ forall e. Entity e => Field
primaryKey @e

{-| Insert multiple rows of an entity.

 @since 0.0.2.0
-}
insertMany
  :: forall e values m
   . (Entity e, ToRow values, MonadIO m)
  => [values]
  -> DBT m ()
insertMany :: forall e values (m :: * -> *).
(Entity e, ToRow values, MonadIO m) =>
[values] -> DBT m ()
insertMany [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
executeMany QueryNature
Insert (forall e. Entity e => Query
_insert @e) [values]
values

{-| 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 :: forall e values (m :: * -> *).
(Entity e, ToRow values, MonadIO m) =>
values -> 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 (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 :: forall e v1 v2 (m :: * -> *).
(Entity e, MonadIO m, ToRow v2, ToField v1) =>
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 (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 :: forall e values (m :: * -> *).
(Entity e, ToRow values, MonadIO m) =>
values -> DBT m ()
delete value
value = forall e values (m :: * -> *).
(Entity e, ToRow values, MonadIO m) =>
Vector Field -> values -> DBT m ()
deleteByField @e [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 :: forall e values (m :: * -> *).
(Entity e, ToRow values, MonadIO m) =>
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 (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 :: forall e. Entity e => 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
<> 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
<> 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 :: forall e. Entity e => 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
tn
  where
    tn :: Text
tn = forall e. Entity e => Text
getTableName @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.
 Fields that do not exist in the Entity will be kept so that PostgreSQL can report the error.

 __Examples__

 >>> _select @BlogPost <> _where [[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 [ [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\" = ?"

 @since 0.0.1.0
-}
_where :: 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
    clauseFields :: Text
clauseFields = Vector Text -> Text
forall m. Monoid m => Vector m -> m
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 a b. (a -> b) -> Vector a -> Vector b
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\" = ?"

 >>> _selectWhere @BlogPost [ [field| author_id |], [field| title |]]
 "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\" FROM \"blogposts\" WHERE \"author_id\" = ? AND \"title\" = ?"

 @since 0.0.1.0
-}
_selectWhere :: forall e. Entity e => Vector Field -> Query
_selectWhere :: forall e. Entity e => Vector Field -> Query
_selectWhere Vector Field
fs = forall e. Entity e => Query
_select @e Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Query
_where 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 :: forall e. Entity e => Vector Field -> Query
_selectWhereNotNull Vector Field
fs = 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 :: forall e. Entity e => Vector Field -> Query
_selectWhereNull Vector Field
fs = 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 statement where the given field is checked aginst the provided array of values .

 >>> _selectWhereIn @BlogPost [field| title |] [ "Unnamed", "Mordred's Song" ]
 "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\" FROM \"blogposts\" WHERE \"title\" IN ('Unnamed', 'Mordred''s Song')"

 @since 0.0.2.0
-}
_selectWhereIn :: forall e. Entity e => Field -> Vector Text -> Query
_selectWhereIn :: forall e. Entity e => Field -> Vector Text -> Query
_selectWhereIn Field
f Vector Text
values = 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
<> Field -> Vector Text -> Text
isIn Field
f Vector Text
values)

{-| 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 :: forall e1 e2. (Entity e1, Entity e2) => 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
<> 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
<> 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
<> forall e. Entity e => Text
getTableName @e1
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Query -> Text
queryToText (forall e. Entity e => Field -> Query
_innerJoin @e2 (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 :: forall e. Entity e => 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
<> 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 :: forall e1 e2.
(Entity e1, Entity e2) =>
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
<> forall e. Entity e => Text
getTableName @e1
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Query -> Text
queryToText (forall e. Entity e => Field -> Query
_innerJoin @e2 (forall e. Entity e => Field
primaryKey @e2))
  where
    tn1 :: Text
tn1 = forall e. Entity e => Text
getTableName @e1
    tn2 :: Text
tn2 = forall e. Entity e => Text
getTableName @e2

{-| Produce a "SELECT FROM" over two entities.

 __Examples__

 >>> _joinSelectOneByField @BlogPost @Author [field| author_id |] [field| name |] :: Query
 "SELECT blogposts.\"blogpost_id\", blogposts.\"author_id\", blogposts.\"uuid_list\", blogposts.\"title\", blogposts.\"content\", blogposts.\"created_at\" FROM \"blogposts\" INNER JOIN \"authors\" ON \"blogposts\".\"author_id\" = \"authors\".\"author_id\" WHERE authors.\"name\" = ?"

 @since 0.0.2.0
-}
_joinSelectOneByField
  :: forall e1 e2
   . (Entity e1, Entity e2)
  => Field
  -> Field
  -> Query
_joinSelectOneByField :: forall e1 e2. (Entity e1, Entity e2) => Field -> Field -> Query
_joinSelectOneByField Field
pivotField Field
whereField =
  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
<> forall e. Entity e => Text
expandQualifiedFields @e1
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" FROM "
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (forall e. Entity e => Text
getTableName @e1)
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" INNER JOIN "
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> forall e. Entity e => Text
getTableName @e2
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ON "
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (forall e. Entity e => Text
getTableName @e1)
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"."
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Field -> Text
getFieldName Field
pivotField
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" = "
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (forall e. Entity e => Text
getTableName @e2)
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"."
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Field -> Text
getFieldName Field
pivotField
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" WHERE "
      Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> forall e. Entity e => Field -> Text
placeholder' @e2 Field
whereField

{-| 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 (?, ?, ?, ?, ?, ?)"

 @since 0.0.1.0
-}
_insert :: forall e. Entity e => Query
_insert :: forall e. Entity e => 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
<> 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 (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
$ forall e. Entity e => Vector Field
fields @e)

{-| Produce a "ON CONFLICT (target) DO UPDATE SET …" statement.

 __Examples__

 >>> _onConflictDoUpdate [[field| blogpost_id |]] [ [field| title |], [field| content |]]
 " ON CONFLICT (blogpost_id) DO UPDATE SET title = EXCLUDED.title, content = EXCLUDED.content"

 >>> _onConflictDoUpdate [[field| blogpost_id |], [field| author_id |]] [ [field| title |], [field| content |]]
 " ON CONFLICT (blogpost_id, author_id) DO UPDATE SET title = EXCLUDED.title, content = EXCLUDED.content"

 >>> _insert @BlogPost <> _onConflictDoUpdate [[field| blogpost_id |]] [ [field| title |], [field| content |]]
 "INSERT INTO \"blogposts\" (\"blogpost_id\", \"author_id\", \"uuid_list\", \"title\", \"content\", \"created_at\") VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (blogpost_id) DO UPDATE SET title = EXCLUDED.title, content = EXCLUDED.content"

 @since 0.0.2.0
-}
_onConflictDoUpdate :: Vector Field -> Vector Field -> Query
_onConflictDoUpdate :: Vector Field -> Vector Field -> Query
_onConflictDoUpdate Vector Field
conflictTarget Vector Field
fieldsToReplace =
  Text -> Query
textToQuery (Text -> Query) -> Text -> Query
forall a b. (a -> b) -> a -> b
$ Text
" ON CONFLICT (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
targetNames Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
") DO UPDATE SET " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
replacedFields
  where
    targetNames :: Text
targetNames = Vector Text -> Text
forall m. Monoid m => Vector m -> m
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
", " ((Field -> Text) -> Vector Field -> Vector Text
forall a b. (a -> b) -> Vector a -> Vector b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Field -> Text
fieldName Vector Field
conflictTarget)
    replacedFields :: Text
replacedFields = Vector Text -> Text
forall m. Monoid m => Vector m -> m
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
", " ((Field -> Text) -> Vector Field -> Vector Text
forall a b. (a -> b) -> Vector a -> Vector b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Text -> Text
replaceField (Text -> Text) -> (Field -> Text) -> Field -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Field -> Text
fieldName) Vector Field
fieldsToReplace)
    replaceField :: Text -> Text
    replaceField :: Text -> Text
replaceField Text
f = Text
f Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" = EXCLUDED." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
f

{-| 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(?, ?, ?, ?, ?) WHERE \"blogpost_id\" = ?"

 @since 0.0.1.0
-}
_update :: forall e. Entity e => Query
_update :: forall e. Entity e => Query
_update = forall e. Entity e => Field -> Query
_updateBy @e (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 :: forall e. Entity e => Field -> Query
_updateBy Field
f = forall e. Entity e => Vector Field -> Field -> Query
_updateFieldsBy @e (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 :: forall e. Entity e => Vector Field -> Query
_updateFields Vector Field
fs = forall e. Entity e => Vector Field -> Field -> Query
_updateFieldsBy @e Vector Field
fs (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 :: forall e. Entity e => 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
<> 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
_where [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
/= (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 :: forall e. Entity e => Query
_delete = Text -> Query
textToQuery (Text
"DELETE FROM " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> forall e. Entity e => Text
getTableName @e) Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Query
_where [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 :: forall e. Entity e => Vector Field -> Query
_deleteWhere Vector Field
fs = Text -> Query
textToQuery (Text
"DELETE FROM " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (forall e. Entity e => Text
getTableName @e)) Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Vector Field -> Query
_where Vector Field
fs

{-| Produce an ORDER BY clause with one field and a sorting keyword

 __Examples__

 >>> _orderBy ([field| title |], ASC)
 " ORDER BY \"title\" ASC"

 @since 0.0.2.0
-}
_orderBy :: (Field, SortKeyword) -> Query
_orderBy :: (Field, SortKeyword) -> Query
_orderBy (Field
f, SortKeyword
sort) = Text -> Query
textToQuery (Text
" ORDER BY " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
quoteName (Field -> Text
fieldName Field
f) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> SortKeyword -> Text
forall a. Display a => a -> Text
display SortKeyword
sort)

{-| Produce an ORDER BY clause with many fields and sorting keywords

 __Examples__

 >>> _orderByMany (V.fromList [([field| title |], ASC), ([field| created_at |], DESC)])
 " ORDER BY \"title\" ASC, \"created_at\" DESC"

 @since 0.0.2.0
-}
_orderByMany :: Vector (Field, SortKeyword) -> Query
_orderByMany :: Vector (Field, SortKeyword) -> Query
_orderByMany Vector (Field, SortKeyword)
sortExpressions = Text -> Query
textToQuery (Text -> Query) -> Text -> Query
forall a b. (a -> b) -> a -> b
$ Text
" ORDER BY " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Vector Text -> Text
forall m. Monoid m => Vector m -> m
forall (t :: * -> *) m. (Foldable t, Monoid m) => t m -> m
fold (Text -> Vector Text -> Vector Text
intercalateVector Text
", " (Vector Text -> Vector Text) -> Vector Text -> Vector Text
forall a b. (a -> b) -> a -> b
$ ((Field, SortKeyword) -> Text)
-> Vector (Field, SortKeyword) -> Vector Text
forall a b. (a -> b) -> Vector a -> Vector b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Field, SortKeyword) -> Text
renderSortExpression Vector (Field, SortKeyword)
sortExpressions)