mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-18 13:02:11 +03:00
118 lines
4.8 KiB
Haskell
118 lines
4.8 KiB
Haskell
|
-- | 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
|
||
|
import Language.GraphQL.Draft.Syntax (unName)
|
||
|
|
||
|
fromInsert :: IR.AnnInsert 'MSSQL Void Expression -> Insert
|
||
|
fromInsert IR.AnnInsert {..} =
|
||
|
let IR.AnnIns {..} = _aiData
|
||
|
insertRows = normalizeInsertRows $ map (IR.getInsertColumns) _aiInsObj
|
||
|
insertColumnNames = maybe [] (map fst) $ listToMaybe insertRows
|
||
|
insertValues = map (Values . map snd) insertRows
|
||
|
allColumnNames = map (ColumnName . unName . IR.ciName) _aiTableCols
|
||
|
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 ::
|
||
|
[[(Column 'MSSQL, Expression)]] ->
|
||
|
[[(Column 'MSSQL, Expression)]]
|
||
|
normalizeInsertRows insertRows =
|
||
|
let insertColumns = nubOrd (concatMap (map fst) insertRows)
|
||
|
allColumnsWithDefaultValue = map (,DefaultExpression) $ insertColumns
|
||
|
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
|
||
|
let normalizedInsertRows = normalizeInsertRows $ map (IR.getInsertColumns) insertRows
|
||
|
insertColumnNames = maybe [] (map fst) $ listToMaybe normalizedInsertRows
|
||
|
allColumnNames = map (ColumnName . unName . IR.ciName) allColumns
|
||
|
|
||
|
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
|
||
|
insertRows = normalizeInsertRows $ map IR.getInsertColumns _aiInsObj
|
||
|
insertColumnNames = maybe [] (map fst) $ listToMaybe insertRows
|
||
|
insertValues = map (Values . map snd) insertRows
|
||
|
in InsertValuesIntoTempTable
|
||
|
{ ivittTempTableName = tempTable,
|
||
|
ivittColumns = insertColumnNames,
|
||
|
ivittValues = insertValues
|
||
|
}
|