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:
Rakesh Emmadi 2021-12-22 16:34:33 +05:30 committed by hasura-bot
parent 02aef27a75
commit 60183df1ea
10 changed files with 69 additions and 99 deletions

View File

@ -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)

View File

@ -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 ;
``` ```

View File

@ -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

View File

@ -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)
-- SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select] let withSelect =
let mutationOutputCheckConstraintSelect = selectMutationOutputAndCheckCondition withAlias mutationOutputSelect checkBoolExp emptySelect
{ selectProjections = [StarProjection],
selectFrom = Just $ FromTempTable $ Aliased tempTableNameInserted "inserted_alias"
}
-- SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select]
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.

View File

@ -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 =>

View File

@ -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)
] ]

View File

@ -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],

View File

@ -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"

View File

@ -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

View File

@ -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