{-|
Module: IHP.DataSync.DynamicQueryCompiler
Description: Compiles a DynamicQuery to SQL
Copyright: (c) digitally induced GmbH, 2021
-}
module IHP.DataSync.DynamicQueryCompiler where

import IHP.Prelude
import IHP.DataSync.DynamicQuery
import qualified IHP.QueryBuilder as QueryBuilder
import qualified Database.PostgreSQL.Simple as PG
import qualified Database.PostgreSQL.Simple.FromField as PG
import qualified Database.PostgreSQL.Simple.FromRow as PG
import qualified Database.PostgreSQL.Simple.ToField as PG
import qualified Database.PostgreSQL.Simple.Types as PG
import qualified Database.PostgreSQL.Simple.Notification as PG
import qualified Data.List as List

compileQuery :: DynamicSQLQuery -> (PG.Query, [PG.Action])
compileQuery :: DynamicSQLQuery -> (Query, [Action])
compileQuery DynamicSQLQuery { [OrderByClause]
Maybe Text
Maybe Condition
Text
SelectedColumns
$sel:offsetClause:DynamicSQLQuery :: DynamicSQLQuery -> Maybe Text
$sel:limitClause:DynamicSQLQuery :: DynamicSQLQuery -> Maybe Text
$sel:orderByClause:DynamicSQLQuery :: DynamicSQLQuery -> [OrderByClause]
$sel:whereCondition:DynamicSQLQuery :: DynamicSQLQuery -> Maybe Condition
$sel:selectedColumns:DynamicSQLQuery :: DynamicSQLQuery -> SelectedColumns
$sel:table:DynamicSQLQuery :: DynamicSQLQuery -> Text
offsetClause :: Maybe Text
limitClause :: Maybe Text
orderByClause :: [OrderByClause]
whereCondition :: Maybe Condition
selectedColumns :: SelectedColumns
table :: Text
.. } = (Query
sql, [Action]
args)
    where
        sql :: Query
sql = Query
"SELECT ? FROM ?" Query -> Query -> Query
forall a. Semigroup a => a -> a -> a
<> Query
orderBySql
        args :: [Action]
args = [Maybe Action] -> [Action]
forall a. [Maybe a] -> [a]
catMaybes
                [ Action -> Maybe Action
forall a. a -> Maybe a
Just (SelectedColumns -> Action
compileSelectedColumns SelectedColumns
selectedColumns)
                , Action -> Maybe Action
forall a. a -> Maybe a
Just (Identifier -> Action
forall a. ToField a => a -> Action
PG.toField (Text -> Identifier
PG.Identifier Text
table))
                ]
                [Action] -> [Action] -> [Action]
forall a. Semigroup a => a -> a -> a
<> [Action]
orderByArgs

        (Query
orderBySql, [Action]
orderByArgs) = case [OrderByClause]
orderByClause of
                [] -> (Query
"", [])
                [OrderByClause]
orderByClauses ->
                    ( ByteString -> Query
PG.Query (ByteString -> Query) -> ByteString -> Query
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
forall a b. ConvertibleStrings a b => a -> b
cs (Text -> ByteString) -> Text -> ByteString
forall a b. (a -> b) -> a -> b
$ Text
" ORDER BY " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (Text -> [Text] -> Text
intercalate Text
", " ((OrderByClause -> Text) -> [OrderByClause] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> OrderByClause -> Text
forall a b. a -> b -> a
const Text
"? ?") [OrderByClause]
orderByClauses))
                    , [OrderByClause]
orderByClauses
                        [OrderByClause] -> ([OrderByClause] -> [[Action]]) -> [[Action]]
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> (OrderByClause -> [Action]) -> [OrderByClause] -> [[Action]]
forall a b. (a -> b) -> [a] -> [b]
map (\QueryBuilder.OrderByClause { ByteString
$sel:orderByColumn:OrderByClause :: OrderByClause -> ByteString
orderByColumn :: ByteString
orderByColumn, OrderByDirection
$sel:orderByDirection:OrderByClause :: OrderByClause -> OrderByDirection
orderByDirection :: OrderByDirection
orderByDirection } ->
                                    [ Identifier -> Action
forall a. ToField a => a -> Action
PG.toField (Identifier -> Action) -> Identifier -> Action
forall a b. (a -> b) -> a -> b
$ Text -> Identifier
PG.Identifier (Text -> Text
fieldNameToColumnName (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ ByteString -> Text
forall a b. ConvertibleStrings a b => a -> b
cs ByteString
orderByColumn)
                                    , Action -> Action
forall a. ToField a => a -> Action
PG.toField (Action -> Action) -> Action -> Action
forall a b. (a -> b) -> a -> b
$ if OrderByDirection
orderByDirection OrderByDirection -> OrderByDirection -> Bool
forall a. Eq a => a -> a -> Bool
== OrderByDirection
QueryBuilder.Desc
                                        then Builder -> Action
PG.Plain Builder
"DESC"
                                        else Builder -> Action
PG.Plain Builder
""
                                    ]
                                )
                        [[Action]] -> ([[Action]] -> [Action]) -> [Action]
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> [[Action]] -> [Action]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat
                    )

compileSelectedColumns :: SelectedColumns -> PG.Action
compileSelectedColumns :: SelectedColumns -> Action
compileSelectedColumns SelectedColumns
SelectAll = Builder -> Action
PG.Plain Builder
"*"
compileSelectedColumns (SelectSpecific [Text]
fields) = [Action] -> Action
PG.Many [Action]
args
    where
        args :: [PG.Action]
        args :: [Action]
args = [Action] -> [[Action]] -> [Action]
forall a. [a] -> [[a]] -> [a]
List.intercalate ([Builder -> Action
PG.Plain Builder
", "]) [[Action]]
fieldActions
        fieldActions :: [[PG.Action]]
        fieldActions :: [[Action]]
fieldActions = ((Text -> [Action]) -> [Text] -> [[Action]]
forall a b. (a -> b) -> [a] -> [b]
map (\Text
field -> [ Identifier -> Action
forall a. ToField a => a -> Action
PG.toField (Text -> Identifier
PG.Identifier Text
field) ]) [Text]
fields)

-- TODO: validate query against schema