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

import Hasura.Backends.Postgres.SQL.DML qualified as S
import Hasura.Backends.Postgres.SQL.Types (Identifier (..), QualifiedFunction, qualifiedObjectToText, toIdentifier)
import Hasura.Backends.Postgres.Translate.Select.Internal.Aliases
import Hasura.Backends.Postgres.Types.Function
import Hasura.Prelude
import Hasura.RQL.IR
import Hasura.RQL.Types.Common (FieldName)
import Hasura.RQL.Types.Function
import Hasura.SQL.Backend

-- | 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"

pageInfoSelectAliasIdentifier :: Identifier
pageInfoSelectAliasIdentifier :: Identifier
pageInfoSelectAliasIdentifier = Text -> Identifier
Identifier Text
"__page_info"

cursorsSelectAliasIdentifier :: Identifier
cursorsSelectAliasIdentifier :: Identifier
cursorsSelectAliasIdentifier = Text -> Identifier
Identifier Text
"__cursors_select"

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 ::
  Identifier -> FunctionArgsExpG (ArgumentExp S.SQLExp) -> S.FunctionArgs
fromTableRowArgs :: Identifier -> FunctionArgsExpG (ArgumentExp SQLExp) -> FunctionArgs
fromTableRowArgs Identifier
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 (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 Identifier
alias)
        (Identifier -> Identifier -> SQLExp
forall a b. (IsIdentifier a, IsIdentifier b) => a -> b -> SQLExp
S.mkQIdenExp Identifier
alias (Identifier -> SQLExp) -> (Text -> Identifier) -> Text -> SQLExp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Identifier
Identifier)
    alias :: Identifier
alias = TableAlias -> Identifier
forall a. IsIdentifier a => a -> Identifier
toIdentifier (TableAlias -> Identifier) -> TableAlias -> Identifier
forall a b. (a -> b) -> a -> b
$ Identifier -> TableAlias
mkBaseTableAlias Identifier
prefix

selectFromToFromItem :: Identifier -> SelectFrom ('Postgres pgKind) -> S.FromItem
selectFromToFromItem :: Identifier -> SelectFrom ('Postgres pgKind) -> FromItem
selectFromToFromItem Identifier
pfx = \case
  FromTable TableName ('Postgres pgKind)
tn -> QualifiedTable -> Maybe TableAlias -> FromItem
S.FISimple TableName ('Postgres pgKind)
QualifiedTable
tn Maybe TableAlias
forall a. Maybe a
Nothing
  FromIdentifier FIIdentifier
i -> Identifier -> FromItem
S.FIIdentifier (Identifier -> FromItem) -> Identifier -> FromItem
forall a b. (a -> b) -> a -> b
$ FIIdentifier -> Identifier
forall a. IsIdentifier a => a -> Identifier
toIdentifier FIIdentifier
i
  FromFunction FunctionName ('Postgres pgKind)
qf FunctionArgsExp
  ('Postgres pgKind) (SQLExpression ('Postgres pgKind))
args Maybe [(Column ('Postgres pgKind), ScalarType ('Postgres pgKind))]
defListM ->
    FunctionExp -> FromItem
S.FIFunc (FunctionExp -> FromItem) -> FunctionExp -> FromItem
forall a b. (a -> b) -> a -> b
$
      QualifiedFunction
-> FunctionArgs -> Maybe FunctionAlias -> FunctionExp
S.FunctionExp FunctionName ('Postgres pgKind)
QualifiedFunction
qf (Identifier -> FunctionArgsExpG (ArgumentExp SQLExp) -> FunctionArgs
fromTableRowArgs Identifier
pfx FunctionArgsExp
  ('Postgres pgKind) (SQLExpression ('Postgres pgKind))
FunctionArgsExpG (ArgumentExp SQLExp)
args) (Maybe FunctionAlias -> FunctionExp)
-> Maybe FunctionAlias -> FunctionExp
forall a b. (a -> b) -> a -> b
$
        FunctionAlias -> Maybe FunctionAlias
forall a. a -> Maybe a
Just (FunctionAlias -> Maybe FunctionAlias)
-> FunctionAlias -> Maybe FunctionAlias
forall a b. (a -> b) -> a -> b
$
          TableAlias -> Maybe [(ColumnAlias, PGScalarType)] -> FunctionAlias
S.mkFunctionAlias
            (Identifier -> TableAlias
forall a. IsIdentifier a => a -> TableAlias
S.toTableAlias (Identifier -> TableAlias) -> Identifier -> TableAlias
forall a b. (a -> b) -> a -> b
$ QualifiedFunction -> Identifier
functionToIdentifier FunctionName ('Postgres pgKind)
QualifiedFunction
qf)
            (([(PGCol, PGScalarType)] -> [(ColumnAlias, PGScalarType)])
-> Maybe [(PGCol, PGScalarType)]
-> Maybe [(ColumnAlias, PGScalarType)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (((PGCol, PGScalarType) -> (ColumnAlias, PGScalarType))
-> [(PGCol, PGScalarType)] -> [(ColumnAlias, PGScalarType)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((PGCol -> ColumnAlias)
-> (PGCol, PGScalarType) -> (ColumnAlias, PGScalarType)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
first PGCol -> ColumnAlias
forall a. IsIdentifier a => a -> ColumnAlias
S.toColumnAlias)) Maybe [(Column ('Postgres pgKind), ScalarType ('Postgres pgKind))]
Maybe [(PGCol, PGScalarType)]
defListM)

-- | 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