-- | Extractors are a pair of an SQL expression and an alias; they get
-- translated like "[SELECT ...] <expr> as <alias>"
module Hasura.Backends.Postgres.Translate.Select.Internal.Extractor
  ( aggregateFieldsToExtractorExps,
    mkAggregateOrderByExtractorAndFields,
    mkRawComputedFieldExpression,
    withJsonAggExtr,
    asSingleRowExtr,
    asJsonAggExtr,
    withColumnOp,
    withRedactionExp,
  )
where

import Control.Monad.Writer.Strict
import Data.List.NonEmpty qualified as NE
import Hasura.Backends.Postgres.SQL.DML qualified as S
import Hasura.Backends.Postgres.SQL.Types
import Hasura.Backends.Postgres.Translate.BoolExp (toSQLBoolExp)
import Hasura.Backends.Postgres.Translate.Select.Internal.Aliases
import Hasura.Backends.Postgres.Translate.Select.Internal.Helpers (fromTableRowArgs)
import Hasura.Backends.Postgres.Translate.Types (PermissionLimitSubQuery (..))
import Hasura.Backends.Postgres.Types.Aggregates
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp
import Hasura.RQL.IR.Select
import Hasura.RQL.Types.Backend
import Hasura.RQL.Types.BackendType
import Hasura.RQL.Types.Column
import Hasura.RQL.Types.Common

-- | Creates node extractors for all of the columns and computed fields used in aggregated fields.
-- The ColumnAliases for all the extractors are namespaced aliases using the 'contextualize*` functions
-- so that none of the extractors names will conflict with one another (for example, if a column name
-- is the same as a field name (eg 'nodes'))
aggregateFieldsToExtractorExps ::
  forall pgKind.
  (Backend ('Postgres pgKind)) =>
  TableIdentifier ->
  AggregateFields ('Postgres pgKind) S.SQLExp ->
  [(S.ColumnAlias, S.SQLExp)]
aggregateFieldsToExtractorExps :: forall (pgKind :: PostgresKind).
Backend ('Postgres pgKind) =>
TableIdentifier
-> AggregateFields ('Postgres pgKind) SQLExp
-> [(ColumnAlias, SQLExp)]
aggregateFieldsToExtractorExps TableIdentifier
sourcePrefix AggregateFields ('Postgres pgKind) SQLExp
aggregateFields =
  (((FieldName, AggregateField ('Postgres pgKind) SQLExp)
  -> [(ColumnAlias, SQLExp)])
 -> AggregateFields ('Postgres pgKind) SQLExp
 -> [(ColumnAlias, SQLExp)])
-> AggregateFields ('Postgres pgKind) SQLExp
-> ((FieldName, AggregateField ('Postgres pgKind) SQLExp)
    -> [(ColumnAlias, SQLExp)])
-> [(ColumnAlias, SQLExp)]
forall a b c. (a -> b -> c) -> b -> a -> c
flip ((FieldName, AggregateField ('Postgres pgKind) SQLExp)
 -> [(ColumnAlias, SQLExp)])
-> AggregateFields ('Postgres pgKind) SQLExp
-> [(ColumnAlias, SQLExp)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap AggregateFields ('Postgres pgKind) SQLExp
aggregateFields (((FieldName, AggregateField ('Postgres pgKind) SQLExp)
  -> [(ColumnAlias, SQLExp)])
 -> [(ColumnAlias, SQLExp)])
-> ((FieldName, AggregateField ('Postgres pgKind) SQLExp)
    -> [(ColumnAlias, SQLExp)])
-> [(ColumnAlias, SQLExp)]
forall a b. (a -> b) -> a -> b
$ \(FieldName
aggregateFieldName, AggregateField ('Postgres pgKind) SQLExp
field) ->
    case AggregateField ('Postgres pgKind) SQLExp
field of
      AFCount CountType ('Postgres pgKind) SQLExp
cty -> case CountAggregate pgKind SQLExp
-> CountType (PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)
forall (pgKind :: PostgresKind) v.
CountAggregate pgKind v
-> CountType (PGCol, AnnRedactionExp ('Postgres pgKind) v)
getCountType CountType ('Postgres pgKind) SQLExp
CountAggregate pgKind SQLExp
cty of
        CountType (PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)
S.CTStar -> []
        S.CTSimple [(PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)]
cols -> [(PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)]
-> [(ColumnAlias, SQLExp)]
colsToExps [(PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)]
cols
        S.CTDistinct [(PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)]
cols -> [(PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)]
-> [(ColumnAlias, SQLExp)]
colsToExps [(PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)]
cols
      AFOp AggregateOp ('Postgres pgKind) SQLExp
aggOp -> ((FieldName, SelectionField ('Postgres pgKind) SQLExp)
 -> Maybe (ColumnAlias, SQLExp))
-> [(FieldName, SelectionField ('Postgres pgKind) SQLExp)]
-> [(ColumnAlias, SQLExp)]
forall a b. (a -> Maybe b) -> [a] -> [b]
forall (f :: * -> *) a b.
Filterable f =>
(a -> Maybe b) -> f a -> f b
mapMaybe (FieldName
-> (FieldName, SelectionField ('Postgres pgKind) SQLExp)
-> Maybe (ColumnAlias, SQLExp)
colToMaybeExp FieldName
aggregateFieldName) ([(FieldName, SelectionField ('Postgres pgKind) SQLExp)]
 -> [(ColumnAlias, SQLExp)])
-> [(FieldName, SelectionField ('Postgres pgKind) SQLExp)]
-> [(ColumnAlias, SQLExp)]
forall a b. (a -> b) -> a -> b
$ AggregateOp ('Postgres pgKind) SQLExp
-> [(FieldName, SelectionField ('Postgres pgKind) SQLExp)]
forall (b :: BackendType) v. AggregateOp b v -> SelectionFields b v
_aoFields AggregateOp ('Postgres pgKind) SQLExp
aggOp
      AFExp Text
_ -> []
  where
    colsToExps :: [(PGCol, AnnRedactionExp ('Postgres pgKind) S.SQLExp)] -> [(S.ColumnAlias, S.SQLExp)]
    colsToExps :: [(PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)]
-> [(ColumnAlias, SQLExp)]
colsToExps = ((PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)
 -> (ColumnAlias, SQLExp))
-> [(PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)]
-> [(ColumnAlias, SQLExp)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(PGCol
col, AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp) -> AnnRedactionExp ('Postgres pgKind) SQLExp
-> PGCol -> (ColumnAlias, SQLExp)
mkColumnExp AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp PGCol
col)

    -- Extract the columns and computed fields we need
    colToMaybeExp ::
      FieldName ->
      (FieldName, SelectionField ('Postgres pgKind) S.SQLExp) ->
      Maybe (S.ColumnAlias, S.SQLExp)
    colToMaybeExp :: FieldName
-> (FieldName, SelectionField ('Postgres pgKind) SQLExp)
-> Maybe (ColumnAlias, SQLExp)
colToMaybeExp FieldName
aggregateFieldName = \case
      (FieldName
_fieldName, SFCol Column ('Postgres pgKind)
col ColumnType ('Postgres pgKind)
_ AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp) -> (ColumnAlias, SQLExp) -> Maybe (ColumnAlias, SQLExp)
forall a. a -> Maybe a
Just ((ColumnAlias, SQLExp) -> Maybe (ColumnAlias, SQLExp))
-> (ColumnAlias, SQLExp) -> Maybe (ColumnAlias, SQLExp)
forall a b. (a -> b) -> a -> b
$ AnnRedactionExp ('Postgres pgKind) SQLExp
-> PGCol -> (ColumnAlias, SQLExp)
mkColumnExp AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp Column ('Postgres pgKind)
PGCol
col
      (FieldName
fieldName, SFComputedField ComputedFieldName
_name ComputedFieldScalarSelect ('Postgres pgKind) SQLExp
cfss) -> (ColumnAlias, SQLExp) -> Maybe (ColumnAlias, SQLExp)
forall a. a -> Maybe a
Just ((ColumnAlias, SQLExp) -> Maybe (ColumnAlias, SQLExp))
-> (ColumnAlias, SQLExp) -> Maybe (ColumnAlias, SQLExp)
forall a b. (a -> b) -> a -> b
$ FieldName
-> FieldName
-> ComputedFieldScalarSelect ('Postgres pgKind) SQLExp
-> (ColumnAlias, SQLExp)
mkComputedFieldExp FieldName
aggregateFieldName FieldName
fieldName ComputedFieldScalarSelect ('Postgres pgKind) SQLExp
cfss
      (FieldName
_fieldName, SFExp Text
_text) -> Maybe (ColumnAlias, SQLExp)
forall a. Maybe a
Nothing

    -- Generate an alias for each column we extract.
    mkColumnExp ::
      AnnRedactionExp ('Postgres pgKind) S.SQLExp ->
      PGCol ->
      (S.ColumnAlias, S.SQLExp)
    mkColumnExp :: AnnRedactionExp ('Postgres pgKind) SQLExp
-> PGCol -> (ColumnAlias, SQLExp)
mkColumnExp AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp PGCol
column =
      let baseTableIdentifier :: TableIdentifier
baseTableIdentifier = TableIdentifier -> TableIdentifier
mkBaseTableIdentifier TableIdentifier
sourcePrefix
          baseTableQual :: Qual
baseTableQual = TableIdentifier -> Maybe TypeAnn -> Qual
S.QualifiedIdentifier TableIdentifier
baseTableIdentifier Maybe TypeAnn
forall a. Maybe a
Nothing
          qualifiedColumn :: SQLExp
qualifiedColumn = Qual
-> AnnRedactionExp ('Postgres pgKind) SQLExp -> SQLExp -> SQLExp
forall (pgKind :: PostgresKind).
Backend ('Postgres pgKind) =>
Qual
-> AnnRedactionExp ('Postgres pgKind) SQLExp -> SQLExp -> SQLExp
withRedactionExp Qual
baseTableQual AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp (SQLExp -> SQLExp) -> SQLExp -> SQLExp
forall a b. (a -> b) -> a -> b
$ TableIdentifier -> Identifier -> SQLExp
forall b. IsIdentifier b => TableIdentifier -> b -> SQLExp
S.mkQIdenExp TableIdentifier
baseTableIdentifier (PGCol -> Identifier
forall a. IsIdentifier a => a -> Identifier
toIdentifier PGCol
column)
          columnAlias :: ColumnAlias
columnAlias = TableIdentifier -> PGCol -> ColumnAlias
contextualizeBaseTableColumn TableIdentifier
sourcePrefix PGCol
column
       in (ColumnAlias -> ColumnAlias
forall a. IsIdentifier a => a -> ColumnAlias
S.toColumnAlias ColumnAlias
columnAlias, SQLExp
qualifiedColumn)

    mkComputedFieldExp :: FieldName -> FieldName -> ComputedFieldScalarSelect ('Postgres pgKind) S.SQLExp -> (S.ColumnAlias, S.SQLExp)
    mkComputedFieldExp :: FieldName
-> FieldName
-> ComputedFieldScalarSelect ('Postgres pgKind) SQLExp
-> (ColumnAlias, SQLExp)
mkComputedFieldExp FieldName
aggregateFieldName FieldName
computedFieldFieldName ComputedFieldScalarSelect ('Postgres pgKind) SQLExp
computedFieldScalarSelect =
      (TableIdentifier -> FieldName -> FieldName -> ColumnAlias
contextualizeAggregateInput TableIdentifier
sourcePrefix FieldName
aggregateFieldName FieldName
computedFieldFieldName, TableIdentifier
-> ComputedFieldScalarSelect ('Postgres pgKind) SQLExp -> SQLExp
forall (pgKind :: PostgresKind).
Backend ('Postgres pgKind) =>
TableIdentifier
-> ComputedFieldScalarSelect ('Postgres pgKind) SQLExp -> SQLExp
mkRawComputedFieldExpression TableIdentifier
sourcePrefix ComputedFieldScalarSelect ('Postgres pgKind) SQLExp
computedFieldScalarSelect)

mkAggregateOrderByExtractorAndFields ::
  forall pgKind.
  (Backend ('Postgres pgKind)) =>
  TableIdentifier ->
  AnnotatedAggregateOrderBy ('Postgres pgKind) S.SQLExp ->
  (S.Extractor, AggregateFields ('Postgres pgKind) S.SQLExp)
mkAggregateOrderByExtractorAndFields :: forall (pgKind :: PostgresKind).
Backend ('Postgres pgKind) =>
TableIdentifier
-> AnnotatedAggregateOrderBy ('Postgres pgKind) SQLExp
-> (Extractor, AggregateFields ('Postgres pgKind) SQLExp)
mkAggregateOrderByExtractorAndFields TableIdentifier
sourcePrefix AnnotatedAggregateOrderBy ('Postgres pgKind) SQLExp
annAggOrderBy =
  case AnnotatedAggregateOrderBy ('Postgres pgKind) SQLExp
annAggOrderBy of
    AnnotatedAggregateOrderBy ('Postgres pgKind) SQLExp
AAOCount ->
      ( SQLExp -> Maybe ColumnAlias -> Extractor
S.Extractor SQLExp
S.countStar Maybe ColumnAlias
alias,
        [(Text -> FieldName
FieldName Text
"count", CountType ('Postgres pgKind) SQLExp
-> AggregateField ('Postgres pgKind) SQLExp
forall (b :: BackendType) v. CountType b v -> AggregateField b v
AFCount (CountType ('Postgres pgKind) SQLExp
 -> AggregateField ('Postgres pgKind) SQLExp)
-> CountType ('Postgres pgKind) SQLExp
-> AggregateField ('Postgres pgKind) SQLExp
forall a b. (a -> b) -> a -> b
$ CountType (PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)
-> CountAggregate pgKind SQLExp
forall (pgKind :: PostgresKind) v.
CountType (PGCol, AnnRedactionExp ('Postgres pgKind) v)
-> CountAggregate pgKind v
CountAggregate CountType (PGCol, AnnRedactionExp ('Postgres pgKind) SQLExp)
forall columnType. CountType columnType
S.CTStar)]
      )
    AAOOp (AggregateOrderByColumn Text
opText ColumnType ('Postgres pgKind)
_resultType ColumnInfo ('Postgres pgKind)
pgColumnInfo AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp) ->
      let pgColumn :: Column ('Postgres pgKind)
pgColumn = ColumnInfo ('Postgres pgKind) -> Column ('Postgres pgKind)
forall (b :: BackendType). ColumnInfo b -> Column b
ciColumn ColumnInfo ('Postgres pgKind)
pgColumnInfo
          pgType :: ColumnType ('Postgres pgKind)
pgType = ColumnInfo ('Postgres pgKind) -> ColumnType ('Postgres pgKind)
forall (b :: BackendType). ColumnInfo b -> ColumnType b
ciType ColumnInfo ('Postgres pgKind)
pgColumnInfo
       in ( SQLExp -> Maybe ColumnAlias -> Extractor
S.Extractor (Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
opText [QIdentifier -> SQLExp
S.SEQIdentifier (QIdentifier -> SQLExp) -> QIdentifier -> SQLExp
forall a b. (a -> b) -> a -> b
$ PGCol -> QIdentifier
columnToQIdentifier Column ('Postgres pgKind)
PGCol
pgColumn] Maybe OrderByExp
forall a. Maybe a
Nothing) Maybe ColumnAlias
alias,
            [ ( Text -> FieldName
FieldName Text
opText,
                AggregateOp ('Postgres pgKind) SQLExp
-> AggregateField ('Postgres pgKind) SQLExp
forall (b :: BackendType) v. AggregateOp b v -> AggregateField b v
AFOp
                  (AggregateOp ('Postgres pgKind) SQLExp
 -> AggregateField ('Postgres pgKind) SQLExp)
-> AggregateOp ('Postgres pgKind) SQLExp
-> AggregateField ('Postgres pgKind) SQLExp
forall a b. (a -> b) -> a -> b
$ Text
-> SelectionFields ('Postgres pgKind) SQLExp
-> AggregateOp ('Postgres pgKind) SQLExp
forall (b :: BackendType) v.
Text -> SelectionFields b v -> AggregateOp b v
AggregateOp
                    Text
opText
                    [ ( forall (b :: BackendType). Backend b => Column b -> FieldName
fromCol @('Postgres pgKind) Column ('Postgres pgKind)
pgColumn,
                        Column ('Postgres pgKind)
-> ColumnType ('Postgres pgKind)
-> AnnRedactionExp ('Postgres pgKind) SQLExp
-> SelectionField ('Postgres pgKind) SQLExp
forall (b :: BackendType) v.
Column b
-> ColumnType b -> AnnRedactionExp b v -> SelectionField b v
SFCol Column ('Postgres pgKind)
pgColumn ColumnType ('Postgres pgKind)
pgType AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp
                      )
                    ]
              )
            ]
          )
  where
    alias :: Maybe ColumnAlias
alias = ColumnAlias -> Maybe ColumnAlias
forall a. a -> Maybe a
Just (ColumnAlias -> Maybe ColumnAlias)
-> ColumnAlias -> Maybe ColumnAlias
forall a b. (a -> b) -> a -> b
$ AnnotatedAggregateOrderBy ('Postgres pgKind) SQLExp -> ColumnAlias
forall (pgKind :: PostgresKind) v.
AnnotatedAggregateOrderBy ('Postgres pgKind) v -> ColumnAlias
mkAggregateOrderByAlias AnnotatedAggregateOrderBy ('Postgres pgKind) SQLExp
annAggOrderBy

    columnToQIdentifier :: PGCol -> S.QIdentifier
    columnToQIdentifier :: PGCol -> QIdentifier
columnToQIdentifier = TableIdentifier -> ColumnAlias -> QIdentifier
forall b. IsIdentifier b => TableIdentifier -> b -> QIdentifier
S.mkQIdentifier TableIdentifier
sourcePrefix (ColumnAlias -> QIdentifier)
-> (PGCol -> ColumnAlias) -> PGCol -> QIdentifier
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TableIdentifier -> PGCol -> ColumnAlias
contextualizeBaseTableColumn TableIdentifier
sourcePrefix

-- This invokes the computed field function, but does not apply the necessary
-- 'toJSONableExp' over the top. This allows the output of this expression to be consumed by
-- other processing functions such as aggregates
mkRawComputedFieldExpression ::
  forall pgKind.
  (Backend ('Postgres pgKind)) =>
  TableIdentifier ->
  ComputedFieldScalarSelect ('Postgres pgKind) S.SQLExp ->
  S.SQLExp
mkRawComputedFieldExpression :: forall (pgKind :: PostgresKind).
Backend ('Postgres pgKind) =>
TableIdentifier
-> ComputedFieldScalarSelect ('Postgres pgKind) SQLExp -> SQLExp
mkRawComputedFieldExpression TableIdentifier
sourcePrefix (ComputedFieldScalarSelect FunctionName ('Postgres pgKind)
fn FunctionArgsExp ('Postgres pgKind) SQLExp
args ScalarType ('Postgres pgKind)
_ Maybe (ScalarSelectionArguments ('Postgres pgKind))
colOpM AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp) =
  -- The computed field is conditionally outputted depending
  -- on the value of `redactionExp`. `redactionExp` will only specify
  -- redaction in the case of an inherited role.
  -- See [SQL generation for inherited role]
  Qual
-> AnnRedactionExp ('Postgres pgKind) SQLExp -> SQLExp -> SQLExp
forall (pgKind :: PostgresKind).
Backend ('Postgres pgKind) =>
Qual
-> AnnRedactionExp ('Postgres pgKind) SQLExp -> SQLExp -> SQLExp
withRedactionExp (TableIdentifier -> Maybe TypeAnn -> Qual
S.QualifiedIdentifier (TableIdentifier -> TableIdentifier
mkBaseTableIdentifier TableIdentifier
sourcePrefix) Maybe TypeAnn
forall a. Maybe a
Nothing) AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp
    (SQLExp -> SQLExp) -> SQLExp -> SQLExp
forall a b. (a -> b) -> a -> b
$ Maybe ColumnOp -> SQLExp -> SQLExp
withColumnOp Maybe (ScalarSelectionArguments ('Postgres pgKind))
Maybe ColumnOp
colOpM
    (SQLExp -> SQLExp) -> SQLExp -> SQLExp
forall a b. (a -> b) -> a -> b
$ FunctionExp -> SQLExp
S.SEFunction
    (FunctionExp -> SQLExp) -> FunctionExp -> SQLExp
forall a b. (a -> b) -> a -> b
$ QualifiedFunction
-> FunctionArgs -> Maybe FunctionAlias -> FunctionExp
S.FunctionExp FunctionName ('Postgres pgKind)
QualifiedFunction
fn (TableIdentifier
-> FunctionArgsExpG (ArgumentExp SQLExp) -> FunctionArgs
fromTableRowArgs TableIdentifier
sourcePrefix FunctionArgsExp ('Postgres pgKind) SQLExp
FunctionArgsExpG (ArgumentExp SQLExp)
args) Maybe FunctionAlias
forall a. Maybe a
Nothing

withJsonAggExtr ::
  PermissionLimitSubQuery -> Maybe S.OrderByExp -> S.ColumnAlias -> S.SQLExp
withJsonAggExtr :: PermissionLimitSubQuery
-> Maybe OrderByExp -> ColumnAlias -> SQLExp
withJsonAggExtr PermissionLimitSubQuery
permLimitSubQuery Maybe OrderByExp
ordBy ColumnAlias
alias =
  -- if select has aggregations then use subquery to apply permission limit
  case PermissionLimitSubQuery
permLimitSubQuery of
    PLSQRequired Int
permLimit -> Int -> SQLExp
withPermLimit Int
permLimit
    PermissionLimitSubQuery
PLSQNotRequired -> SQLExp
simpleJsonAgg
  where
    simpleJsonAgg :: SQLExp
simpleJsonAgg = SQLExp -> Maybe OrderByExp -> SQLExp
mkSimpleJsonAgg SQLExp
rowIdenExp Maybe OrderByExp
ordBy
    rowIdenExp :: SQLExp
rowIdenExp = Identifier -> SQLExp
S.SEIdentifier (Identifier -> SQLExp) -> Identifier -> SQLExp
forall a b. (a -> b) -> a -> b
$ ColumnAlias -> Identifier
forall a. IsIdentifier a => a -> Identifier
toIdentifier ColumnAlias
alias
    subSelAls :: TableAlias
subSelAls = Text -> TableAlias
S.mkTableAlias Text
"sub_query"
    subSelIdentifier :: TableIdentifier
subSelIdentifier = TableAlias -> TableIdentifier
S.tableAliasToIdentifier TableAlias
subSelAls
    unnestTable :: TableAlias
unnestTable = Text -> TableAlias
S.mkTableAlias Text
"unnest_table"
    unnestTableIdentifier :: TableIdentifier
unnestTableIdentifier = TableAlias -> TableIdentifier
S.tableAliasToIdentifier TableAlias
unnestTable

    mkSimpleJsonAgg :: SQLExp -> Maybe OrderByExp -> SQLExp
mkSimpleJsonAgg SQLExp
rowExp Maybe OrderByExp
ob =
      let jsonAggExp :: SQLExp
jsonAggExp = Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"json_agg" [SQLExp
rowExp] Maybe OrderByExp
ob
       in Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"coalesce" [SQLExp
jsonAggExp, Text -> SQLExp
S.SELit Text
"[]"] Maybe OrderByExp
forall a. Maybe a
Nothing

    withPermLimit :: Int -> SQLExp
withPermLimit Int
limit =
      let subSelect :: Select
subSelect = Int -> Select
mkSubSelect Int
limit
          rowIdentifier :: SQLExp
rowIdentifier = TableIdentifier -> ColumnAlias -> SQLExp
forall b. IsIdentifier b => TableIdentifier -> b -> SQLExp
S.mkQIdenExp TableIdentifier
subSelIdentifier ColumnAlias
alias
          extr :: Extractor
extr = SQLExp -> Maybe ColumnAlias -> Extractor
S.Extractor (SQLExp -> Maybe OrderByExp -> SQLExp
mkSimpleJsonAgg SQLExp
rowIdentifier Maybe OrderByExp
newOrderBy) Maybe ColumnAlias
forall a. Maybe a
Nothing
          fromExp :: FromExp
fromExp =
            [FromItem] -> FromExp
S.FromExp
              ([FromItem] -> FromExp) -> [FromItem] -> FromExp
forall a b. (a -> b) -> a -> b
$ FromItem -> [FromItem]
forall a. a -> [a]
forall (f :: * -> *) a. Applicative f => a -> f a
pure
              (FromItem -> [FromItem]) -> FromItem -> [FromItem]
forall a b. (a -> b) -> a -> b
$ Select -> TableAlias -> FromItem
S.mkSelFromItem Select
subSelect
              (TableAlias -> FromItem) -> TableAlias -> FromItem
forall a b. (a -> b) -> a -> b
$ TableAlias -> TableAlias
forall a. IsIdentifier a => a -> TableAlias
S.toTableAlias TableAlias
subSelAls
       in Select -> SQLExp
S.SESelect
            (Select -> SQLExp) -> Select -> SQLExp
forall a b. (a -> b) -> a -> b
$ Select
S.mkSelect
              { selExtr :: [Extractor]
S.selExtr = Extractor -> [Extractor]
forall a. a -> [a]
forall (f :: * -> *) a. Applicative f => a -> f a
pure Extractor
extr,
                selFrom :: Maybe FromExp
S.selFrom = FromExp -> Maybe FromExp
forall a. a -> Maybe a
Just FromExp
fromExp
              }

    mkSubSelect :: Int -> Select
mkSubSelect Int
limit =
      let jsonRowExtr :: Extractor
jsonRowExtr =
            (SQLExp -> Maybe ColumnAlias -> Extractor)
-> Maybe ColumnAlias -> SQLExp -> Extractor
forall a b c. (a -> b -> c) -> b -> a -> c
flip SQLExp -> Maybe ColumnAlias -> Extractor
S.Extractor (ColumnAlias -> Maybe ColumnAlias
forall a. a -> Maybe a
Just ColumnAlias
alias)
              (SQLExp -> Extractor) -> SQLExp -> Extractor
forall a b. (a -> b) -> a -> b
$ TableIdentifier -> ColumnAlias -> SQLExp
forall b. IsIdentifier b => TableIdentifier -> b -> SQLExp
S.mkQIdenExp TableIdentifier
unnestTableIdentifier ColumnAlias
alias
          obExtrs :: [Extractor]
obExtrs = ((Identifier -> Extractor) -> [Identifier] -> [Extractor])
-> [Identifier] -> (Identifier -> Extractor) -> [Extractor]
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Identifier -> Extractor) -> [Identifier] -> [Extractor]
forall a b. (a -> b) -> [a] -> [b]
map [Identifier]
newOBAliases ((Identifier -> Extractor) -> [Extractor])
-> (Identifier -> Extractor) -> [Extractor]
forall a b. (a -> b) -> a -> b
$ \Identifier
a ->
            SQLExp -> Maybe ColumnAlias -> Extractor
S.Extractor (TableIdentifier -> Identifier -> SQLExp
forall b. IsIdentifier b => TableIdentifier -> b -> SQLExp
S.mkQIdenExp TableIdentifier
unnestTableIdentifier Identifier
a) (Maybe ColumnAlias -> Extractor) -> Maybe ColumnAlias -> Extractor
forall a b. (a -> b) -> a -> b
$ ColumnAlias -> Maybe ColumnAlias
forall a. a -> Maybe a
Just (ColumnAlias -> Maybe ColumnAlias)
-> ColumnAlias -> Maybe ColumnAlias
forall a b. (a -> b) -> a -> b
$ Identifier -> ColumnAlias
forall a. IsIdentifier a => a -> ColumnAlias
S.toColumnAlias Identifier
a
       in Select
S.mkSelect
            { selExtr :: [Extractor]
S.selExtr = Extractor
jsonRowExtr Extractor -> [Extractor] -> [Extractor]
forall a. a -> [a] -> [a]
: [Extractor]
obExtrs,
              selFrom :: Maybe FromExp
S.selFrom = FromExp -> Maybe FromExp
forall a. a -> Maybe a
Just (FromExp -> Maybe FromExp) -> FromExp -> Maybe FromExp
forall a b. (a -> b) -> a -> b
$ [FromItem] -> FromExp
S.FromExp ([FromItem] -> FromExp) -> [FromItem] -> FromExp
forall a b. (a -> b) -> a -> b
$ FromItem -> [FromItem]
forall a. a -> [a]
forall (f :: * -> *) a. Applicative f => a -> f a
pure FromItem
unnestFromItem,
              selLimit :: Maybe LimitExp
S.selLimit = LimitExp -> Maybe LimitExp
forall a. a -> Maybe a
Just (LimitExp -> Maybe LimitExp) -> LimitExp -> Maybe LimitExp
forall a b. (a -> b) -> a -> b
$ SQLExp -> LimitExp
S.LimitExp (SQLExp -> LimitExp) -> SQLExp -> LimitExp
forall a b. (a -> b) -> a -> b
$ Int -> SQLExp
S.intToSQLExp Int
limit,
              selOrderBy :: Maybe OrderByExp
S.selOrderBy = Maybe OrderByExp
newOrderBy
            }

    unnestFromItem :: FromItem
unnestFromItem =
      let arrayAggItems :: [SQLExp]
arrayAggItems = ((SQLExp -> SQLExp) -> [SQLExp] -> [SQLExp])
-> [SQLExp] -> (SQLExp -> SQLExp) -> [SQLExp]
forall a b c. (a -> b -> c) -> b -> a -> c
flip (SQLExp -> SQLExp) -> [SQLExp] -> [SQLExp]
forall a b. (a -> b) -> [a] -> [b]
map (SQLExp
rowIdenExp SQLExp -> [SQLExp] -> [SQLExp]
forall a. a -> [a] -> [a]
: [SQLExp]
obCols)
            ((SQLExp -> SQLExp) -> [SQLExp]) -> (SQLExp -> SQLExp) -> [SQLExp]
forall a b. (a -> b) -> a -> b
$ \SQLExp
s -> Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"array_agg" [SQLExp
s] Maybe OrderByExp
forall a. Maybe a
Nothing
       in [SQLExp] -> TableAlias -> [ColumnAlias] -> FromItem
S.FIUnnest [SQLExp]
arrayAggItems (TableAlias -> TableAlias
forall a. IsIdentifier a => a -> TableAlias
S.toTableAlias TableAlias
unnestTable)
            ([ColumnAlias] -> FromItem) -> [ColumnAlias] -> FromItem
forall a b. (a -> b) -> a -> b
$ ColumnAlias
alias
            ColumnAlias -> [ColumnAlias] -> [ColumnAlias]
forall a. a -> [a] -> [a]
: (Identifier -> ColumnAlias) -> [Identifier] -> [ColumnAlias]
forall a b. (a -> b) -> [a] -> [b]
map Identifier -> ColumnAlias
forall a. IsIdentifier a => a -> ColumnAlias
S.toColumnAlias [Identifier]
newOBAliases

    newOrderBy :: Maybe OrderByExp
newOrderBy = NonEmpty OrderByItem -> OrderByExp
S.OrderByExp (NonEmpty OrderByItem -> OrderByExp)
-> Maybe (NonEmpty OrderByItem) -> Maybe OrderByExp
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [OrderByItem] -> Maybe (NonEmpty OrderByItem)
forall a. [a] -> Maybe (NonEmpty a)
NE.nonEmpty [OrderByItem]
newOBItems

    ([OrderByItem]
newOBItems, [SQLExp]
obCols, [Identifier]
newOBAliases) = ([OrderByItem], [SQLExp], [Identifier])
-> (OrderByExp -> ([OrderByItem], [SQLExp], [Identifier]))
-> Maybe OrderByExp
-> ([OrderByItem], [SQLExp], [Identifier])
forall b a. b -> (a -> b) -> Maybe a -> b
maybe ([], [], []) OrderByExp -> ([OrderByItem], [SQLExp], [Identifier])
transformOrderBy Maybe OrderByExp
ordBy
    transformOrderBy :: OrderByExp -> ([OrderByItem], [SQLExp], [Identifier])
transformOrderBy (S.OrderByExp NonEmpty OrderByItem
l) = [(OrderByItem, SQLExp, Identifier)]
-> ([OrderByItem], [SQLExp], [Identifier])
forall a b c. [(a, b, c)] -> ([a], [b], [c])
unzip3
      ([(OrderByItem, SQLExp, Identifier)]
 -> ([OrderByItem], [SQLExp], [Identifier]))
-> [(OrderByItem, SQLExp, Identifier)]
-> ([OrderByItem], [SQLExp], [Identifier])
forall a b. (a -> b) -> a -> b
$ (((OrderByItem, Int) -> (OrderByItem, SQLExp, Identifier))
 -> [(OrderByItem, Int)] -> [(OrderByItem, SQLExp, Identifier)])
-> [(OrderByItem, Int)]
-> ((OrderByItem, Int) -> (OrderByItem, SQLExp, Identifier))
-> [(OrderByItem, SQLExp, Identifier)]
forall a b c. (a -> b -> c) -> b -> a -> c
flip ((OrderByItem, Int) -> (OrderByItem, SQLExp, Identifier))
-> [(OrderByItem, Int)] -> [(OrderByItem, SQLExp, Identifier)]
forall a b. (a -> b) -> [a] -> [b]
map ([OrderByItem] -> [Int] -> [(OrderByItem, Int)]
forall a b. [a] -> [b] -> [(a, b)]
zip (NonEmpty OrderByItem -> [OrderByItem]
forall a. NonEmpty a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList NonEmpty OrderByItem
l) [Int
1 ..])
      (((OrderByItem, Int) -> (OrderByItem, SQLExp, Identifier))
 -> [(OrderByItem, SQLExp, Identifier)])
-> ((OrderByItem, Int) -> (OrderByItem, SQLExp, Identifier))
-> [(OrderByItem, SQLExp, Identifier)]
forall a b. (a -> b) -> a -> b
$ \(OrderByItem
obItem, Int
i :: Int) ->
        let iden :: Identifier
iden = Text -> Identifier
Identifier (Text -> Identifier) -> Text -> Identifier
forall a b. (a -> b) -> a -> b
$ Text
"ob_col_" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
tshow Int
i
         in ( OrderByItem
obItem {oExpression :: SQLExp
S.oExpression = Identifier -> SQLExp
S.SEIdentifier Identifier
iden},
              OrderByItem -> SQLExp
S.oExpression OrderByItem
obItem,
              Identifier
iden
            )

asSingleRowExtr :: S.ColumnAlias -> S.SQLExp
asSingleRowExtr :: ColumnAlias -> SQLExp
asSingleRowExtr ColumnAlias
col =
  Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"coalesce" [SQLExp
jsonAgg, Text -> SQLExp
S.SELit Text
"null"] Maybe OrderByExp
forall a. Maybe a
Nothing
  where
    jsonAgg :: SQLExp
jsonAgg =
      SQLOp -> [SQLExp] -> SQLExp
S.SEOpApp
        (Text -> SQLOp
S.SQLOp Text
"->")
        [ Text -> [SQLExp] -> Maybe OrderByExp -> SQLExp
S.SEFnApp Text
"json_agg" [Identifier -> SQLExp
S.SEIdentifier (Identifier -> SQLExp) -> Identifier -> SQLExp
forall a b. (a -> b) -> a -> b
$ ColumnAlias -> Identifier
forall a. IsIdentifier a => a -> Identifier
toIdentifier ColumnAlias
col] Maybe OrderByExp
forall a. Maybe a
Nothing,
          Text -> SQLExp
S.SEUnsafe Text
"0"
        ]

asJsonAggExtr ::
  JsonAggSelect -> S.ColumnAlias -> PermissionLimitSubQuery -> Maybe S.OrderByExp -> S.Extractor
asJsonAggExtr :: JsonAggSelect
-> ColumnAlias
-> PermissionLimitSubQuery
-> Maybe OrderByExp
-> Extractor
asJsonAggExtr JsonAggSelect
jsonAggSelect ColumnAlias
als PermissionLimitSubQuery
permLimitSubQuery Maybe OrderByExp
ordByExpM =
  (SQLExp -> Maybe ColumnAlias -> Extractor)
-> Maybe ColumnAlias -> SQLExp -> Extractor
forall a b c. (a -> b -> c) -> b -> a -> c
flip SQLExp -> Maybe ColumnAlias -> Extractor
S.Extractor (ColumnAlias -> Maybe ColumnAlias
forall a. a -> Maybe a
Just ColumnAlias
als) (SQLExp -> Extractor) -> SQLExp -> Extractor
forall a b. (a -> b) -> a -> b
$ case JsonAggSelect
jsonAggSelect of
    JsonAggSelect
JASMultipleRows -> PermissionLimitSubQuery
-> Maybe OrderByExp -> ColumnAlias -> SQLExp
withJsonAggExtr PermissionLimitSubQuery
permLimitSubQuery Maybe OrderByExp
ordByExpM ColumnAlias
als
    JsonAggSelect
JASSingleObject -> ColumnAlias -> SQLExp
asSingleRowExtr ColumnAlias
als

withColumnOp :: Maybe S.ColumnOp -> S.SQLExp -> S.SQLExp
withColumnOp :: Maybe ColumnOp -> SQLExp -> SQLExp
withColumnOp Maybe ColumnOp
colOpM SQLExp
sqlExp = case Maybe ColumnOp
colOpM of
  Maybe ColumnOp
Nothing -> SQLExp
sqlExp
  Just (S.ColumnOp SQLOp
opText SQLExp
cExp) -> SQLOp -> SQLExp -> SQLExp -> SQLExp
S.mkSQLOpExp SQLOp
opText SQLExp
sqlExp SQLExp
cExp

withRedactionExp ::
  (Backend ('Postgres pgKind)) =>
  S.Qual ->
  AnnRedactionExp ('Postgres pgKind) S.SQLExp ->
  S.SQLExp ->
  S.SQLExp
withRedactionExp :: forall (pgKind :: PostgresKind).
Backend ('Postgres pgKind) =>
Qual
-> AnnRedactionExp ('Postgres pgKind) SQLExp -> SQLExp -> SQLExp
withRedactionExp Qual
tableQual AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp SQLExp
sqlExpression =
  -- Check out [SQL generation for inherited role]
  case AnnRedactionExp ('Postgres pgKind) SQLExp
redactionExp of
    AnnRedactionExp ('Postgres pgKind) SQLExp
NoRedaction -> SQLExp
sqlExpression
    RedactIfFalse GBoolExp
  ('Postgres pgKind) (AnnBoolExpFld ('Postgres pgKind) SQLExp)
gBoolExp ->
      let boolExp :: BoolExp
boolExp =
            BoolExp -> BoolExp
S.simplifyBoolExp
              (BoolExp -> BoolExp) -> BoolExp -> BoolExp
forall a b. (a -> b) -> a -> b
$ Qual
-> GBoolExp
     ('Postgres pgKind)
     (AnnBoolExpFld
        ('Postgres pgKind) (SQLExpression ('Postgres pgKind)))
-> BoolExp
forall (pgKind :: PostgresKind).
Backend ('Postgres pgKind) =>
Qual -> AnnBoolExpSQL ('Postgres pgKind) -> BoolExp
toSQLBoolExp Qual
tableQual
              (GBoolExp
   ('Postgres pgKind)
   (AnnBoolExpFld
      ('Postgres pgKind) (SQLExpression ('Postgres pgKind)))
 -> BoolExp)
-> GBoolExp
     ('Postgres pgKind)
     (AnnBoolExpFld
        ('Postgres pgKind) (SQLExpression ('Postgres pgKind)))
-> BoolExp
forall a b. (a -> b) -> a -> b
$ GBoolExp
  ('Postgres pgKind)
  (AnnBoolExpFld
     ('Postgres pgKind) (SQLExpression ('Postgres pgKind)))
GBoolExp
  ('Postgres pgKind) (AnnBoolExpFld ('Postgres pgKind) SQLExp)
gBoolExp
       in BoolExp -> SQLExp -> SQLExp -> SQLExp
S.SECond BoolExp
boolExp SQLExp
sqlExpression SQLExp
S.SENull