2022-03-10 13:33:55 +03:00
|
|
|
-- | This module defines the translation functions for insert and upsert
|
|
|
|
-- mutations.
|
|
|
|
module Hasura.Backends.MSSQL.FromIr.Insert
|
|
|
|
( fromInsert,
|
|
|
|
toMerge,
|
|
|
|
toInsertValuesIntoTempTable,
|
|
|
|
)
|
|
|
|
where
|
|
|
|
|
|
|
|
import Data.Containers.ListUtils (nubOrd)
|
|
|
|
import Data.HashMap.Strict qualified as HM
|
|
|
|
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.Column qualified as IR
|
|
|
|
import Hasura.SQL.Backend
|
|
|
|
|
|
|
|
fromInsert :: IR.AnnInsert 'MSSQL Void Expression -> Insert
|
|
|
|
fromInsert IR.AnnInsert {..} =
|
|
|
|
let IR.AnnIns {..} = _aiData
|
2022-03-14 18:34:14 +03:00
|
|
|
insertRows = normalizeInsertRows _aiDefVals $ map IR.getInsertColumns _aiInsObj
|
2022-03-10 13:33:55 +03:00
|
|
|
insertColumnNames = maybe [] (map fst) $ listToMaybe insertRows
|
|
|
|
insertValues = map (Values . map snd) insertRows
|
2022-03-18 13:04:52 +03:00
|
|
|
allColumnNames = map IR.ciColumn _aiTableCols
|
2022-03-10 13:33:55 +03:00
|
|
|
insertOutput = Output Inserted $ map OutputColumn allColumnNames
|
|
|
|
tempTable = TempTable tempTableNameInserted allColumnNames
|
|
|
|
in Insert _aiTableName insertColumnNames insertOutput tempTable 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 ::
|
2022-03-14 18:34:14 +03:00
|
|
|
HM.HashMap (Column 'MSSQL) Expression ->
|
2022-03-10 13:33:55 +03:00
|
|
|
[[(Column 'MSSQL, Expression)]] ->
|
|
|
|
[[(Column 'MSSQL, Expression)]]
|
2022-03-14 18:34:14 +03:00
|
|
|
normalizeInsertRows presets insertRows =
|
|
|
|
let insertColumns = nubOrd (concatMap (map fst) insertRows <> HM.keys presets)
|
|
|
|
allColumnsWithDefaultValue =
|
|
|
|
map (\col -> (col, fromMaybe DefaultExpression $ HM.lookup col presets)) insertColumns
|
2022-03-10 13:33:55 +03:00
|
|
|
addMissingColumns insertRow =
|
|
|
|
HM.toList $ HM.fromList insertRow `HM.union` HM.fromList allColumnsWithDefaultValue
|
|
|
|
sortByColumn = sortBy (\l r -> compare (fst l) (fst r))
|
|
|
|
in map (sortByColumn . addMissingColumns) insertRows
|
|
|
|
|
|
|
|
-- | Construct a MERGE statement from AnnInsert 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 insertRows allColumns IfMatched {..} = do
|
2022-03-14 18:34:14 +03:00
|
|
|
let normalizedInsertRows = normalizeInsertRows _imColumnPresets $ map IR.getInsertColumns insertRows
|
2022-03-10 13:33:55 +03:00
|
|
|
insertColumnNames = maybe [] (map fst) $ listToMaybe normalizedInsertRows
|
2022-03-18 13:04:52 +03:00
|
|
|
allColumnNames = map IR.ciColumn allColumns
|
2022-03-10 13:33:55 +03:00
|
|
|
|
|
|
|
matchConditions <-
|
|
|
|
flip runReaderT (EntityAlias "target") $ -- the table is aliased as "target" in MERGE sql
|
|
|
|
fromGBoolExp _imConditions
|
|
|
|
|
|
|
|
pure $
|
|
|
|
Merge
|
|
|
|
{ mergeTargetTable = tableName,
|
|
|
|
mergeUsing = MergeUsing tempTableNameValues insertColumnNames,
|
|
|
|
mergeOn = MergeOn _imMatchColumns,
|
|
|
|
mergeWhenMatched = MergeWhenMatched _imUpdateColumns matchConditions _imColumnPresets,
|
|
|
|
mergeWhenNotMatched = MergeWhenNotMatched insertColumnNames,
|
|
|
|
mergeInsertOutput = Output Inserted $ map OutputColumn allColumnNames,
|
|
|
|
mergeOutputTempTable = TempTable tempTableNameInserted 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.AnnInsert 'MSSQL Void Expression -> InsertValuesIntoTempTable
|
|
|
|
toInsertValuesIntoTempTable tempTable IR.AnnInsert {..} =
|
|
|
|
let IR.AnnIns {..} = _aiData
|
2022-03-14 18:34:14 +03:00
|
|
|
insertRows = normalizeInsertRows _aiDefVals $ map IR.getInsertColumns _aiInsObj
|
2022-03-10 13:33:55 +03:00
|
|
|
insertColumnNames = maybe [] (map fst) $ listToMaybe insertRows
|
|
|
|
insertValues = map (Values . map snd) insertRows
|
|
|
|
in InsertValuesIntoTempTable
|
|
|
|
{ ivittTempTableName = tempTable,
|
|
|
|
ivittColumns = insertColumnNames,
|
|
|
|
ivittValues = insertValues
|
|
|
|
}
|