-- | Stuff gutted from Translate.Select.
module Hasura.Backends.Postgres.Translate.Select.Internal.Helpers
  ( mkFirstElementExp,
    mkLastElementExp,
    cursorIdentifier,
    startCursorIdentifier,
    endCursorIdentifier,
    hasNextPageIdentifier,
    hasPreviousPageIdentifier,
    pageInfoSelectAlias,
    pageInfoSelectAliasIdentifier,
    cursorsSelectAlias,
    cursorsSelectAliasIdentifier,
    encodeBase64,
    fromTableRowArgs,
    functionToIdentifier,
    withJsonBuildObj,
    withForceAggregation,
    selectToSelectWith,
    customSQLToTopLevelCTEs,
    customSQLToInnerCTEs,
    nativeQueryNameToAlias,
    toQuery,
  )
where

import Control.Monad.Writer (Writer, runWriter)
import Data.Bifunctor (bimap)
import Data.HashMap.Strict qualified as HashMap
import Data.Text.Extended (toTxt)
import Database.PG.Query (Query, fromBuilder)
import Hasura.Backends.Postgres.SQL.DML qualified as S
import Hasura.Backends.Postgres.SQL.RenameIdentifiers
import Hasura.Backends.Postgres.SQL.Types
  ( Identifier (..),
    QualifiedFunction,
    TableIdentifier (..),
    qualifiedObjectToText,
    tableIdentifierToIdentifier,
  )
import Hasura.Backends.Postgres.Translate.Select.Internal.Aliases
import Hasura.Backends.Postgres.Translate.Types (CustomSQLCTEs (..))
import Hasura.Backends.Postgres.Types.Function
import Hasura.Function.Cache
import Hasura.NativeQuery.Metadata (NativeQueryName (..))
import Hasura.Prelude
import Hasura.RQL.Types.Common (FieldName)
import Hasura.SQL.Types (ToSQL (toSQL))

-- | First element extractor expression from given record set
-- For example:- To get first "id" column from given row set,
-- the function generates the SQL expression AS `(array_agg("id"))[1]`
mkFirstElementExp :: S.SQLExp -> S.SQLExp
mkFirstElementExp :: SQLExp -> SQLExp
mkFirstElementExp SQLExp
expIdentifier =
  -- For Example
  SQLExp -> SQLExp -> SQLExp
S.SEArrayIndex (Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"array_agg" [SQLExp
expIdentifier] Maybe OrderByExp
forall a. Maybe a
Nothing) (Int -> SQLExp
S.intToSQLExp Int
1)

-- | Last element extractor expression from given record set.
-- For example:- To get first "id" column from given row set,
-- the function generates the SQL expression AS `(array_agg("id"))[array_length(array_agg("id"), 1)]`
mkLastElementExp :: S.SQLExp -> S.SQLExp
mkLastElementExp :: SQLExp -> SQLExp
mkLastElementExp SQLExp
expIdentifier =
  let arrayExp :: SQLExp
arrayExp = Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"array_agg" [SQLExp
expIdentifier] Maybe OrderByExp
forall a. Maybe a
Nothing
   in SQLExp -> SQLExp -> SQLExp
S.SEArrayIndex SQLExp
arrayExp
        (SQLExp -> SQLExp) -> SQLExp -> SQLExp
forall a b. (a -> b) -> a -> b
$ Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"array_length" [SQLExp
arrayExp, Int -> SQLExp
S.intToSQLExp Int
1] Maybe OrderByExp
forall a. Maybe a
Nothing

cursorIdentifier :: Identifier
cursorIdentifier :: Identifier
cursorIdentifier = Text -> Identifier
Identifier Text
"__cursor"

startCursorIdentifier :: Identifier
startCursorIdentifier :: Identifier
startCursorIdentifier = Text -> Identifier
Identifier Text
"__start_cursor"

endCursorIdentifier :: Identifier
endCursorIdentifier :: Identifier
endCursorIdentifier = Text -> Identifier
Identifier Text
"__end_cursor"

hasPreviousPageIdentifier :: Identifier
hasPreviousPageIdentifier :: Identifier
hasPreviousPageIdentifier = Text -> Identifier
Identifier Text
"__has_previous_page"

hasNextPageIdentifier :: Identifier
hasNextPageIdentifier :: Identifier
hasNextPageIdentifier = Text -> Identifier
Identifier Text
"__has_next_page"

pageInfoSelectAlias :: S.TableAlias
pageInfoSelectAlias :: TableAlias
pageInfoSelectAlias = Text -> TableAlias
S.mkTableAlias Text
"__page_info"

pageInfoSelectAliasIdentifier :: TableIdentifier
pageInfoSelectAliasIdentifier :: TableIdentifier
pageInfoSelectAliasIdentifier = TableAlias -> TableIdentifier
S.tableAliasToIdentifier TableAlias
pageInfoSelectAlias

cursorsSelectAlias :: S.TableAlias
cursorsSelectAlias :: TableAlias
cursorsSelectAlias = Text -> TableAlias
S.mkTableAlias Text
"__cursors_select"

cursorsSelectAliasIdentifier :: TableIdentifier
cursorsSelectAliasIdentifier :: TableIdentifier
cursorsSelectAliasIdentifier = TableAlias -> TableIdentifier
S.tableAliasToIdentifier TableAlias
cursorsSelectAlias

encodeBase64 :: S.SQLExp -> S.SQLExp
encodeBase64 :: SQLExp -> SQLExp
encodeBase64 =
  SQLExp -> SQLExp
removeNewline (SQLExp -> SQLExp) -> (SQLExp -> SQLExp) -> SQLExp -> SQLExp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SQLExp -> SQLExp
bytesToBase64Text (SQLExp -> SQLExp) -> (SQLExp -> SQLExp) -> SQLExp -> SQLExp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SQLExp -> SQLExp
convertToBytes
  where
    convertToBytes :: SQLExp -> SQLExp
convertToBytes SQLExp
e =
      Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"convert_to" [SQLExp
e, Text -> SQLExp
S.SELit Text
"UTF8"] Maybe OrderByExp
forall a. Maybe a
Nothing
    bytesToBase64Text :: SQLExp -> SQLExp
bytesToBase64Text SQLExp
e =
      Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"encode" [SQLExp
e, Text -> SQLExp
S.SELit Text
"base64"] Maybe OrderByExp
forall a. Maybe a
Nothing
    removeNewline :: SQLExp -> SQLExp
removeNewline SQLExp
e =
      Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"regexp_replace" [SQLExp
e, Text -> SQLExp
S.SELit Text
"\\n", Text -> SQLExp
S.SELit Text
"", Text -> SQLExp
S.SELit Text
"g"] Maybe OrderByExp
forall a. Maybe a
Nothing

fromTableRowArgs ::
  TableIdentifier -> FunctionArgsExpG (ArgumentExp S.SQLExp) -> S.FunctionArgs
fromTableRowArgs :: TableIdentifier
-> FunctionArgsExpG (ArgumentExp SQLExp) -> FunctionArgs
fromTableRowArgs TableIdentifier
prefix = FunctionArgsExpG SQLExp -> FunctionArgs
toFunctionArgs (FunctionArgsExpG SQLExp -> FunctionArgs)
-> (FunctionArgsExpG (ArgumentExp SQLExp)
    -> FunctionArgsExpG SQLExp)
-> FunctionArgsExpG (ArgumentExp SQLExp)
-> FunctionArgs
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ArgumentExp SQLExp -> SQLExp)
-> FunctionArgsExpG (ArgumentExp SQLExp) -> FunctionArgsExpG SQLExp
forall a b. (a -> b) -> FunctionArgsExpG a -> FunctionArgsExpG b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ArgumentExp SQLExp -> SQLExp
toSQLExp
  where
    toFunctionArgs :: FunctionArgsExpG SQLExp -> FunctionArgs
toFunctionArgs (FunctionArgsExp [SQLExp]
positional HashMap Text SQLExp
named) =
      [SQLExp] -> HashMap Text SQLExp -> FunctionArgs
S.FunctionArgs [SQLExp]
positional HashMap Text SQLExp
named
    toSQLExp :: ArgumentExp SQLExp -> SQLExp
toSQLExp =
      SQLExp -> (Text -> SQLExp) -> ArgumentExp SQLExp -> SQLExp
forall a. a -> (Text -> a) -> ArgumentExp a -> a
onArgumentExp
        (Identifier -> SQLExp
S.SERowIdentifier (TableIdentifier -> Identifier
tableIdentifierToIdentifier TableIdentifier
baseTableIdentifier))
        (TableIdentifier -> Identifier -> SQLExp
forall b. IsIdentifier b => TableIdentifier -> b -> SQLExp
S.mkQIdenExp TableIdentifier
baseTableIdentifier (Identifier -> SQLExp) -> (Text -> Identifier) -> Text -> SQLExp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Identifier
Identifier)
    baseTableIdentifier :: TableIdentifier
baseTableIdentifier = TableIdentifier -> TableIdentifier
mkBaseTableIdentifier TableIdentifier
prefix

-- | Given a @NativeQueryName@, what should we call the CTE generated for it?
nativeQueryNameToAlias :: NativeQueryName -> Int -> S.TableAlias
nativeQueryNameToAlias :: NativeQueryName -> Int -> TableAlias
nativeQueryNameToAlias NativeQueryName
nqName Int
freshId = Text -> TableAlias
S.mkTableAlias (Text
"cte_" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Name -> Text
forall a. ToTxt a => a -> Text
toTxt (NativeQueryName -> Name
getNativeQueryName NativeQueryName
nqName) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"_" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
tshow Int
freshId)

-- | Converts a function name to an 'Identifier'.
--
-- If the schema name is public, it will just use its name, otherwise it will
-- prefix it by the schema name.
functionToIdentifier :: QualifiedFunction -> Identifier
functionToIdentifier :: QualifiedFunction -> Identifier
functionToIdentifier = Text -> Identifier
Identifier (Text -> Identifier)
-> (QualifiedFunction -> Text) -> QualifiedFunction -> Identifier
forall b c a. (b -> c) -> (a -> b) -> a -> c
. QualifiedFunction -> Text
forall a. ToTxt a => QualifiedObject a -> Text
qualifiedObjectToText

-- uses json_build_object to build a json object
withJsonBuildObj ::
  FieldName -> [S.SQLExp] -> (S.ColumnAlias, S.SQLExp)
withJsonBuildObj :: FieldName -> [SQLExp] -> (ColumnAlias, SQLExp)
withJsonBuildObj FieldName
parAls [SQLExp]
exps =
  (FieldName -> ColumnAlias
forall a. IsIdentifier a => a -> ColumnAlias
S.toColumnAlias FieldName
parAls, SQLExp
jsonRow)
  where
    jsonRow :: SQLExp
jsonRow = [SQLExp] -> SQLExp
S.applyJsonBuildObj [SQLExp]
exps

-- | Forces aggregation
withForceAggregation :: S.TypeAnn -> S.SQLExp -> S.SQLExp
withForceAggregation :: TypeAnn -> SQLExp -> SQLExp
withForceAggregation TypeAnn
tyAnn SQLExp
e =
  -- bool_or to force aggregation
  Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"coalesce" [SQLExp
e, SQLExp -> TypeAnn -> SQLExp
S.SETyAnn (Text -> SQLExp
S.SEUnsafe Text
"bool_or('true')") TypeAnn
tyAnn] Maybe OrderByExp
forall a. Maybe a
Nothing

-- | unwrap any emitted TopLevelCTEs for custom sql from the Writer and combine
-- them with a @Select@ to create a @SelectWith@
selectToSelectWith :: Writer CustomSQLCTEs S.Select -> S.SelectWith
selectToSelectWith :: Writer CustomSQLCTEs Select -> SelectWithG TopLevelCTE
selectToSelectWith Writer CustomSQLCTEs Select
action =
  let (Select
selectSQL, CustomSQLCTEs
customSQLCTEs) = Writer CustomSQLCTEs Select -> (Select, CustomSQLCTEs)
forall w a. Writer w a -> (a, w)
runWriter Writer CustomSQLCTEs Select
action
   in [(TableAlias, TopLevelCTE)] -> Select -> SelectWithG TopLevelCTE
forall statement.
[(TableAlias, statement)] -> Select -> SelectWithG statement
S.SelectWith (CustomSQLCTEs -> [(TableAlias, TopLevelCTE)]
customSQLToTopLevelCTEs CustomSQLCTEs
customSQLCTEs) Select
selectSQL

-- | convert map of CustomSQL CTEs into named TopLevelCTEs
customSQLToTopLevelCTEs :: CustomSQLCTEs -> [(S.TableAlias, S.TopLevelCTE)]
customSQLToTopLevelCTEs :: CustomSQLCTEs -> [(TableAlias, TopLevelCTE)]
customSQLToTopLevelCTEs =
  ((TableAlias, InterpolatedQuery SQLExp)
 -> (TableAlias, TopLevelCTE))
-> [(TableAlias, InterpolatedQuery SQLExp)]
-> [(TableAlias, TopLevelCTE)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((TableAlias -> TableAlias)
-> (InterpolatedQuery SQLExp -> TopLevelCTE)
-> (TableAlias, InterpolatedQuery SQLExp)
-> (TableAlias, TopLevelCTE)
forall a b c d. (a -> b) -> (c -> d) -> (a, c) -> (b, d)
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap TableAlias -> TableAlias
forall a. IsIdentifier a => a -> TableAlias
S.toTableAlias InterpolatedQuery SQLExp -> TopLevelCTE
S.CTEUnsafeRawSQL) ([(TableAlias, InterpolatedQuery SQLExp)]
 -> [(TableAlias, TopLevelCTE)])
-> (CustomSQLCTEs -> [(TableAlias, InterpolatedQuery SQLExp)])
-> CustomSQLCTEs
-> [(TableAlias, TopLevelCTE)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashMap TableAlias (InterpolatedQuery SQLExp)
-> [(TableAlias, InterpolatedQuery SQLExp)]
forall k v. HashMap k v -> [(k, v)]
HashMap.toList (HashMap TableAlias (InterpolatedQuery SQLExp)
 -> [(TableAlias, InterpolatedQuery SQLExp)])
-> (CustomSQLCTEs -> HashMap TableAlias (InterpolatedQuery SQLExp))
-> CustomSQLCTEs
-> [(TableAlias, InterpolatedQuery SQLExp)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CustomSQLCTEs -> HashMap TableAlias (InterpolatedQuery SQLExp)
getCustomSQLCTEs

-- | convert map of CustomSQL CTEs into named InnerCTEs
customSQLToInnerCTEs :: CustomSQLCTEs -> [(S.TableAlias, S.InnerCTE)]
customSQLToInnerCTEs :: CustomSQLCTEs -> [(TableAlias, InnerCTE)]
customSQLToInnerCTEs =
  ((TableAlias, InterpolatedQuery SQLExp) -> (TableAlias, InnerCTE))
-> [(TableAlias, InterpolatedQuery SQLExp)]
-> [(TableAlias, InnerCTE)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((TableAlias -> TableAlias)
-> (InterpolatedQuery SQLExp -> InnerCTE)
-> (TableAlias, InterpolatedQuery SQLExp)
-> (TableAlias, InnerCTE)
forall a b c d. (a -> b) -> (c -> d) -> (a, c) -> (b, d)
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap TableAlias -> TableAlias
forall a. IsIdentifier a => a -> TableAlias
S.toTableAlias InterpolatedQuery SQLExp -> InnerCTE
S.ICTEUnsafeRawSQL) ([(TableAlias, InterpolatedQuery SQLExp)]
 -> [(TableAlias, InnerCTE)])
-> (CustomSQLCTEs -> [(TableAlias, InterpolatedQuery SQLExp)])
-> CustomSQLCTEs
-> [(TableAlias, InnerCTE)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashMap TableAlias (InterpolatedQuery SQLExp)
-> [(TableAlias, InterpolatedQuery SQLExp)]
forall k v. HashMap k v -> [(k, v)]
HashMap.toList (HashMap TableAlias (InterpolatedQuery SQLExp)
 -> [(TableAlias, InterpolatedQuery SQLExp)])
-> (CustomSQLCTEs -> HashMap TableAlias (InterpolatedQuery SQLExp))
-> CustomSQLCTEs
-> [(TableAlias, InterpolatedQuery SQLExp)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. CustomSQLCTEs -> HashMap TableAlias (InterpolatedQuery SQLExp)
getCustomSQLCTEs

toQuery :: S.SelectWithG S.TopLevelCTE -> Query
toQuery :: SelectWithG TopLevelCTE -> Query
toQuery = Builder -> Query
fromBuilder (Builder -> Query)
-> (SelectWithG TopLevelCTE -> Builder)
-> SelectWithG TopLevelCTE
-> Query
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SelectWithG TopLevelCTE -> Builder
forall a. ToSQL a => a -> Builder
toSQL (SelectWithG TopLevelCTE -> Builder)
-> (SelectWithG TopLevelCTE -> SelectWithG TopLevelCTE)
-> SelectWithG TopLevelCTE
-> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SelectWithG TopLevelCTE -> SelectWithG TopLevelCTE
renameIdentifiersSelectWithTopLevelCTE