mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
server/mssql: use temporary tables for insert mutation execution
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3205 GitOrigin-RevId: ff889ce43ed613c283edefa68992eab7c36db18c
This commit is contained in:
parent
02aef27a75
commit
60183df1ea
@ -8,6 +8,8 @@
|
|||||||
### Bug fixes and improvements
|
### Bug fixes and improvements
|
||||||
(Add entries below in the order of server, console, cli, docs, others)
|
(Add entries below in the order of server, console, cli, docs, others)
|
||||||
|
|
||||||
|
- server: extend support for insert mutations to tables without primary key constraint in a MSSQL backend
|
||||||
|
|
||||||
## v2.1.1
|
## v2.1.1
|
||||||
|
|
||||||
- server: implement update mutations for MS SQL Server (closes #7834)
|
- server: implement update mutations for MS SQL Server (closes #7834)
|
||||||
|
@ -92,12 +92,12 @@ Mutation AST is a data type which can be readily translated to SQL Text. We need
|
|||||||
- `data Delete` for Delete
|
- `data Delete` for Delete
|
||||||
|
|
||||||
Specimen SQL for reference: Unlike Postgres, we cannot integrate DML statements in [common table expression](https://docs.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-ver15) of MSSQL. Only SELECTs are allowed in a common table expression.
|
Specimen SQL for reference: Unlike Postgres, we cannot integrate DML statements in [common table expression](https://docs.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-ver15) of MSSQL. Only SELECTs are allowed in a common table expression.
|
||||||
Our proposal is to use [local variables](https://docs.microsoft.com/en-us/sql/t-sql/language-elements/declare-local-variable-transact-sql?view=sql-server-ver15) to capture mutated rows and generated appropriate response using SELECT statement.
|
Our proposal is to use [temporary tables](https://www.sqlservertutorial.net/sql-server-basics/sql-server-temporary-tables/) to capture mutated rows and generated appropriate response using SELECT statement.
|
||||||
|
|
||||||
```mssql
|
```mssql
|
||||||
INSERT INTO test (name, age) OUTPUT INSERTED.<primarykey-column> values ('rakesh', 25)
|
INSERT INTO test (name, age) OUTPUT INSERTED.<column1>, INSERTED.<column2> INTO #temp_table values ('rakesh', 25)
|
||||||
|
|
||||||
WITH some_alias AS (SELECT * FROM test WHERE <primarykey-column> IN (<values fetched from above SQL>))
|
WITH some_alias AS (SELECT * FROM #temp_table)
|
||||||
SELECT (SELECT * FROM some_alias FOR JSON PATH, INCLUDE_NULL_VALUES) AS [returning], count(*) AS [affected_rows] FROM some_alias FOR JSON PATH, WITHOUT_ARRAY_WRAPPER;
|
SELECT (SELECT * FROM some_alias FOR JSON PATH, INCLUDE_NULL_VALUES) AS [returning], count(*) AS [affected_rows] FROM some_alias FOR JSON PATH, WITHOUT_ARRAY_WRAPPER;
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -109,9 +109,9 @@ Like in Postgres, we need to generate expression to evaluate the check condition
|
|||||||
If check constraint is not satisfied we'll raise exception in the Haskell code.
|
If check constraint is not satisfied we'll raise exception in the Haskell code.
|
||||||
|
|
||||||
```mssql
|
```mssql
|
||||||
INSERT INTO test (name, age) OUTPUT INSERTED.<primarykey-column> values ('rakesh', 25)
|
INSERT INTO test (name, age) OUTPUT INSERTED.<column1>, INSERTED.<column2> INTO #temp_table values ('rakesh', 25)
|
||||||
|
|
||||||
WITH alias AS (SELECT * FROM test where id IN (<values-returned-from-above-sql>))
|
WITH alias AS (SELECT * FROM #temp_table)
|
||||||
SELECT (SELECT (SELECT * FROM alias FOR JSON PATH, INCLUDE_NULL_VALUES) AS [returning], count(*) AS [affected_rows] FROM alias FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS [data], SUM(case when (id = 12) then 0 else 1 end) AS [check_constraint] FROM alias ;
|
SELECT (SELECT (SELECT * FROM alias FOR JSON PATH, INCLUDE_NULL_VALUES) AS [returning], count(*) AS [affected_rows] FROM alias FOR JSON PATH, WITHOUT_ARRAY_WRAPPER) AS [data], SUM(case when (id = 12) then 0 else 1 end) AS [check_constraint] FROM alias ;
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -42,7 +42,7 @@ import Data.Proxy
|
|||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import Database.ODBC.SQLServer qualified as ODBC
|
import Database.ODBC.SQLServer qualified as ODBC
|
||||||
import Hasura.Backends.MSSQL.Instances.Types ()
|
import Hasura.Backends.MSSQL.Instances.Types ()
|
||||||
import Hasura.Backends.MSSQL.Types.Insert as TSQL (BackendInsert (..), ExtraColumnInfo (..))
|
import Hasura.Backends.MSSQL.Types.Insert as TSQL (BackendInsert (..))
|
||||||
import Hasura.Backends.MSSQL.Types.Internal as TSQL
|
import Hasura.Backends.MSSQL.Types.Internal as TSQL
|
||||||
import Hasura.Backends.MSSQL.Types.Update as TSQL (BackendUpdate (..), Update (..))
|
import Hasura.Backends.MSSQL.Types.Update as TSQL (BackendUpdate (..), Update (..))
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
@ -1073,8 +1073,10 @@ fromInsert IR.AnnInsert {..} =
|
|||||||
insertRows = normalizeInsertRows _aiData $ map (IR.getInsertColumns) _aiInsObj
|
insertRows = normalizeInsertRows _aiData $ map (IR.getInsertColumns) _aiInsObj
|
||||||
insertColumnNames = maybe [] (map fst) $ listToMaybe insertRows
|
insertColumnNames = maybe [] (map fst) $ listToMaybe insertRows
|
||||||
insertValues = map (Values . map snd) insertRows
|
insertValues = map (Values . map snd) insertRows
|
||||||
primaryKeyColumns = map OutputColumn $ _eciPrimaryKeyColumns $ _biExtraColumnInfo _aiBackendInsert
|
allColumnNames = map (ColumnName . unName . IR.pgiName) _aiTableCols
|
||||||
in Insert _aiTableName insertColumnNames (Output Inserted primaryKeyColumns) insertValues
|
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
|
-- | 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.
|
-- all rows are consistent in column values and order.
|
||||||
@ -1099,7 +1101,7 @@ fromInsert IR.AnnInsert {..} =
|
|||||||
normalizeInsertRows :: IR.AnnIns 'MSSQL [] Expression -> [[(Column 'MSSQL, Expression)]] -> [[(Column 'MSSQL, Expression)]]
|
normalizeInsertRows :: IR.AnnIns 'MSSQL [] Expression -> [[(Column 'MSSQL, Expression)]] -> [[(Column 'MSSQL, Expression)]]
|
||||||
normalizeInsertRows IR.AnnIns {..} insertRows =
|
normalizeInsertRows IR.AnnIns {..} insertRows =
|
||||||
let isIdentityColumn column =
|
let isIdentityColumn column =
|
||||||
IR.pgiColumn column `elem` _eciIdentityColumns (_biExtraColumnInfo _aiBackendInsert)
|
IR.pgiColumn column `elem` _biIdentityColumns _aiBackendInsert
|
||||||
allColumnsWithDefaultValue =
|
allColumnsWithDefaultValue =
|
||||||
-- DEFAULT or NULL are not allowed as explicit identity values.
|
-- DEFAULT or NULL are not allowed as explicit identity values.
|
||||||
map ((,DefaultExpression) . IR.pgiColumn) $ filter (not . isIdentityColumn) _aiTableCols
|
map ((,DefaultExpression) . IR.pgiColumn) $ filter (not . isIdentityColumn) _aiTableCols
|
||||||
|
@ -15,14 +15,13 @@ import Data.HashSet qualified as Set
|
|||||||
import Data.List.NonEmpty qualified as NE
|
import Data.List.NonEmpty qualified as NE
|
||||||
import Data.Text.Extended qualified as T
|
import Data.Text.Extended qualified as T
|
||||||
import Database.MSSQL.Transaction qualified as Tx
|
import Database.MSSQL.Transaction qualified as Tx
|
||||||
import Database.ODBC.Internal qualified as ODBCI
|
|
||||||
import Database.ODBC.SQLServer qualified as ODBC
|
import Database.ODBC.SQLServer qualified as ODBC
|
||||||
import Hasura.Backends.MSSQL.Connection
|
import Hasura.Backends.MSSQL.Connection
|
||||||
import Hasura.Backends.MSSQL.FromIr as TSQL
|
import Hasura.Backends.MSSQL.FromIr as TSQL
|
||||||
import Hasura.Backends.MSSQL.Plan
|
import Hasura.Backends.MSSQL.Plan
|
||||||
import Hasura.Backends.MSSQL.SQL.Value (txtEncodedColVal)
|
import Hasura.Backends.MSSQL.SQL.Value (txtEncodedColVal)
|
||||||
import Hasura.Backends.MSSQL.ToQuery as TQ
|
import Hasura.Backends.MSSQL.ToQuery as TQ
|
||||||
import Hasura.Backends.MSSQL.Types.Insert (BackendInsert (..), ExtraColumnInfo (..))
|
import Hasura.Backends.MSSQL.Types.Insert (BackendInsert (..))
|
||||||
import Hasura.Backends.MSSQL.Types.Internal as TSQL
|
import Hasura.Backends.MSSQL.Types.Internal as TSQL
|
||||||
import Hasura.Backends.MSSQL.Types.Update
|
import Hasura.Backends.MSSQL.Types.Update
|
||||||
import Hasura.Base.Error
|
import Hasura.Base.Error
|
||||||
@ -250,23 +249,26 @@ msDBMutationPlan userInfo stringifyNum sourceName sourceConfig mrf = do
|
|||||||
-- --
|
-- --
|
||||||
-- Step 1: Inserting rows into the table
|
-- Step 1: Inserting rows into the table
|
||||||
-- --
|
-- --
|
||||||
-- -- a. Generate an SQL Insert statement from the GraphQL insert mutation with OUTPUT expression to return
|
-- -- a. Create an empty temporary table with name #inserted to store inserted rows
|
||||||
-- -- primary key column values after insertion.
|
-- --
|
||||||
|
-- -- SELECT column1, column2 INTO #inserted FROM some_table WHERE (1 <> 1)
|
||||||
|
-- --
|
||||||
-- -- b. Before insert, Set IDENTITY_INSERT to ON if any insert row contains atleast one identity column.
|
-- -- b. Before insert, Set IDENTITY_INSERT to ON if any insert row contains atleast one identity column.
|
||||||
-- --
|
-- --
|
||||||
-- -- SET IDENTITY_INSERT some_table ON;
|
-- -- SET IDENTITY_INSERT some_table ON;
|
||||||
-- -- INSERT INTO some_table (column1, column2) OUTPUT INSERTED.pkey_column1, INSERTED.pkey_column2 VALUES (value1, value2), (value3, value4);
|
-- --
|
||||||
|
-- -- c. Generate an SQL Insert statement from the GraphQL insert mutation with OUTPUT expression to fill temporary table with inserted rows
|
||||||
|
-- --
|
||||||
|
-- -- INSERT INTO some_table (column1, column2) OUTPUT INSERTED.column1, INSERTED.column2 INTO #inserted(column1, column2) VALUES (value1, value2), (value3, value4);
|
||||||
-- --
|
-- --
|
||||||
-- Step 2: Generation of the mutation response
|
-- Step 2: Generation of the mutation response
|
||||||
-- --
|
-- --
|
||||||
-- -- An SQL statement is generated and when executed it returns the mutation selection set containing 'affected_rows' and 'returning' field values.
|
-- -- An SQL statement is generated and when executed it returns the mutation selection set containing 'affected_rows' and 'returning' field values.
|
||||||
-- -- The statement is generated with multiple sub select queries explained below:
|
-- -- The statement is generated with multiple sub select queries explained below:
|
||||||
-- --
|
-- --
|
||||||
-- -- a. A SQL Select statement to fetch only inserted rows from the table using primary key column values fetched from
|
-- -- a. A SQL Select statement to fetch only inserted rows from temporary table
|
||||||
-- -- Step 1 in the WHERE clause
|
|
||||||
-- --
|
-- --
|
||||||
-- -- <table_select> :=
|
-- -- <table_select> := SELECT * FROM #inserted
|
||||||
-- -- SELECT * FROM some_table WHERE (pkey_column1 = value1 AND pkey_column2 = value2) OR (pkey_column1 = value3 AND pkey_column2 = value4)
|
|
||||||
-- --
|
-- --
|
||||||
-- -- The above select statement is referred through a common table expression - "WITH [with_alias] AS (<table_select>)"
|
-- -- The above select statement is referred through a common table expression - "WITH [with_alias] AS (<table_select>)"
|
||||||
-- --
|
-- --
|
||||||
@ -303,13 +305,19 @@ executeInsert userInfo stringifyNum sourceConfig annInsert = do
|
|||||||
sessionVariables = _uiSession userInfo
|
sessionVariables = _uiSession userInfo
|
||||||
pool = _mscConnectionPool sourceConfig
|
pool = _mscConnectionPool sourceConfig
|
||||||
table = _aiTableName $ _aiData annInsert
|
table = _aiTableName $ _aiData annInsert
|
||||||
withSelectTableAlias = "t_" <> tableName table
|
|
||||||
withAlias = "with_alias"
|
withAlias = "with_alias"
|
||||||
|
|
||||||
buildInsertTx :: AnnInsert 'MSSQL Void Expression -> Tx.TxET QErr IO EncJSON
|
buildInsertTx :: AnnInsert 'MSSQL Void Expression -> Tx.TxET QErr IO EncJSON
|
||||||
buildInsertTx insert = do
|
buildInsertTx insert = do
|
||||||
let identityColumns = _eciIdentityColumns $ _biExtraColumnInfo $ _aiBackendInsert $ _aiData insert
|
let identityColumns = _biIdentityColumns $ _aiBackendInsert $ _aiData insert
|
||||||
insertColumns = concatMap (map fst . getInsertColumns) $ _aiInsObj $ _aiData insert
|
insertColumns = concatMap (map fst . getInsertColumns) $ _aiInsObj $ _aiData insert
|
||||||
|
createTempTableQuery =
|
||||||
|
toQueryFlat $
|
||||||
|
TQ.fromSelectIntoTempTable $
|
||||||
|
TSQL.toSelectIntoTempTable tempTableNameInserted table (_aiTableCols $ _aiData insert)
|
||||||
|
|
||||||
|
-- Create #inserted temporary table
|
||||||
|
Tx.unitQueryE fromMSSQLTxError createTempTableQuery
|
||||||
|
|
||||||
-- Set identity insert to ON if insert object contains identity columns
|
-- Set identity insert to ON if insert object contains identity columns
|
||||||
when (any (`elem` identityColumns) insertColumns) $
|
when (any (`elem` identityColumns) insertColumns) $
|
||||||
@ -320,53 +328,37 @@ executeInsert userInfo stringifyNum sourceConfig annInsert = do
|
|||||||
|
|
||||||
-- Generate the INSERT query
|
-- Generate the INSERT query
|
||||||
let insertQuery = toQueryFlat $ TQ.fromInsert $ TSQL.fromInsert insert
|
let insertQuery = toQueryFlat $ TQ.fromInsert $ TSQL.fromInsert insert
|
||||||
fromODBCException e =
|
|
||||||
(err400 MSSQLError "insert query exception") {qeInternal = Just (ExtraInternal $ odbcExceptionToJSONValue e)}
|
|
||||||
|
|
||||||
-- Execute the INSERT query and fetch the primary key values
|
|
||||||
primaryKeyValues <- Tx.buildGenericTxE fromODBCException $ \conn -> ODBCI.query conn (ODBC.renderQuery insertQuery)
|
|
||||||
let withSelect = generateWithSelect primaryKeyValues
|
|
||||||
-- WITH [with_alias] AS (select_query)
|
|
||||||
withExpression = With $ pure $ Aliased withSelect withAlias
|
|
||||||
|
|
||||||
|
-- Execute the INSERT query
|
||||||
|
Tx.unitQueryE fromMSSQLTxError insertQuery
|
||||||
mutationOutputSelect <- mkMutationOutputSelect stringifyNum withAlias $ _aiOutput insert
|
mutationOutputSelect <- mkMutationOutputSelect stringifyNum withAlias $ _aiOutput insert
|
||||||
let (checkCondition, _) = _aiCheckCond $ _aiData insert
|
|
||||||
|
|
||||||
-- The check constraint is translated to boolean expression
|
-- The check constraint is translated to boolean expression
|
||||||
|
let checkCondition = fst $ _aiCheckCond $ _aiData insert
|
||||||
checkBoolExp <-
|
checkBoolExp <-
|
||||||
V.runValidate (runFromIr $ runReaderT (fromGBoolExp checkCondition) (EntityAlias withAlias))
|
V.runValidate (runFromIr $ runReaderT (fromGBoolExp checkCondition) (EntityAlias withAlias))
|
||||||
`onLeft` (throw500 . tshow)
|
`onLeft` (throw500 . tshow)
|
||||||
|
|
||||||
|
let withSelect =
|
||||||
|
emptySelect
|
||||||
|
{ selectProjections = [StarProjection],
|
||||||
|
selectFrom = Just $ FromTempTable $ Aliased tempTableNameInserted "inserted_alias"
|
||||||
|
}
|
||||||
-- SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select]
|
-- SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select]
|
||||||
let mutationOutputCheckConstraintSelect = selectMutationOutputAndCheckCondition withAlias mutationOutputSelect checkBoolExp
|
mutationOutputCheckConstraintSelect = selectMutationOutputAndCheckCondition withAlias mutationOutputSelect checkBoolExp
|
||||||
|
|
||||||
-- WITH "with_alias" AS (<table_select>)
|
-- WITH "with_alias" AS (<table_select>)
|
||||||
-- SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select]
|
-- SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select]
|
||||||
finalSelect = mutationOutputCheckConstraintSelect {selectWith = Just withExpression}
|
finalSelect = mutationOutputCheckConstraintSelect {selectWith = Just $ With $ pure $ Aliased withSelect withAlias}
|
||||||
|
|
||||||
|
-- Execute SELECT query to fetch mutation response and check constraint result
|
||||||
(responseText, checkConditionInt) <- Tx.singleRowQueryE fromMSSQLTxError (toQueryFlat $ TQ.fromSelect finalSelect)
|
(responseText, checkConditionInt) <- Tx.singleRowQueryE fromMSSQLTxError (toQueryFlat $ TQ.fromSelect finalSelect)
|
||||||
|
-- Drop the temp table
|
||||||
|
Tx.unitQueryE fromMSSQLTxError $ toQueryFlat $ dropTempTableQuery tempTableNameInserted
|
||||||
|
-- Raise an exception if the check condition is not met
|
||||||
unless (checkConditionInt == (0 :: Int)) $
|
unless (checkConditionInt == (0 :: Int)) $
|
||||||
throw400 PermissionError "check constraint of an insert permission has failed"
|
throw400 PermissionError "check constraint of an insert permission has failed"
|
||||||
pure $ encJFromText responseText
|
pure $ encJFromText responseText
|
||||||
|
|
||||||
columnFieldExpression :: ODBCI.Column -> Expression
|
|
||||||
columnFieldExpression column =
|
|
||||||
ColumnExpression $ TSQL.FieldName (ODBCI.columnName column) withSelectTableAlias
|
|
||||||
|
|
||||||
generateWithSelect :: [[(ODBCI.Column, ODBC.Value)]] -> Select
|
|
||||||
generateWithSelect pkeyValues =
|
|
||||||
emptySelect
|
|
||||||
{ selectProjections = [StarProjection],
|
|
||||||
selectFrom = Just $ FromQualifiedTable $ Aliased table withSelectTableAlias,
|
|
||||||
selectWhere = whereExpression
|
|
||||||
}
|
|
||||||
where
|
|
||||||
-- WHERE (column1 = value1 AND column2 = value2) OR (column1 = value3 AND column2 = value4)
|
|
||||||
whereExpression =
|
|
||||||
let mkColumnEqExpression (column, value) =
|
|
||||||
OpExpression EQ' (columnFieldExpression column) (ValueExpression value)
|
|
||||||
in Where $ pure $ OrExpression $ map (AndExpression . map mkColumnEqExpression) pkeyValues
|
|
||||||
|
|
||||||
-- Delete
|
-- Delete
|
||||||
|
|
||||||
-- | Executes a Delete IR AST and return results as JSON.
|
-- | Executes a Delete IR AST and return results as JSON.
|
||||||
|
@ -9,7 +9,7 @@ import Data.List.NonEmpty qualified as NE
|
|||||||
import Data.Text.Encoding (encodeUtf8)
|
import Data.Text.Encoding (encodeUtf8)
|
||||||
import Data.Text.Extended
|
import Data.Text.Extended
|
||||||
import Database.ODBC.SQLServer qualified as ODBC
|
import Database.ODBC.SQLServer qualified as ODBC
|
||||||
import Hasura.Backends.MSSQL.Types.Insert (BackendInsert (..), ExtraColumnInfo (..))
|
import Hasura.Backends.MSSQL.Types.Insert (BackendInsert (..))
|
||||||
import Hasura.Backends.MSSQL.Types.Internal qualified as MSSQL
|
import Hasura.Backends.MSSQL.Types.Internal qualified as MSSQL
|
||||||
import Hasura.Backends.MSSQL.Types.Update (BackendUpdate (..), UpdateOperator (..))
|
import Hasura.Backends.MSSQL.Types.Update (BackendUpdate (..), UpdateOperator (..))
|
||||||
import Hasura.Base.Error
|
import Hasura.Base.Error
|
||||||
@ -65,10 +65,6 @@ instance BackendSchema 'MSSQL where
|
|||||||
-- SQL literals
|
-- SQL literals
|
||||||
columnDefaultValue = msColumnDefaultValue
|
columnDefaultValue = msColumnDefaultValue
|
||||||
|
|
||||||
-- | MSSQL only supports inserts into tables that have a primary key defined.
|
|
||||||
supportsInserts :: TableInfo 'MSSQL -> Bool
|
|
||||||
supportsInserts = isJust . _tciPrimaryKey . _tiCoreInfo
|
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
-- Top level parsers
|
-- Top level parsers
|
||||||
|
|
||||||
@ -95,25 +91,7 @@ msBuildTableInsertMutationFields ::
|
|||||||
Maybe (SelPermInfo 'MSSQL) ->
|
Maybe (SelPermInfo 'MSSQL) ->
|
||||||
Maybe (UpdPermInfo 'MSSQL) ->
|
Maybe (UpdPermInfo 'MSSQL) ->
|
||||||
m [FieldParser n (AnnInsert 'MSSQL (RemoteRelationshipField UnpreparedValue) (UnpreparedValue 'MSSQL))]
|
m [FieldParser n (AnnInsert 'MSSQL (RemoteRelationshipField UnpreparedValue) (UnpreparedValue 'MSSQL))]
|
||||||
msBuildTableInsertMutationFields
|
msBuildTableInsertMutationFields = GSB.buildTableInsertMutationFields backendInsertParser
|
||||||
sourceName
|
|
||||||
tableName
|
|
||||||
tableInfo
|
|
||||||
gqlName
|
|
||||||
insPerms
|
|
||||||
mSelPerms
|
|
||||||
mUpdPerms
|
|
||||||
| supportsInserts tableInfo =
|
|
||||||
GSB.buildTableInsertMutationFields
|
|
||||||
backendInsertParser
|
|
||||||
sourceName
|
|
||||||
tableName
|
|
||||||
tableInfo
|
|
||||||
gqlName
|
|
||||||
insPerms
|
|
||||||
mSelPerms
|
|
||||||
mUpdPerms
|
|
||||||
| otherwise = return []
|
|
||||||
|
|
||||||
backendInsertParser ::
|
backendInsertParser ::
|
||||||
forall m r n.
|
forall m r n.
|
||||||
@ -130,13 +108,8 @@ backendInsertParser _sourceName tableInfo _selectPerms _updatePerms = do
|
|||||||
pure $ do
|
pure $ do
|
||||||
-- _biIfMatched <- ifMatched
|
-- _biIfMatched <- ifMatched
|
||||||
let _biIfMatched = Nothing
|
let _biIfMatched = Nothing
|
||||||
|
_biIdentityColumns = _tciExtraTableMetadata $ _tiCoreInfo tableInfo
|
||||||
pure $ BackendInsert {..}
|
pure $ BackendInsert {..}
|
||||||
where
|
|
||||||
_biExtraColumnInfo :: ExtraColumnInfo
|
|
||||||
_biExtraColumnInfo =
|
|
||||||
let pkeyColumns = fmap (map pgiColumn . toList . _pkColumns) . _tciPrimaryKey . _tiCoreInfo $ tableInfo
|
|
||||||
identityColumns = _tciExtraTableMetadata $ _tiCoreInfo tableInfo
|
|
||||||
in ExtraColumnInfo (fromMaybe [] pkeyColumns) identityColumns
|
|
||||||
|
|
||||||
msBuildTableUpdateMutationFields ::
|
msBuildTableUpdateMutationFields ::
|
||||||
MonadBuildSchema 'MSSQL r m n =>
|
MonadBuildSchema 'MSSQL r m n =>
|
||||||
|
@ -205,6 +205,7 @@ fromInsert Insert {..} =
|
|||||||
[ "INSERT INTO " <+> fromTableName insertTable,
|
[ "INSERT INTO " <+> fromTableName insertTable,
|
||||||
"(" <+> SepByPrinter ", " (map (fromNameText . columnNameText) insertColumns) <+> ")",
|
"(" <+> SepByPrinter ", " (map (fromNameText . columnNameText) insertColumns) <+> ")",
|
||||||
fromInsertOutput insertOutput,
|
fromInsertOutput insertOutput,
|
||||||
|
"INTO " <+> fromTempTable insertTempTable,
|
||||||
"VALUES " <+> SepByPrinter ", " (map fromValues insertValues)
|
"VALUES " <+> SepByPrinter ", " (map fromValues insertValues)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
-- | Types for MSSQL Insert IR.
|
-- | Types for MSSQL Insert IR.
|
||||||
module Hasura.Backends.MSSQL.Types.Insert
|
module Hasura.Backends.MSSQL.Types.Insert
|
||||||
( BackendInsert (..),
|
( BackendInsert (..),
|
||||||
ExtraColumnInfo (..),
|
|
||||||
IfMatched (..),
|
IfMatched (..),
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
@ -16,7 +15,7 @@ import Hasura.SQL.Backend (BackendType (MSSQL))
|
|||||||
|
|
||||||
data BackendInsert v = BackendInsert
|
data BackendInsert v = BackendInsert
|
||||||
{ _biIfMatched :: Maybe (IfMatched v),
|
{ _biIfMatched :: Maybe (IfMatched v),
|
||||||
_biExtraColumnInfo :: ExtraColumnInfo
|
_biIdentityColumns :: [ColumnName]
|
||||||
}
|
}
|
||||||
|
|
||||||
deriving instance Backend 'MSSQL => Functor BackendInsert
|
deriving instance Backend 'MSSQL => Functor BackendInsert
|
||||||
@ -25,11 +24,6 @@ deriving instance Backend 'MSSQL => Foldable BackendInsert
|
|||||||
|
|
||||||
deriving instance Backend 'MSSQL => Traversable BackendInsert
|
deriving instance Backend 'MSSQL => Traversable BackendInsert
|
||||||
|
|
||||||
data ExtraColumnInfo = ExtraColumnInfo
|
|
||||||
{ _eciPrimaryKeyColumns :: ![ColumnName],
|
|
||||||
_eciIdentityColumns :: ![ColumnName]
|
|
||||||
}
|
|
||||||
|
|
||||||
-- | The IR data representing an @if_matched@ clause, which handles upserts.
|
-- | The IR data representing an @if_matched@ clause, which handles upserts.
|
||||||
data IfMatched v = IfMatched
|
data IfMatched v = IfMatched
|
||||||
{ _imMatchColumns :: [Column 'MSSQL],
|
{ _imMatchColumns :: [Column 'MSSQL],
|
||||||
|
@ -70,6 +70,7 @@ module Hasura.Backends.MSSQL.Types.Internal
|
|||||||
scalarTypeDBName,
|
scalarTypeDBName,
|
||||||
snakeCaseTableName,
|
snakeCaseTableName,
|
||||||
stringTypes,
|
stringTypes,
|
||||||
|
tempTableNameInserted,
|
||||||
tempTableNameDeleted,
|
tempTableNameDeleted,
|
||||||
tempTableNameUpdated,
|
tempTableNameUpdated,
|
||||||
)
|
)
|
||||||
@ -180,10 +181,11 @@ type InsertOutput = Output Inserted
|
|||||||
newtype Values = Values [Expression]
|
newtype Values = Values [Expression]
|
||||||
|
|
||||||
data Insert = Insert
|
data Insert = Insert
|
||||||
{ insertTable :: !TableName,
|
{ insertTable :: TableName,
|
||||||
insertColumns :: ![ColumnName],
|
insertColumns :: [ColumnName],
|
||||||
insertOutput :: !InsertOutput,
|
insertOutput :: InsertOutput,
|
||||||
insertValues :: ![Values]
|
insertTempTable :: TempTable,
|
||||||
|
insertValues :: [Values]
|
||||||
}
|
}
|
||||||
|
|
||||||
data SetValue
|
data SetValue
|
||||||
@ -215,6 +217,9 @@ data SelectIntoTempTable = SelectIntoTempTable
|
|||||||
-- | A temporary table name is prepended by a hash-sign
|
-- | A temporary table name is prepended by a hash-sign
|
||||||
newtype TempTableName = TempTableName Text
|
newtype TempTableName = TempTableName Text
|
||||||
|
|
||||||
|
tempTableNameInserted :: TempTableName
|
||||||
|
tempTableNameInserted = TempTableName "inserted"
|
||||||
|
|
||||||
tempTableNameDeleted :: TempTableName
|
tempTableNameDeleted :: TempTableName
|
||||||
tempTableNameDeleted = TempTableName "deleted"
|
tempTableNameDeleted = TempTableName "deleted"
|
||||||
|
|
||||||
|
@ -13,8 +13,9 @@ query:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
response:
|
response:
|
||||||
errors:
|
data:
|
||||||
- extensions:
|
insert_table_no_pk:
|
||||||
path: $.selectionSet.insert_table_no_pk
|
affected_rows: 1
|
||||||
code: validation-failed
|
returning:
|
||||||
message: "field \"insert_table_no_pk\" not found in type: 'mutation_root'"
|
- id: 1
|
||||||
|
name: Foo
|
@ -867,8 +867,8 @@ class TestGraphQLInsertMSSQL:
|
|||||||
def test_insert_multiple_objects(self, hge_ctx):
|
def test_insert_multiple_objects(self, hge_ctx):
|
||||||
check_query_f(hge_ctx, self.dir() + "/insert_multiple_objects_mssql.yaml")
|
check_query_f(hge_ctx, self.dir() + "/insert_multiple_objects_mssql.yaml")
|
||||||
|
|
||||||
def test_insert_table_no_pk_fail(self, hge_ctx):
|
def test_insert_table_no_pk(self, hge_ctx):
|
||||||
check_query_f(hge_ctx, self.dir() + "/insert_table_no_pk_fail_mssql.yaml")
|
check_query_f(hge_ctx, self.dir() + "/insert_table_no_pk_mssql.yaml")
|
||||||
|
|
||||||
@pytest.mark.parametrize("backend", ['mssql'])
|
@pytest.mark.parametrize("backend", ['mssql'])
|
||||||
@use_mutation_fixtures
|
@use_mutation_fixtures
|
||||||
|
Loading…
Reference in New Issue
Block a user