{-# LANGUAGE GeneralizedNewtypeDeriving #-}

{- |
Copyright : Flipstone Technology Partners 2023
License   : MIT
Stability : Stable

@since 1.0.0.0
-}
module Orville.PostgreSQL.Expr.Insert
  ( InsertExpr
  , insertExpr
  , InsertColumnList
  , insertColumnList
  , InsertSource
  , insertSqlValues
  , RowValues
  , rowValues
  )
where

import Data.Maybe (catMaybes)

import Orville.PostgreSQL.Expr.Name (ColumnName, Qualified, TableName)
import Orville.PostgreSQL.Expr.ReturningExpr (ReturningExpr)
import qualified Orville.PostgreSQL.Raw.RawSql as RawSql
import Orville.PostgreSQL.Raw.SqlValue (SqlValue)

{- |
Type to represent a SQL "INSERT" statement. E.G.

> INSERT INTO foo (id) VALUES (1),(3),(3)

'InsertExpr' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype InsertExpr
  = InsertExpr RawSql.RawSql
  deriving
    ( -- | @since 1.0.0.0
      RawSql -> InsertExpr
InsertExpr -> RawSql
(InsertExpr -> RawSql)
-> (RawSql -> InsertExpr) -> SqlExpression InsertExpr
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: InsertExpr -> RawSql
toRawSql :: InsertExpr -> RawSql
$cunsafeFromRawSql :: RawSql -> InsertExpr
unsafeFromRawSql :: RawSql -> InsertExpr
RawSql.SqlExpression
    )

{- | Create an 'InsertExpr' for the given 'TableName', limited to the specific columns if
given. Callers of this likely want to use a function to create the 'InsertSource' to ensure the
input values are correctly used as parameters. This function does not include that protection
itself.

@since 1.0.0.0
-}
insertExpr ::
  Qualified TableName ->
  Maybe InsertColumnList ->
  InsertSource ->
  Maybe ReturningExpr ->
  InsertExpr
insertExpr :: Qualified TableName
-> Maybe InsertColumnList
-> InsertSource
-> Maybe ReturningExpr
-> InsertExpr
insertExpr Qualified TableName
target Maybe InsertColumnList
maybeInsertColumns InsertSource
source Maybe ReturningExpr
maybeReturning =
  RawSql -> InsertExpr
InsertExpr
    (RawSql -> InsertExpr)
-> ([RawSql] -> RawSql) -> [RawSql] -> InsertExpr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RawSql -> [RawSql] -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
RawSql.space
    ([RawSql] -> InsertExpr) -> [RawSql] -> InsertExpr
forall a b. (a -> b) -> a -> b
$ [Maybe RawSql] -> [RawSql]
forall a. [Maybe a] -> [a]
catMaybes
      [ RawSql -> Maybe RawSql
forall a. a -> Maybe a
Just (RawSql -> Maybe RawSql) -> RawSql -> Maybe RawSql
forall a b. (a -> b) -> a -> b
$ String -> RawSql
RawSql.fromString String
"INSERT INTO"
      , RawSql -> Maybe RawSql
forall a. a -> Maybe a
Just (RawSql -> Maybe RawSql) -> RawSql -> Maybe RawSql
forall a b. (a -> b) -> a -> b
$ Qualified TableName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql Qualified TableName
target
      , (InsertColumnList -> RawSql)
-> Maybe InsertColumnList -> Maybe RawSql
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap InsertColumnList -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql Maybe InsertColumnList
maybeInsertColumns
      , RawSql -> Maybe RawSql
forall a. a -> Maybe a
Just (RawSql -> Maybe RawSql) -> RawSql -> Maybe RawSql
forall a b. (a -> b) -> a -> b
$ InsertSource -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql InsertSource
source
      , (ReturningExpr -> RawSql) -> Maybe ReturningExpr -> Maybe RawSql
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ReturningExpr -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql Maybe ReturningExpr
maybeReturning
      ]

{- |
Type to represent the SQL columns list for an insert statement. E.G.

> (foo,bar,baz)

'InsertColumnList' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype InsertColumnList
  = InsertColumnList RawSql.RawSql
  deriving
    ( -- | @since 1.0.0.0
      RawSql -> InsertColumnList
InsertColumnList -> RawSql
(InsertColumnList -> RawSql)
-> (RawSql -> InsertColumnList) -> SqlExpression InsertColumnList
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: InsertColumnList -> RawSql
toRawSql :: InsertColumnList -> RawSql
$cunsafeFromRawSql :: RawSql -> InsertColumnList
unsafeFromRawSql :: RawSql -> InsertColumnList
RawSql.SqlExpression
    )

{- | Create an 'InsertColumnList' for the given 'ColumnName's, making sure the columns are wrapped in
parens and commas are used to separate.

@since 1.0.0.0
-}
insertColumnList :: [ColumnName] -> InsertColumnList
insertColumnList :: [ColumnName] -> InsertColumnList
insertColumnList [ColumnName]
columnNames =
  RawSql -> InsertColumnList
InsertColumnList (RawSql -> InsertColumnList) -> RawSql -> InsertColumnList
forall a b. (a -> b) -> a -> b
$
    RawSql
RawSql.leftParen
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql -> [RawSql] -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
RawSql.comma ((ColumnName -> RawSql) -> [ColumnName] -> [RawSql]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ColumnName -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql [ColumnName]
columnNames)
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql
RawSql.rightParen

{- |
Type to represent the SQL for the source of data for an insert statement. E.G.

> VALUES ('Bob',32),('Cindy',33)

'InsertSource' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype InsertSource
  = InsertSource RawSql.RawSql
  deriving
    ( -- | @since 1.0.0.0
      RawSql -> InsertSource
InsertSource -> RawSql
(InsertSource -> RawSql)
-> (RawSql -> InsertSource) -> SqlExpression InsertSource
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: InsertSource -> RawSql
toRawSql :: InsertSource -> RawSql
$cunsafeFromRawSql :: RawSql -> InsertSource
unsafeFromRawSql :: RawSql -> InsertSource
RawSql.SqlExpression
    )

{- | Create an 'InsertSource' for the given 'RowValues'. This ensures that all input values are used
as parameters and comma-separated in the generated SQL.

@since 1.0.0.0
-}
insertRowValues :: [RowValues] -> InsertSource
insertRowValues :: [RowValues] -> InsertSource
insertRowValues [RowValues]
rows =
  RawSql -> InsertSource
InsertSource (RawSql -> InsertSource) -> RawSql -> InsertSource
forall a b. (a -> b) -> a -> b
$
    String -> RawSql
RawSql.fromString String
"VALUES "
      RawSql -> RawSql -> RawSql
forall a. Semigroup a => a -> a -> a
<> RawSql -> [RawSql] -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
RawSql.comma ((RowValues -> RawSql) -> [RowValues] -> [RawSql]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap RowValues -> RawSql
forall a. SqlExpression a => a -> RawSql
RawSql.toRawSql [RowValues]
rows)

{- | Create an 'InsertSource' for the given 'SqlValue's. This ensures that all input values are used
as parameters and comma-separated in the generated SQL.

@since 1.0.0.0
-}
insertSqlValues :: [[SqlValue]] -> InsertSource
insertSqlValues :: [[SqlValue]] -> InsertSource
insertSqlValues =
  [RowValues] -> InsertSource
insertRowValues ([RowValues] -> InsertSource)
-> ([[SqlValue]] -> [RowValues]) -> [[SqlValue]] -> InsertSource
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([SqlValue] -> RowValues) -> [[SqlValue]] -> [RowValues]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [SqlValue] -> RowValues
rowValues

{- |
Type to represent a SQL row literal. For example, a single row to insert
in a @VALUES@ clause. E.G.

> ('Cindy',33)

'RowValues' provides a 'RawSql.SqlExpression' instance. See
'RawSql.unsafeSqlExpression' for how to construct a value with your own custom
SQL.

@since 1.0.0.0
-}
newtype RowValues
  = RowValues RawSql.RawSql
  deriving
    ( -- | @since 1.0.0.0
      RawSql -> RowValues
RowValues -> RawSql
(RowValues -> RawSql)
-> (RawSql -> RowValues) -> SqlExpression RowValues
forall a. (a -> RawSql) -> (RawSql -> a) -> SqlExpression a
$ctoRawSql :: RowValues -> RawSql
toRawSql :: RowValues -> RawSql
$cunsafeFromRawSql :: RawSql -> RowValues
unsafeFromRawSql :: RawSql -> RowValues
RawSql.SqlExpression
    )

{- | Create a 'RowValues' for the given 'SqlValue's. This ensures that all input values are used as
parameters and comma-separated in the generated SQL.

@since 1.0.0.0
-}
rowValues :: [SqlValue] -> RowValues
rowValues :: [SqlValue] -> RowValues
rowValues [SqlValue]
values =
  RawSql -> RowValues
RowValues (RawSql -> RowValues) -> RawSql -> RowValues
forall a b. (a -> b) -> a -> b
$
    [RawSql] -> RawSql
forall a. Monoid a => [a] -> a
mconcat
      [ RawSql
RawSql.leftParen
      , RawSql -> [RawSql] -> RawSql
forall sql (f :: * -> *).
(SqlExpression sql, Foldable f) =>
RawSql -> f sql -> RawSql
RawSql.intercalate RawSql
RawSql.comma ((SqlValue -> RawSql) -> [SqlValue] -> [RawSql]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap SqlValue -> RawSql
RawSql.parameter [SqlValue]
values)
      , RawSql
RawSql.rightParen
      ]