-- | This module defines the translation functions for insert and upsert
-- mutations.
module Hasura.Backends.MSSQL.FromIr.Insert
  ( fromInsert,
    toMerge,
    toInsertValuesIntoTempTable,
  )
where

import Data.HashMap.Strict.Extended qualified as HashMap
import Data.HashSet qualified as HS
import Hasura.Backends.MSSQL.FromIr (FromIr)
import Hasura.Backends.MSSQL.FromIr.Constants (tempTableNameInserted, tempTableNameValues)
import Hasura.Backends.MSSQL.FromIr.Expression (fromGBoolExp)
import Hasura.Backends.MSSQL.Instances.Types ()
import Hasura.Backends.MSSQL.Types.Insert (IfMatched (..))
import Hasura.Backends.MSSQL.Types.Internal as TSQL
import Hasura.Prelude
import Hasura.RQL.IR qualified as IR
import Hasura.RQL.Types.BackendType
import Hasura.RQL.Types.Column qualified as IR

fromInsert :: IR.AnnotatedInsert 'MSSQL Void Expression -> Insert
fromInsert :: AnnotatedInsert 'MSSQL Void Expression -> Insert
fromInsert IR.AnnotatedInsert {Bool
Maybe NamingCase
Text
MutationOutputG 'MSSQL Void Expression
MultiObjectInsert 'MSSQL Expression
_aiFieldName :: Text
_aiIsSingle :: Bool
_aiData :: MultiObjectInsert 'MSSQL Expression
_aiOutput :: MutationOutputG 'MSSQL Void Expression
_aiNamingConvention :: Maybe NamingCase
_aiFieldName :: forall (b :: BackendType) r v. AnnotatedInsert b r v -> Text
_aiIsSingle :: forall (b :: BackendType) r v. AnnotatedInsert b r v -> Bool
_aiData :: forall (b :: BackendType) r v.
AnnotatedInsert b r v -> MultiObjectInsert b v
_aiOutput :: forall (b :: BackendType) r v.
AnnotatedInsert b r v -> MutationOutputG b r v
_aiNamingConvention :: forall (b :: BackendType) r v.
AnnotatedInsert b r v -> Maybe NamingCase
..} =
  let IR.AnnotatedInsertData {[AnnotatedInsertRow 'MSSQL Expression]
[ColumnInfo 'MSSQL]
Maybe (NESeq (Column 'MSSQL))
Maybe (ValidateInput ResolvedWebhook)
(AnnBoolExp 'MSSQL Expression,
 Maybe (AnnBoolExp 'MSSQL Expression))
PreSetColsG 'MSSQL Expression
TableName 'MSSQL
ExtraTableMetadata 'MSSQL
BackendInsert 'MSSQL Expression
_aiInsertObject :: [AnnotatedInsertRow 'MSSQL Expression]
_aiTableName :: TableName 'MSSQL
_aiCheckCondition :: (AnnBoolExp 'MSSQL Expression,
 Maybe (AnnBoolExp 'MSSQL Expression))
_aiTableColumns :: [ColumnInfo 'MSSQL]
_aiPrimaryKey :: Maybe (NESeq (Column 'MSSQL))
_aiExtraTableMetadata :: ExtraTableMetadata 'MSSQL
_aiPresetValues :: PreSetColsG 'MSSQL Expression
_aiBackendInsert :: BackendInsert 'MSSQL Expression
_aiValidateInput :: Maybe (ValidateInput ResolvedWebhook)
_aiInsertObject :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> f (AnnotatedInsertRow b v)
_aiTableName :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> TableName b
_aiCheckCondition :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v
-> (AnnBoolExp b v, Maybe (AnnBoolExp b v))
_aiTableColumns :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> [ColumnInfo b]
_aiPrimaryKey :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> Maybe (NESeq (Column b))
_aiExtraTableMetadata :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> ExtraTableMetadata b
_aiPresetValues :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> PreSetColsG b v
_aiBackendInsert :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> BackendInsert b v
_aiValidateInput :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> Maybe (ValidateInput ResolvedWebhook)
..} = MultiObjectInsert 'MSSQL Expression
_aiData
      (HashSet ColumnName
insertColumnNames, [HashMap ColumnName Expression]
insertRows) = HashMap ColumnName Expression
-> [AnnotatedInsertRow 'MSSQL Expression]
-> (HashSet ColumnName, [HashMap ColumnName Expression])
normalizeInsertRows PreSetColsG 'MSSQL Expression
HashMap ColumnName Expression
_aiPresetValues ([AnnotatedInsertRow 'MSSQL Expression]
 -> (HashSet ColumnName, [HashMap ColumnName Expression]))
-> [AnnotatedInsertRow 'MSSQL Expression]
-> (HashSet ColumnName, [HashMap ColumnName Expression])
forall a b. (a -> b) -> a -> b
$ [AnnotatedInsertRow 'MSSQL Expression]
_aiInsertObject
      insertValues :: [Values]
insertValues = (HashMap ColumnName Expression -> Values)
-> [HashMap ColumnName Expression] -> [Values]
forall a b. (a -> b) -> [a] -> [b]
map ([Expression] -> Values
Values ([Expression] -> Values)
-> (HashMap ColumnName Expression -> [Expression])
-> HashMap ColumnName Expression
-> Values
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashMap ColumnName Expression -> [Expression]
forall k v. HashMap k v -> [v]
HashMap.elems) [HashMap ColumnName Expression]
insertRows
      allColumnNames :: [ColumnName]
allColumnNames = (ColumnInfo 'MSSQL -> ColumnName)
-> [ColumnInfo 'MSSQL] -> [ColumnName]
forall a b. (a -> b) -> [a] -> [b]
map ColumnInfo 'MSSQL -> Column 'MSSQL
ColumnInfo 'MSSQL -> ColumnName
forall (b :: BackendType). ColumnInfo b -> Column b
IR.ciColumn [ColumnInfo 'MSSQL]
_aiTableColumns
      insertOutput :: Output Inserted
insertOutput = Inserted -> [OutputColumn] -> Output Inserted
forall t. t -> [OutputColumn] -> Output t
Output Inserted
Inserted ([OutputColumn] -> Output Inserted)
-> [OutputColumn] -> Output Inserted
forall a b. (a -> b) -> a -> b
$ (ColumnName -> OutputColumn) -> [ColumnName] -> [OutputColumn]
forall a b. (a -> b) -> [a] -> [b]
map ColumnName -> OutputColumn
OutputColumn [ColumnName]
allColumnNames
      tempTable :: TempTable
tempTable = TempTableName -> [ColumnName] -> TempTable
TempTable TempTableName
tempTableNameInserted [ColumnName]
allColumnNames
   in TableName
-> [ColumnName]
-> Output Inserted
-> TempTable
-> [Values]
-> Insert
Insert TableName 'MSSQL
TableName
_aiTableName (HashSet ColumnName -> [ColumnName]
forall a. HashSet a -> [a]
HS.toList HashSet ColumnName
insertColumnNames) Output Inserted
insertOutput TempTable
tempTable [Values]
insertValues

-- | Normalize a row by adding missing columns with @DEFAULT@ value and sort by
-- column name to make sure all rows are consistent in column values and order.
--
-- Example: A table "author" is defined as:
--
-- > CREATE TABLE author ([id] INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL, age INTEGER)
--
-- Consider the following mutation:
--
-- > mutation {
-- >   insert_author(
-- >     objects: [{id: 1, name: "Foo", age: 21}, {id: 2, name: "Bar"}]
-- >   ){
-- >     affected_rows
-- >   }
-- > }
--
-- We consider @DEFAULT@ value for @age@ column which is missing in second
-- insert row.
--
-- The corresponding @INSERT@ statement looks like:
--
-- > INSERT INTO author (id, name, age)
-- >   OUTPUT INSERTED.id
-- >   VALUES (1, 'Foo', 21), (2, 'Bar', DEFAULT)
normalizeInsertRows ::
  HashMap.HashMap (Column 'MSSQL) Expression ->
  [IR.AnnotatedInsertRow 'MSSQL Expression] ->
  (HashSet (Column 'MSSQL), [HashMap.HashMap (Column 'MSSQL) Expression])
normalizeInsertRows :: HashMap ColumnName Expression
-> [AnnotatedInsertRow 'MSSQL Expression]
-> (HashSet ColumnName, [HashMap ColumnName Expression])
normalizeInsertRows HashMap ColumnName Expression
presets [AnnotatedInsertRow 'MSSQL Expression]
insertRows =
  Expression
-> [HashMap ColumnName Expression]
-> (HashSet ColumnName, [HashMap ColumnName Expression])
forall a b.
Hashable a =>
b -> [HashMap a b] -> (HashSet a, [HashMap a b])
HashMap.homogenise
    Expression
DefaultExpression
    ((AnnotatedInsertRow 'MSSQL Expression
 -> HashMap ColumnName Expression)
-> [AnnotatedInsertRow 'MSSQL Expression]
-> [HashMap ColumnName Expression]
forall a b. (a -> b) -> [a] -> [b]
map ((HashMap ColumnName Expression
presets HashMap ColumnName Expression
-> HashMap ColumnName Expression -> HashMap ColumnName Expression
forall a. Semigroup a => a -> a -> a
<>) (HashMap ColumnName Expression -> HashMap ColumnName Expression)
-> (AnnotatedInsertRow 'MSSQL Expression
    -> HashMap ColumnName Expression)
-> AnnotatedInsertRow 'MSSQL Expression
-> HashMap ColumnName Expression
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(ColumnName, Expression)] -> HashMap ColumnName Expression
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HashMap.fromList ([(ColumnName, Expression)] -> HashMap ColumnName Expression)
-> (AnnotatedInsertRow 'MSSQL Expression
    -> [(ColumnName, Expression)])
-> AnnotatedInsertRow 'MSSQL Expression
-> HashMap ColumnName Expression
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AnnotatedInsertRow 'MSSQL Expression
-> [(Column 'MSSQL, Expression)]
AnnotatedInsertRow 'MSSQL Expression -> [(ColumnName, Expression)]
forall (b :: BackendType) v.
AnnotatedInsertRow b v -> [(Column b, v)]
IR.getInsertColumns) [AnnotatedInsertRow 'MSSQL Expression]
insertRows)

-- | Construct a MERGE statement from AnnotatedInsert information.
--   A MERGE statement is responsible for actually inserting and/or updating
--   the data in the table.
toMerge ::
  TableName ->
  [IR.AnnotatedInsertRow 'MSSQL Expression] ->
  [IR.ColumnInfo 'MSSQL] ->
  IfMatched Expression ->
  FromIr Merge
toMerge :: TableName
-> [AnnotatedInsertRow 'MSSQL Expression]
-> [ColumnInfo 'MSSQL]
-> IfMatched Expression
-> FromIr Merge
toMerge TableName
tableName [AnnotatedInsertRow 'MSSQL Expression]
insertRows [ColumnInfo 'MSSQL]
allColumns IfMatched {[ColumnName]
HashMap ColumnName Expression
AnnBoolExp 'MSSQL Expression
_imMatchColumns :: [ColumnName]
_imUpdateColumns :: [ColumnName]
_imConditions :: AnnBoolExp 'MSSQL Expression
_imColumnPresets :: HashMap ColumnName Expression
_imMatchColumns :: forall v. IfMatched v -> [ColumnName]
_imUpdateColumns :: forall v. IfMatched v -> [ColumnName]
_imConditions :: forall v. IfMatched v -> AnnBoolExp 'MSSQL v
_imColumnPresets :: forall v. IfMatched v -> HashMap ColumnName v
..} = do
  let insertColumnNames :: [ColumnName]
insertColumnNames =
        HashSet ColumnName -> [ColumnName]
forall a. HashSet a -> [a]
HS.toList
          (HashSet ColumnName -> [ColumnName])
-> HashSet ColumnName -> [ColumnName]
forall a b. (a -> b) -> a -> b
$ HashMap ColumnName Expression -> HashSet ColumnName
forall k a. HashMap k a -> HashSet k
HashMap.keysSet HashMap ColumnName Expression
_imColumnPresets
          HashSet ColumnName -> HashSet ColumnName -> HashSet ColumnName
forall a. Semigroup a => a -> a -> a
<> [HashSet ColumnName] -> HashSet ColumnName
forall a. (Eq a, Hashable a) => [HashSet a] -> HashSet a
HS.unions ((AnnotatedInsertRow 'MSSQL Expression -> HashSet ColumnName)
-> [AnnotatedInsertRow 'MSSQL Expression] -> [HashSet ColumnName]
forall a b. (a -> b) -> [a] -> [b]
map (HashMap ColumnName Expression -> HashSet ColumnName
forall k a. HashMap k a -> HashSet k
HashMap.keysSet (HashMap ColumnName Expression -> HashSet ColumnName)
-> (AnnotatedInsertRow 'MSSQL Expression
    -> HashMap ColumnName Expression)
-> AnnotatedInsertRow 'MSSQL Expression
-> HashSet ColumnName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(ColumnName, Expression)] -> HashMap ColumnName Expression
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HashMap.fromList ([(ColumnName, Expression)] -> HashMap ColumnName Expression)
-> (AnnotatedInsertRow 'MSSQL Expression
    -> [(ColumnName, Expression)])
-> AnnotatedInsertRow 'MSSQL Expression
-> HashMap ColumnName Expression
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AnnotatedInsertRow 'MSSQL Expression
-> [(Column 'MSSQL, Expression)]
AnnotatedInsertRow 'MSSQL Expression -> [(ColumnName, Expression)]
forall (b :: BackendType) v.
AnnotatedInsertRow b v -> [(Column b, v)]
IR.getInsertColumns) [AnnotatedInsertRow 'MSSQL Expression]
insertRows)
      allColumnNames :: [ColumnName]
allColumnNames = (ColumnInfo 'MSSQL -> ColumnName)
-> [ColumnInfo 'MSSQL] -> [ColumnName]
forall a b. (a -> b) -> [a] -> [b]
map ColumnInfo 'MSSQL -> Column 'MSSQL
ColumnInfo 'MSSQL -> ColumnName
forall (b :: BackendType). ColumnInfo b -> Column b
IR.ciColumn [ColumnInfo 'MSSQL]
allColumns

  Expression
matchConditions <-
    (ReaderT EntityAlias FromIr Expression
 -> EntityAlias -> FromIr Expression)
-> EntityAlias
-> ReaderT EntityAlias FromIr Expression
-> FromIr Expression
forall a b c. (a -> b -> c) -> b -> a -> c
flip ReaderT EntityAlias FromIr Expression
-> EntityAlias -> FromIr Expression
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT (Text -> EntityAlias
EntityAlias Text
"target")
      (ReaderT EntityAlias FromIr Expression -> FromIr Expression)
-> ReaderT EntityAlias FromIr Expression -> FromIr Expression
forall a b. (a -> b) -> a -> b
$ AnnBoolExp 'MSSQL Expression
-> ReaderT EntityAlias FromIr Expression
fromGBoolExp AnnBoolExp 'MSSQL Expression
_imConditions -- the table is aliased as "target" in MERGE sql
  Merge -> FromIr Merge
forall a. a -> FromIr a
forall (f :: * -> *) a. Applicative f => a -> f a
pure
    (Merge -> FromIr Merge) -> Merge -> FromIr Merge
forall a b. (a -> b) -> a -> b
$ Merge
      { $sel:mergeTargetTable:Merge :: TableName
mergeTargetTable = TableName
tableName,
        $sel:mergeUsing:Merge :: MergeUsing
mergeUsing = TempTableName -> [ColumnName] -> MergeUsing
MergeUsing TempTableName
tempTableNameValues [ColumnName]
insertColumnNames,
        $sel:mergeOn:Merge :: MergeOn
mergeOn = [ColumnName] -> MergeOn
MergeOn [ColumnName]
_imMatchColumns,
        $sel:mergeWhenMatched:Merge :: MergeWhenMatched
mergeWhenMatched = [ColumnName]
-> Expression -> HashMap ColumnName Expression -> MergeWhenMatched
MergeWhenMatched [ColumnName]
_imUpdateColumns Expression
matchConditions HashMap ColumnName Expression
_imColumnPresets,
        $sel:mergeWhenNotMatched:Merge :: MergeWhenNotMatched
mergeWhenNotMatched = [ColumnName] -> MergeWhenNotMatched
MergeWhenNotMatched [ColumnName]
insertColumnNames,
        $sel:mergeInsertOutput:Merge :: Output Inserted
mergeInsertOutput = Inserted -> [OutputColumn] -> Output Inserted
forall t. t -> [OutputColumn] -> Output t
Output Inserted
Inserted ([OutputColumn] -> Output Inserted)
-> [OutputColumn] -> Output Inserted
forall a b. (a -> b) -> a -> b
$ (ColumnName -> OutputColumn) -> [ColumnName] -> [OutputColumn]
forall a b. (a -> b) -> [a] -> [b]
map ColumnName -> OutputColumn
OutputColumn [ColumnName]
allColumnNames,
        $sel:mergeOutputTempTable:Merge :: TempTable
mergeOutputTempTable = TempTableName -> [ColumnName] -> TempTable
TempTable TempTableName
tempTableNameInserted [ColumnName]
allColumnNames
      }

-- | As part of an INSERT/UPSERT process, insert VALUES into a temporary table.
--   The content of the temporary table will later be inserted into the original table
--   using a MERGE statement.
--
--   We insert the values into a temporary table first in order to replace the missing
--   fields with @DEFAULT@ in @normalizeInsertRows@, and we can't do that in a
--   MERGE statement directly.
toInsertValuesIntoTempTable :: TempTableName -> IR.AnnotatedInsert 'MSSQL Void Expression -> InsertValuesIntoTempTable
toInsertValuesIntoTempTable :: TempTableName
-> AnnotatedInsert 'MSSQL Void Expression
-> InsertValuesIntoTempTable
toInsertValuesIntoTempTable TempTableName
tempTable IR.AnnotatedInsert {Bool
Maybe NamingCase
Text
MutationOutputG 'MSSQL Void Expression
MultiObjectInsert 'MSSQL Expression
_aiFieldName :: forall (b :: BackendType) r v. AnnotatedInsert b r v -> Text
_aiIsSingle :: forall (b :: BackendType) r v. AnnotatedInsert b r v -> Bool
_aiData :: forall (b :: BackendType) r v.
AnnotatedInsert b r v -> MultiObjectInsert b v
_aiOutput :: forall (b :: BackendType) r v.
AnnotatedInsert b r v -> MutationOutputG b r v
_aiNamingConvention :: forall (b :: BackendType) r v.
AnnotatedInsert b r v -> Maybe NamingCase
_aiFieldName :: Text
_aiIsSingle :: Bool
_aiData :: MultiObjectInsert 'MSSQL Expression
_aiOutput :: MutationOutputG 'MSSQL Void Expression
_aiNamingConvention :: Maybe NamingCase
..} =
  let IR.AnnotatedInsertData {[AnnotatedInsertRow 'MSSQL Expression]
[ColumnInfo 'MSSQL]
Maybe (NESeq (Column 'MSSQL))
Maybe (ValidateInput ResolvedWebhook)
(AnnBoolExp 'MSSQL Expression,
 Maybe (AnnBoolExp 'MSSQL Expression))
PreSetColsG 'MSSQL Expression
TableName 'MSSQL
ExtraTableMetadata 'MSSQL
BackendInsert 'MSSQL Expression
_aiInsertObject :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> f (AnnotatedInsertRow b v)
_aiTableName :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> TableName b
_aiCheckCondition :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v
-> (AnnBoolExp b v, Maybe (AnnBoolExp b v))
_aiTableColumns :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> [ColumnInfo b]
_aiPrimaryKey :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> Maybe (NESeq (Column b))
_aiExtraTableMetadata :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> ExtraTableMetadata b
_aiPresetValues :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> PreSetColsG b v
_aiBackendInsert :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> BackendInsert b v
_aiValidateInput :: forall (b :: BackendType) (f :: * -> *) v.
AnnotatedInsertData b f v -> Maybe (ValidateInput ResolvedWebhook)
_aiInsertObject :: [AnnotatedInsertRow 'MSSQL Expression]
_aiTableName :: TableName 'MSSQL
_aiCheckCondition :: (AnnBoolExp 'MSSQL Expression,
 Maybe (AnnBoolExp 'MSSQL Expression))
_aiTableColumns :: [ColumnInfo 'MSSQL]
_aiPrimaryKey :: Maybe (NESeq (Column 'MSSQL))
_aiExtraTableMetadata :: ExtraTableMetadata 'MSSQL
_aiPresetValues :: PreSetColsG 'MSSQL Expression
_aiBackendInsert :: BackendInsert 'MSSQL Expression
_aiValidateInput :: Maybe (ValidateInput ResolvedWebhook)
..} = MultiObjectInsert 'MSSQL Expression
_aiData
      (HashSet ColumnName
insertColumnNames, [HashMap ColumnName Expression]
insertRows) = HashMap ColumnName Expression
-> [AnnotatedInsertRow 'MSSQL Expression]
-> (HashSet ColumnName, [HashMap ColumnName Expression])
normalizeInsertRows PreSetColsG 'MSSQL Expression
HashMap ColumnName Expression
_aiPresetValues [AnnotatedInsertRow 'MSSQL Expression]
_aiInsertObject
      insertValues :: [Values]
insertValues = (HashMap ColumnName Expression -> Values)
-> [HashMap ColumnName Expression] -> [Values]
forall a b. (a -> b) -> [a] -> [b]
map ([Expression] -> Values
Values ([Expression] -> Values)
-> (HashMap ColumnName Expression -> [Expression])
-> HashMap ColumnName Expression
-> Values
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashMap ColumnName Expression -> [Expression]
forall k v. HashMap k v -> [v]
HashMap.elems) [HashMap ColumnName Expression]
insertRows
   in InsertValuesIntoTempTable
        { $sel:ivittTempTableName:InsertValuesIntoTempTable :: TempTableName
ivittTempTableName = TempTableName
tempTable,
          $sel:ivittColumns:InsertValuesIntoTempTable :: [ColumnName]
ivittColumns = HashSet ColumnName -> [ColumnName]
forall a. HashSet a -> [a]
HS.toList HashSet ColumnName
insertColumnNames,
          $sel:ivittValues:InsertValuesIntoTempTable :: [Values]
ivittValues = [Values]
insertValues
        }