Safe Haskell | None |
---|---|
Language | Haskell2010 |
Responsible for translating and building an MSSQL execution plan for delete mutations.
This module is used by Hasura.Backends.MSSQL.Instances.Execute.
Synopsis
- executeInsert :: (MonadError QErr m, MonadReader QueryTagsComment m) => UserInfo -> StringifyNumbers -> SourceConfig 'MSSQL -> AnnotatedInsert 'MSSQL Void (UnpreparedValue 'MSSQL) -> m (ExceptT QErr IO EncJSON)
- buildInsertTx :: TableName -> Text -> StringifyNumbers -> AnnotatedInsert 'MSSQL Void Expression -> QueryTagsComment -> TxET QErr IO EncJSON
- buildUpsertTx :: TableName -> AnnotatedInsert 'MSSQL Void Expression -> IfMatched Expression -> QueryTagsComment -> TxET QErr IO ()
- buildInsertResponseTx :: StringifyNumbers -> Text -> AnnotatedInsert 'MSSQL Void Expression -> QueryTagsComment -> TxET QErr IO (Text, Int)
Documentation
executeInsert :: (MonadError QErr m, MonadReader QueryTagsComment m) => UserInfo -> StringifyNumbers -> SourceConfig 'MSSQL -> AnnotatedInsert 'MSSQL Void (UnpreparedValue 'MSSQL) -> m (ExceptT QErr IO EncJSON) Source #
Execute and insert/upsert mutation against MS SQL Server.
See the documentation for buildInsertTx
to see how it's done.
buildInsertTx :: TableName -> Text -> StringifyNumbers -> AnnotatedInsert 'MSSQL Void Expression -> QueryTagsComment -> TxET QErr IO EncJSON Source #
Translates an IR Insert/upsert mutation description to SQL and builds a corresponding transaction to run against MS SQL Server.
Execution of a MSSQL insert mutation broadly involves two steps.
insert_table(objects: [ {column1: value1, column2: value2}, {column1: value3, column2: value4} ], if_matched: {match_columns: [column1], update_columns: [column2]} # Optional field to enable upserting ){ affected_rows returning { column1 column2 } }
Step 1: Inserting rows into the table
a. Create an empty temporary table with name #inserted to store affected rows (for the response)
SELECT column1, column2 INTO #inserted FROM some_table WHERE (1 <> 1) UNION ALL SELECT column1, column2 FROM some_table WHERE (1 <> 1);
b. If if_matched
is found: Use MERGE statment to perform upsert
b.1 Use #values temporary table to store input object values
SELECT column1, column2 INTO #values FROM some_table WHERE (1 <> 1)
b.2 Insert input object values into the temporary table
INSERT INTO #values (column1, column2) VALUES (value1, value2), (value3, value4)
b.3 Generate an SQL Merge statement to perform either update or insert (upsert) to the table
MERGE some_table AS [target] USING (SELECT column1, column2 from #values) AS [source](column1, column2) ON ([target].column1 = [source].column1) WHEN MATCHED THEN UPDATE SET [column2] = [source].[column2] WHEN NOT MATCHED THEN INSERT (column1, column2) VALUES ([source].column1, [source].column2) OUTPUT INSERTED.column1, INSERTED.column2 INTO #inserted(column1, column2)
NOTE: In MERGE
statement, we use SELECT query from a temporary table
as source but not VALUES
expression
because, we can't use DEFAULT
expression (for missing columns in objects
field) in VALUES
expression.
else: Generate an SQL Insert statement from the GraphQL insert mutation with OUTPUT expression to fill #inserted
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
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:
a. A SQL Select statement to fetch only inserted rows from temporary table
<table_select> := SELECT * FROM #inserted
The above select statement is referred through a common table expression - WITH [with_alias] AS (table_select)
b. The affected_rows
field value is obtained by using COUNT
aggregation and the returning
field selection set is translated to
a SQL select statement using mkSQLSelect
.
<mutation_output_select> := SELECT (SELECT COUNT(*) FROM [with_alias]) AS [affected_rows], (select_from_returning) AS [returning] FOR JSON PATH, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER
c. Evaluate the check constraint using CASE
expression. We use SUM
aggregation to check if any inserted row has failed the check constraint.
<check_constraint_select> := SELECT SUM([check_sub_query].[check_evaluation]) FROM ( SELECT (CASE WHEN <check_boolean_expression> THEN 0 ELSE 1 END) AS [check_evaluation] FROM [with_alias] ) AS [check_sub_query]
d. The final select statement look like
WITH "with_alias" AS (<table_select>) SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select]
When executed, the above statement returns a single row with mutation response as a string value and check constraint result as an integer value.
buildUpsertTx :: TableName -> AnnotatedInsert 'MSSQL Void Expression -> IfMatched Expression -> QueryTagsComment -> TxET QErr IO () Source #
Translates an IR IfMatched clause to SQL and builds a corresponding transaction to run against MS SQL Server.
We do this in 2 steps:
- Create a temporary table called
#values
which will hold the values the user want to insert, and insert the values into it - Build an run a
MERGE
statement to either insert or upsert the values from the temporary table#values
into the original table, and output the affected rows into another temporary table called#inserted
which will be used to build a "response" for the user.
Should be used as part of a bigger transaction in buildInsertTx
.
buildInsertResponseTx :: StringifyNumbers -> Text -> AnnotatedInsert 'MSSQL Void Expression -> QueryTagsComment -> TxET QErr IO (Text, Int) Source #
Builds a response to the user using the values in the temporary table named #inserted.