-- | Postgres Translate Mutation
--
-- Provide a combinator for generating a Postgres SQL SELECT statement for the
-- selected columns in mutation queries.
--
-- See 'Hasura.Backends.Postgres.Execute.Mutation' and note
-- [Prepared statements in Mutations]
module Hasura.Backends.Postgres.Translate.Mutation
  ( mkSelectExpFromColumnValues,
  )
where

import Data.HashMap.Strict qualified as HashMap
import Data.Text.Extended
import Hasura.Backends.Postgres.SQL.DML qualified as S
import Hasura.Backends.Postgres.SQL.Types
import Hasura.Backends.Postgres.SQL.Value
import Hasura.Backends.Postgres.Types.Column
import Hasura.Base.Error
import Hasura.Prelude
import Hasura.RQL.Types.BackendType
import Hasura.RQL.Types.Column
import Hasura.SQL.Types
import Hasura.Table.Cache

-- | Note:- Using sorted columns is necessary to enable casting the rows returned by VALUES expression to table type.
-- For example, let's consider the table, `CREATE TABLE test (id serial primary key, name text not null, age int)`.
-- The generated values expression should be in order of columns;
-- `SELECT ("row"::table).* VALUES (1, 'Robert', 23) AS "row"`.
mkSelectExpFromColumnValues ::
  forall pgKind m.
  (MonadError QErr m) =>
  QualifiedTable ->
  [ColumnInfo ('Postgres pgKind)] ->
  [ColumnValues ('Postgres pgKind) TxtEncodedVal] ->
  m S.Select
mkSelectExpFromColumnValues :: forall (pgKind :: PostgresKind) (m :: * -> *).
MonadError QErr m =>
QualifiedTable
-> [ColumnInfo ('Postgres pgKind)]
-> [ColumnValues ('Postgres pgKind) TxtEncodedVal]
-> m Select
mkSelectExpFromColumnValues QualifiedTable
qt [ColumnInfo ('Postgres pgKind)]
allCols = \case
  [] -> Select -> m Select
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Select
selNoRows
  [ColumnValues ('Postgres pgKind) TxtEncodedVal]
colVals -> do
    [TupleExp]
tuples <- (HashMap PGCol TxtEncodedVal -> m TupleExp)
-> [HashMap PGCol TxtEncodedVal] -> m [TupleExp]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM HashMap PGCol TxtEncodedVal -> m TupleExp
mkTupsFromColVal [ColumnValues ('Postgres pgKind) TxtEncodedVal]
[HashMap PGCol TxtEncodedVal]
colVals
    let fromItem :: FromItem
fromItem = ValuesExp -> TableAlias -> Maybe [ColumnAlias] -> FromItem
S.FIValues ([TupleExp] -> ValuesExp
S.ValuesExp [TupleExp]
tuples) TableAlias
rowAlias Maybe [ColumnAlias]
forall a. Maybe a
Nothing
    Select -> m Select
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return
      Select
S.mkSelect
        { selExtr :: [Extractor]
S.selExtr = [Extractor
extractor],
          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
fromItem]
        }
  where
    rowAlias :: TableAlias
rowAlias = Text -> TableAlias
S.mkTableAlias Text
"row"
    rowIdentifier :: TableIdentifier
rowIdentifier = TableAlias -> TableIdentifier
S.tableAliasToIdentifier TableAlias
rowAlias
    extractor :: Extractor
extractor = Qual -> Extractor
S.selectStar' (Qual -> Extractor) -> Qual -> Extractor
forall a b. (a -> b) -> a -> b
$ TableIdentifier -> Maybe TypeAnn -> Qual
S.QualifiedIdentifier TableIdentifier
rowIdentifier (Maybe TypeAnn -> Qual) -> Maybe TypeAnn -> Qual
forall a b. (a -> b) -> a -> b
$ TypeAnn -> Maybe TypeAnn
forall a. a -> Maybe a
Just (TypeAnn -> Maybe TypeAnn) -> TypeAnn -> Maybe TypeAnn
forall a b. (a -> b) -> a -> b
$ Text -> TypeAnn
S.TypeAnn (Text -> TypeAnn) -> Text -> TypeAnn
forall a b. (a -> b) -> a -> b
$ QualifiedTable -> Text
forall a. ToSQL a => a -> Text
toSQLTxt QualifiedTable
qt
    sortedCols :: [ColumnInfo ('Postgres pgKind)]
sortedCols = [ColumnInfo ('Postgres pgKind)] -> [ColumnInfo ('Postgres pgKind)]
forall (backend :: BackendType).
[ColumnInfo backend] -> [ColumnInfo backend]
sortCols [ColumnInfo ('Postgres pgKind)]
allCols
    mkTupsFromColVal :: HashMap PGCol TxtEncodedVal -> m TupleExp
mkTupsFromColVal HashMap PGCol TxtEncodedVal
colVal =
      ([SQLExp] -> TupleExp) -> m [SQLExp] -> m TupleExp
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [SQLExp] -> TupleExp
S.TupleExp
        (m [SQLExp] -> m TupleExp) -> m [SQLExp] -> m TupleExp
forall a b. (a -> b) -> a -> b
$ [ColumnInfo ('Postgres pgKind)]
-> (ColumnInfo ('Postgres pgKind) -> m SQLExp) -> m [SQLExp]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [ColumnInfo ('Postgres pgKind)]
sortedCols
        ((ColumnInfo ('Postgres pgKind) -> m SQLExp) -> m [SQLExp])
-> (ColumnInfo ('Postgres pgKind) -> m SQLExp) -> m [SQLExp]
forall a b. (a -> b) -> a -> b
$ \ColumnInfo ('Postgres pgKind)
ci -> do
          let pgCol :: Column ('Postgres pgKind)
pgCol = ColumnInfo ('Postgres pgKind) -> Column ('Postgres pgKind)
forall (b :: BackendType). ColumnInfo b -> Column b
ciColumn ColumnInfo ('Postgres pgKind)
ci
          TxtEncodedVal
val <-
            Maybe TxtEncodedVal -> m TxtEncodedVal -> m TxtEncodedVal
forall (m :: * -> *) a. Applicative m => Maybe a -> m a -> m a
onNothing (PGCol -> HashMap PGCol TxtEncodedVal -> Maybe TxtEncodedVal
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HashMap.lookup Column ('Postgres pgKind)
PGCol
pgCol HashMap PGCol TxtEncodedVal
colVal)
              (m TxtEncodedVal -> m TxtEncodedVal)
-> m TxtEncodedVal -> m TxtEncodedVal
forall a b. (a -> b) -> a -> b
$ Text -> m TxtEncodedVal
forall (m :: * -> *) a. QErrM m => Text -> m a
throw500
              (Text -> m TxtEncodedVal) -> Text -> m TxtEncodedVal
forall a b. (a -> b) -> a -> b
$ Text
"column "
              Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Column ('Postgres pgKind)
PGCol
pgCol
              PGCol -> Text -> Text
forall t. ToTxt t => t -> Text -> Text
<<> Text
" not found in returning values"
          SQLExp -> m SQLExp
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SQLExp -> m SQLExp) -> SQLExp -> m SQLExp
forall a b. (a -> b) -> a -> b
$ ColumnType ('Postgres pgKind) -> TxtEncodedVal -> SQLExp
forall {pgKind :: PostgresKind}.
ColumnType ('Postgres pgKind) -> TxtEncodedVal -> SQLExp
txtEncodedToSQLExp (ColumnInfo ('Postgres pgKind) -> ColumnType ('Postgres pgKind)
forall (b :: BackendType). ColumnInfo b -> ColumnType b
ciType ColumnInfo ('Postgres pgKind)
ci) TxtEncodedVal
val

    selNoRows :: Select
selNoRows =
      Select
S.mkSelect
        { selExtr :: [Extractor]
S.selExtr = [Extractor
S.selectStar],
          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
$ QualifiedTable -> FromExp
S.mkSimpleFromExp QualifiedTable
qt,
          selWhere :: Maybe WhereFrag
S.selWhere = WhereFrag -> Maybe WhereFrag
forall a. a -> Maybe a
Just (WhereFrag -> Maybe WhereFrag) -> WhereFrag -> Maybe WhereFrag
forall a b. (a -> b) -> a -> b
$ BoolExp -> WhereFrag
S.WhereFrag (BoolExp -> WhereFrag) -> BoolExp -> WhereFrag
forall a b. (a -> b) -> a -> b
$ Bool -> BoolExp
S.BELit Bool
False
        }

    txtEncodedToSQLExp :: ColumnType ('Postgres pgKind) -> TxtEncodedVal -> SQLExp
txtEncodedToSQLExp ColumnType ('Postgres pgKind)
colTy = \case
      TxtEncodedVal
TENull -> SQLExp
S.SENull
      TELit Text
textValue ->
        PGScalarType -> SQLExp -> SQLExp
withScalarTypeAnn (ColumnType ('Postgres pgKind) -> PGScalarType
forall (pgKind :: PostgresKind).
ColumnType ('Postgres pgKind) -> PGScalarType
unsafePGColumnToBackend ColumnType ('Postgres pgKind)
colTy) (SQLExp -> SQLExp) -> SQLExp -> SQLExp
forall a b. (a -> b) -> a -> b
$ Text -> SQLExp
S.SELit Text
textValue