mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
server/mssql: update mutation, SQL generation and execution
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3059 GitOrigin-RevId: 4ed0cbf54ac2a7103cb2b7adc97b2dfdf9994c4f
This commit is contained in:
parent
ae124e2ee8
commit
f00404e0f6
@ -6,6 +6,8 @@
|
||||
### Bug fixes and improvements
|
||||
(Add entries below in the order of server, console, cli, docs, others)
|
||||
|
||||
- server: implement update mutations for MS SQL Server (closes #7834)
|
||||
|
||||
## v2.1.0
|
||||
|
||||
- server: fix issue interpreting urls from environment in the `TestWebhookTransform` endpoint.
|
||||
|
@ -384,6 +384,7 @@ library
|
||||
, Hasura.Backends.MSSQL.Plan
|
||||
, Hasura.Backends.MSSQL.SQL.Value
|
||||
, Hasura.Backends.MSSQL.ToQuery
|
||||
, Hasura.Backends.MSSQL.Types
|
||||
, Hasura.Backends.MSSQL.Types.Insert
|
||||
, Hasura.Backends.MSSQL.Types.Instances
|
||||
, Hasura.Backends.MSSQL.Types.Internal
|
||||
|
@ -29,6 +29,7 @@ module Hasura.Backends.MSSQL.FromIr
|
||||
jsonFieldName,
|
||||
fromInsert,
|
||||
fromDelete,
|
||||
fromUpdate,
|
||||
toSelectIntoTempTable,
|
||||
)
|
||||
where
|
||||
@ -41,8 +42,7 @@ import Data.Proxy
|
||||
import Data.Text qualified as T
|
||||
import Database.ODBC.SQLServer qualified as ODBC
|
||||
import Hasura.Backends.MSSQL.Instances.Types ()
|
||||
import Hasura.Backends.MSSQL.Types.Insert as TSQL (MSSQLExtraInsertData (..))
|
||||
import Hasura.Backends.MSSQL.Types.Internal as TSQL
|
||||
import Hasura.Backends.MSSQL.Types as TSQL
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR qualified as IR
|
||||
import Hasura.RQL.Types.Column qualified as IR
|
||||
@ -1132,6 +1132,30 @@ fromDelete (IR.AnnDel tableName (permFilter, whereClause) _ allColumns) = do
|
||||
)
|
||||
tableAlias
|
||||
|
||||
-- | Convert IR AST representing update into MSSQL AST representing an update statement
|
||||
fromUpdate :: IR.AnnotatedUpdate 'MSSQL -> FromIr Update
|
||||
fromUpdate (IR.AnnotatedUpdateG tableName (permFilter, whereClause) _ backendUpdate _ allColumns) = do
|
||||
tableAlias <- fromTableName tableName
|
||||
runReaderT
|
||||
( do
|
||||
permissionsFilter <- fromGBoolExp permFilter
|
||||
whereExpression <- fromGBoolExp whereClause
|
||||
let columnNames = map (ColumnName . unName . IR.pgiName) allColumns
|
||||
pure
|
||||
Update
|
||||
{ updateTable =
|
||||
Aliased
|
||||
{ aliasedAlias = entityAliasText tableAlias,
|
||||
aliasedThing = tableName
|
||||
},
|
||||
updateSet = updateOperations backendUpdate,
|
||||
updateOutput = Output Inserted (map OutputColumn columnNames),
|
||||
updateTempTable = TempTable tempTableNameUpdated columnNames,
|
||||
updateWhere = Where [permissionsFilter, whereExpression]
|
||||
}
|
||||
)
|
||||
tableAlias
|
||||
|
||||
-- | Create a temporary table with the same schema as the given table.
|
||||
toSelectIntoTempTable :: TempTableName -> TableName -> [IR.ColumnInfo 'MSSQL] -> SelectIntoTempTable
|
||||
toSelectIntoTempTable tempTableName fromTable allColumns = do
|
||||
|
@ -24,6 +24,7 @@ import Hasura.Backends.MSSQL.SQL.Value (txtEncodedColVal)
|
||||
import Hasura.Backends.MSSQL.ToQuery as TQ
|
||||
import Hasura.Backends.MSSQL.Types.Insert (MSSQLExtraInsertData (..))
|
||||
import Hasura.Backends.MSSQL.Types.Internal as TSQL
|
||||
import Hasura.Backends.MSSQL.Types.Update
|
||||
import Hasura.Base.Error
|
||||
import Hasura.EncJSON
|
||||
import Hasura.GraphQL.Execute.Backend
|
||||
@ -228,7 +229,7 @@ msDBMutationPlan userInfo stringifyNum sourceName sourceConfig mrf = do
|
||||
go <$> case mrf of
|
||||
MDBInsert annInsert -> executeInsert userInfo stringifyNum sourceConfig annInsert
|
||||
MDBDelete annDelete -> executeDelete userInfo stringifyNum sourceConfig annDelete
|
||||
MDBUpdate _annUpdate -> throw400 NotSupported "update mutations are not supported in MS SQL Server"
|
||||
MDBUpdate annUpdate -> executeUpdate userInfo stringifyNum sourceConfig annUpdate
|
||||
MDBFunction {} -> throw400 NotSupported "function mutations are not supported in MSSQL"
|
||||
where
|
||||
go v = DBStepInfo @'MSSQL sourceName sourceConfig Nothing v
|
||||
@ -337,7 +338,7 @@ executeInsert userInfo stringifyNum sourceConfig annInsert = do
|
||||
`onLeft` (throw500 . tshow)
|
||||
|
||||
-- SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select]
|
||||
let mutationOutputCheckConstraintSelect = selectMutationOutputAndCheckCondition mutationOutputSelect checkBoolExp
|
||||
let mutationOutputCheckConstraintSelect = selectMutationOutputAndCheckCondition withAlias mutationOutputSelect checkBoolExp
|
||||
|
||||
-- WITH "with_alias" AS (<table_select>)
|
||||
-- SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select]
|
||||
@ -366,25 +367,6 @@ executeInsert userInfo stringifyNum sourceConfig annInsert = do
|
||||
OpExpression EQ' (columnFieldExpression column) (ValueExpression value)
|
||||
in Where $ pure $ OrExpression $ map (AndExpression . map mkColumnEqExpression) pkeyValues
|
||||
|
||||
generateCheckConstraintSelect :: Expression -> Select
|
||||
generateCheckConstraintSelect checkBoolExp =
|
||||
let zeroValue = ValueExpression $ ODBC.IntValue 0
|
||||
oneValue = ValueExpression $ ODBC.IntValue 1
|
||||
caseExpression = ConditionalExpression checkBoolExp zeroValue oneValue
|
||||
sumAggregate = OpAggregate "SUM" [caseExpression]
|
||||
in emptySelect
|
||||
{ selectProjections = [AggregateProjection (Aliased sumAggregate "check")],
|
||||
selectFrom = Just $ TSQL.FromIdentifier withAlias
|
||||
}
|
||||
|
||||
selectMutationOutputAndCheckCondition :: Select -> Expression -> Select
|
||||
selectMutationOutputAndCheckCondition mutationOutputSelect checkBoolExp =
|
||||
let mutationOutputProjection =
|
||||
ExpressionProjection $ Aliased (SelectExpression mutationOutputSelect) "mutation_response"
|
||||
checkConstraintProjection =
|
||||
ExpressionProjection $ Aliased (SelectExpression (generateCheckConstraintSelect checkBoolExp)) "check_constraint_select"
|
||||
in emptySelect {selectProjections = [mutationOutputProjection, checkConstraintProjection]}
|
||||
|
||||
-- Delete
|
||||
|
||||
-- | Executes a Delete IR AST and return results as JSON.
|
||||
@ -443,6 +425,77 @@ buildDeleteTx deleteOperation stringifyNum = do
|
||||
-- Execute SELECT query and fetch mutation response
|
||||
encJFromText <$> Tx.singleRowQueryE fromMSSQLTxError mutationOutputSelectQuery
|
||||
|
||||
-- | Executes an Update IR AST and return results as JSON.
|
||||
executeUpdate ::
|
||||
MonadError QErr m =>
|
||||
UserInfo ->
|
||||
Bool ->
|
||||
SourceConfig 'MSSQL ->
|
||||
AnnotatedUpdateG 'MSSQL Void (UnpreparedValue 'MSSQL) ->
|
||||
m (ExceptT QErr IO EncJSON)
|
||||
executeUpdate userInfo stringifyNum sourceConfig updateOperation = do
|
||||
preparedUpdate <- traverse (prepareValueQuery $ _uiSession userInfo) updateOperation
|
||||
let pool = _mscConnectionPool sourceConfig
|
||||
if null $ updateOperations . _auBackend $ updateOperation
|
||||
then pure $ pure $ IR.buildEmptyMutResp $ _auOutput preparedUpdate
|
||||
else pure $ withMSSQLPool pool $ Tx.runTxE fromMSSQLTxError (buildUpdateTx preparedUpdate stringifyNum)
|
||||
|
||||
-- | Converts an Update IR AST to a transaction of three update sql statements.
|
||||
--
|
||||
-- A GraphQL update mutation does two things:
|
||||
--
|
||||
-- 1. Update rows in a table according to some predicate
|
||||
-- 2. (Potentially) returns the updated rows (including relationships) as JSON
|
||||
--
|
||||
-- In order to complete these 2 things we need 3 SQL statements:
|
||||
--
|
||||
-- 1. SELECT INTO <temp_table> WHERE <false> - creates a temporary table
|
||||
-- with the same schema as the original table in which we'll store the updated rows
|
||||
-- from the table we are deleting
|
||||
-- 2. UPDATE SET FROM with OUTPUT - updates the rows from the table and inserts the
|
||||
-- updated rows to the temporary table from (1)
|
||||
-- 3. SELECT - constructs the @returning@ query from the temporary table, including
|
||||
-- relationships with other tables.
|
||||
buildUpdateTx ::
|
||||
AnnotatedUpdate 'MSSQL ->
|
||||
Bool ->
|
||||
Tx.TxET QErr IO EncJSON
|
||||
buildUpdateTx updateOperation stringifyNum = do
|
||||
let withAlias = "with_alias"
|
||||
createTempTableQuery =
|
||||
toQueryFlat $
|
||||
TQ.fromSelectIntoTempTable $
|
||||
TSQL.toSelectIntoTempTable tempTableNameUpdated (_auTable updateOperation) (_auAllCols updateOperation)
|
||||
-- Create a temp table
|
||||
Tx.unitQueryE fromMSSQLTxError createTempTableQuery
|
||||
let updateQuery = TQ.fromUpdate <$> TSQL.fromUpdate updateOperation
|
||||
updateQueryValidated <- toQueryFlat <$> V.runValidate (runFromIr updateQuery) `onLeft` (throw500 . tshow)
|
||||
-- Execute UPDATE statement
|
||||
Tx.unitQueryE fromMSSQLTxError updateQueryValidated
|
||||
mutationOutputSelect <- mkMutationOutputSelect stringifyNum withAlias $ _auOutput updateOperation
|
||||
let checkCondition = _auCheck updateOperation
|
||||
-- The check constraint is translated to boolean expression
|
||||
checkBoolExp <-
|
||||
V.runValidate (runFromIr $ runReaderT (fromGBoolExp checkCondition) (EntityAlias withAlias))
|
||||
`onLeft` (throw500 . tshow)
|
||||
|
||||
let withSelect =
|
||||
emptySelect
|
||||
{ selectProjections = [StarProjection],
|
||||
selectFrom = Just $ FromTempTable $ Aliased tempTableNameUpdated "updated_alias"
|
||||
}
|
||||
mutationOutputCheckConstraintSelect = selectMutationOutputAndCheckCondition withAlias mutationOutputSelect checkBoolExp
|
||||
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)
|
||||
-- Drop the temp table
|
||||
Tx.unitQueryE fromMSSQLTxError $ toQueryFlat $ dropTempTableQuery tempTableNameUpdated
|
||||
-- Raise an exception if the check condition is not met
|
||||
unless (checkConditionInt == (0 :: Int)) $
|
||||
throw400 PermissionError "check constraint of an update permission has failed"
|
||||
pure $ encJFromText responseText
|
||||
|
||||
-- | Generate a SQL SELECT statement which outputs the mutation response
|
||||
--
|
||||
-- For multi row inserts:
|
||||
@ -467,6 +520,40 @@ mkMutationOutputSelect stringifyNum withAlias = \case
|
||||
pure emptySelect {selectFor = forJson, selectProjections = projections}
|
||||
IR.MOutSinglerowObject singleRowField -> mkSelect stringifyNum withAlias JASSingleObject singleRowField
|
||||
|
||||
-- | Generate a SQL SELECT statement which outputs the mutation response and check constraint result
|
||||
--
|
||||
-- The check constraint boolean expression is evaluated on mutated rows in a CASE expression so that
|
||||
-- the int value "0" is returned when check constraint is true otherwise the int value "1" is returned.
|
||||
-- We use "SUM" aggregation on the returned value and if check constraint on any row is not met, the summed
|
||||
-- value will not equal to "0" (always > 1).
|
||||
--
|
||||
-- <check_constraint_select> :=
|
||||
-- SELECT SUM(CASE WHEN <check_boolean_expression> THEN 0 ELSE 1 END) FROM [with_alias]
|
||||
--
|
||||
-- <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
|
||||
--
|
||||
-- SELECT (<mutation_output_select>) AS [mutation_response], (<check_constraint_select>) AS [check_constraint_select]
|
||||
selectMutationOutputAndCheckCondition :: Text -> Select -> Expression -> Select
|
||||
selectMutationOutputAndCheckCondition alias mutationOutputSelect checkBoolExp =
|
||||
let mutationOutputProjection =
|
||||
ExpressionProjection $ Aliased (SelectExpression mutationOutputSelect) "mutation_response"
|
||||
checkConstraintProjection =
|
||||
-- apply ISNULL() to avoid check constraint select statement yielding empty rows
|
||||
ExpressionProjection $
|
||||
Aliased (FunctionExpression "ISNULL" [SelectExpression checkConstraintSelect, ValueExpression (ODBC.IntValue 0)]) "check_constraint_select"
|
||||
in emptySelect {selectProjections = [mutationOutputProjection, checkConstraintProjection]}
|
||||
where
|
||||
checkConstraintSelect =
|
||||
let zeroValue = ValueExpression $ ODBC.IntValue 0
|
||||
oneValue = ValueExpression $ ODBC.IntValue 1
|
||||
caseExpression = ConditionalExpression checkBoolExp zeroValue oneValue
|
||||
sumAggregate = OpAggregate "SUM" [caseExpression]
|
||||
in emptySelect
|
||||
{ selectProjections = [AggregateProjection (Aliased sumAggregate "check")],
|
||||
selectFrom = Just $ TSQL.FromIdentifier alias
|
||||
}
|
||||
|
||||
mkSelect ::
|
||||
MonadError QErr m =>
|
||||
Bool ->
|
||||
|
@ -38,7 +38,7 @@ instance BackendSchema 'MSSQL where
|
||||
buildTableRelayQueryFields = msBuildTableRelayQueryFields
|
||||
buildTableInsertMutationFields = msBuildTableInsertMutationFields
|
||||
buildTableDeleteMutationFields = GSB.buildTableDeleteMutationFields
|
||||
buildTableUpdateMutationFields = \_ _ _ _ _ _ -> return [] -- see _msBuildTableUpdateMutationFields.
|
||||
buildTableUpdateMutationFields = msBuildTableUpdateMutationFields
|
||||
|
||||
buildFunctionQueryFields = msBuildFunctionQueryFields
|
||||
buildFunctionRelayQueryFields = msBuildFunctionRelayQueryFields
|
||||
@ -121,10 +121,7 @@ getExtraInsertData tableInfo =
|
||||
identityColumns = _tciExtraTableMetadata $ _tiCoreInfo tableInfo
|
||||
in MSSQLExtraInsertData (fromMaybe [] pkeyColumns) identityColumns
|
||||
|
||||
-- Replace the instance implementation of 'buildTableUpdateMutationFields' with
|
||||
-- the below when we have an executable implementation of updates, in order to
|
||||
-- enable the update schema.
|
||||
_msBuildTableUpdateMutationFields ::
|
||||
msBuildTableUpdateMutationFields ::
|
||||
MonadBuildSchema 'MSSQL r m n =>
|
||||
SourceName ->
|
||||
TableName 'MSSQL ->
|
||||
@ -133,7 +130,7 @@ _msBuildTableUpdateMutationFields ::
|
||||
UpdPermInfo 'MSSQL ->
|
||||
Maybe (SelPermInfo 'MSSQL) ->
|
||||
m [FieldParser n (AnnotatedUpdateG 'MSSQL (RemoteRelationshipField UnpreparedValue) (UnpreparedValue 'MSSQL))]
|
||||
_msBuildTableUpdateMutationFields =
|
||||
msBuildTableUpdateMutationFields =
|
||||
GSB.buildTableUpdateMutationFields
|
||||
( \ti updPerms ->
|
||||
fmap BackendUpdate
|
||||
|
@ -10,12 +10,15 @@ module Hasura.Backends.MSSQL.ToQuery
|
||||
fromInsert,
|
||||
fromSetIdentityInsert,
|
||||
fromDelete,
|
||||
fromUpdate,
|
||||
fromSelectIntoTempTable,
|
||||
dropTempTableQuery,
|
||||
Printer (..),
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson (ToJSON (..))
|
||||
import Data.HashMap.Strict qualified as HM
|
||||
import Data.List (intersperse)
|
||||
import Data.String
|
||||
import Data.Text qualified as T
|
||||
@ -23,8 +26,7 @@ import Data.Text.Extended qualified as T
|
||||
import Data.Text.Lazy qualified as L
|
||||
import Data.Text.Lazy.Builder qualified as L
|
||||
import Database.ODBC.SQLServer
|
||||
import Hasura.Backends.MSSQL.Types.Instances ()
|
||||
import Hasura.Backends.MSSQL.Types.Internal
|
||||
import Hasura.Backends.MSSQL.Types
|
||||
import Hasura.Prelude hiding (GT, LT)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@ -112,6 +114,11 @@ fromExpression =
|
||||
<+> " ("
|
||||
<+> fromExpression y
|
||||
<+> ")"
|
||||
FunctionExpression function args ->
|
||||
fromString (T.unpack function)
|
||||
<+> "("
|
||||
<+> SepByPrinter ", " (map fromExpression args)
|
||||
<+> ")"
|
||||
ListExpression xs -> SepByPrinter ", " $ fromExpression <$> xs
|
||||
STOpExpression op e str ->
|
||||
"(" <+> fromExpression e <+> ")."
|
||||
@ -184,6 +191,9 @@ fromInsertOutput = fromOutput fromInserted
|
||||
fromDeleteOutput :: DeleteOutput -> Printer
|
||||
fromDeleteOutput = fromOutput fromDeleted
|
||||
|
||||
fromUpdateOutput :: UpdateOutput -> Printer
|
||||
fromUpdateOutput = fromOutput fromInserted
|
||||
|
||||
fromValues :: Values -> Printer
|
||||
fromValues (Values values) =
|
||||
"( " <+> SepByPrinter ", " (map fromExpression values) <+> " )"
|
||||
@ -233,6 +243,42 @@ fromDelete Delete {deleteTable, deleteOutput, deleteTempTable, deleteWhere} =
|
||||
fromWhere deleteWhere
|
||||
]
|
||||
|
||||
-- | Generate an update statement
|
||||
--
|
||||
-- > Update
|
||||
-- > (Aliased (TableName "table" "schema") "alias")
|
||||
-- > (fromList [(ColumnName "name", ValueExpression (TextValue "updated_name"))])
|
||||
-- > (Output Inserted)
|
||||
-- > (TempTable (TempTableName "updated") [ColumnName "id", ColumnName "name"])
|
||||
-- > (Where [OpExpression EQ' (ColumnName "id") (ValueExpression (IntValue 1))])
|
||||
--
|
||||
-- Becomes:
|
||||
--
|
||||
-- > UPDATE [alias] SET [alias].[name] = 'updated_name' OUTPUT INSERTED.[id], INSERTED.[name] INTO
|
||||
-- > #updated([id], [name]) FROM [schema].[table] AS [alias] WHERE (id = 1)
|
||||
fromUpdate :: Update -> Printer
|
||||
fromUpdate Update {..} =
|
||||
SepByPrinter
|
||||
NewlinePrinter
|
||||
[ "UPDATE " <+> fromNameText (aliasedAlias updateTable),
|
||||
fromUpdateSet updateSet,
|
||||
fromUpdateOutput updateOutput,
|
||||
"INTO " <+> fromTempTable updateTempTable,
|
||||
"FROM " <+> fromAliased (fmap fromTableName updateTable),
|
||||
fromWhere updateWhere
|
||||
]
|
||||
where
|
||||
fromUpdateSet :: UpdateSet -> Printer
|
||||
fromUpdateSet setColumns =
|
||||
let updateColumnValue (column, updateOp) =
|
||||
fromColumnName column <+> fromUpdateOperator (fromExpression <$> updateOp)
|
||||
in "SET " <+> SepByPrinter ", " (map updateColumnValue (HM.toList setColumns))
|
||||
|
||||
fromUpdateOperator :: UpdateOperator Printer -> Printer
|
||||
fromUpdateOperator = \case
|
||||
UpdateSet p -> " = " <+> p
|
||||
UpdateInc p -> " += " <+> p
|
||||
|
||||
-- | Converts `SelectIntoTempTable`.
|
||||
--
|
||||
-- > SelectIntoTempTable (TempTableName "deleted") [UnifiedColumn "id" IntegerType, UnifiedColumn "name" TextType] (TableName "table" "schema")
|
||||
@ -281,6 +327,11 @@ fromTempTable :: TempTable -> Printer
|
||||
fromTempTable (TempTable table columns) =
|
||||
fromTempTableName table <+> parens (SepByPrinter ", " (map fromColumnName columns))
|
||||
|
||||
-- | @TempTableName "temp_table" is converted to "DROP TABLE #temp_table"
|
||||
dropTempTableQuery :: TempTableName -> Printer
|
||||
dropTempTableQuery tempTableName =
|
||||
QueryPrinter "DROP TABLE " <+> fromTempTableName tempTableName
|
||||
|
||||
fromSelect :: Select -> Printer
|
||||
fromSelect Select {..} = fmap fromWith selectWith ?<+> wrapFor selectFor result
|
||||
where
|
||||
|
10
server/src-lib/Hasura/Backends/MSSQL/Types.hs
Normal file
10
server/src-lib/Hasura/Backends/MSSQL/Types.hs
Normal file
@ -0,0 +1,10 @@
|
||||
-- | This module exports modules @Hasura.Backends.MSSQL.Types.*@
|
||||
module Hasura.Backends.MSSQL.Types
|
||||
( module M,
|
||||
)
|
||||
where
|
||||
|
||||
import Hasura.Backends.MSSQL.Types.Insert as M
|
||||
import Hasura.Backends.MSSQL.Types.Instances ()
|
||||
import Hasura.Backends.MSSQL.Types.Internal as M
|
||||
import Hasura.Backends.MSSQL.Types.Update as M
|
@ -71,6 +71,7 @@ module Hasura.Backends.MSSQL.Types.Internal
|
||||
snakeCaseTableName,
|
||||
stringTypes,
|
||||
tempTableNameDeleted,
|
||||
tempTableNameUpdated,
|
||||
)
|
||||
where
|
||||
|
||||
@ -217,6 +218,9 @@ newtype TempTableName = TempTableName Text
|
||||
tempTableNameDeleted :: TempTableName
|
||||
tempTableNameDeleted = TempTableName "deleted"
|
||||
|
||||
tempTableNameUpdated :: TempTableName
|
||||
tempTableNameUpdated = TempTableName "updated"
|
||||
|
||||
data TempTable = TempTable
|
||||
{ ttName :: !TempTableName,
|
||||
ttColumns :: ![ColumnName]
|
||||
@ -311,6 +315,8 @@ data Expression
|
||||
| -- | This is for getting actual atomic values out of a JSON
|
||||
-- string.
|
||||
JsonValueExpression Expression JsonPath
|
||||
| -- | This is for evaluating SQL functions, text(e1, e2, ..).
|
||||
FunctionExpression Text [Expression]
|
||||
| OpExpression Op Expression Expression
|
||||
| ListExpression [Expression]
|
||||
| STOpExpression SpatialOp Expression Expression
|
||||
|
@ -2,11 +2,14 @@
|
||||
module Hasura.Backends.MSSQL.Types.Update
|
||||
( BackendUpdate (..),
|
||||
UpdateOperator (..),
|
||||
Update (..),
|
||||
UpdateSet,
|
||||
UpdateOutput,
|
||||
)
|
||||
where
|
||||
|
||||
import Hasura.Backends.MSSQL.Types.Instances ()
|
||||
import Hasura.Backends.MSSQL.Types.Internal qualified as MSSQL
|
||||
import Hasura.Backends.MSSQL.Types.Internal
|
||||
import Hasura.Prelude
|
||||
|
||||
-- | The MSSQL-specific data of an Update expression.
|
||||
@ -16,7 +19,7 @@ import Hasura.Prelude
|
||||
-- the data at the leaves.
|
||||
data BackendUpdate v = BackendUpdate
|
||||
{ -- | The update operations to perform on each column.
|
||||
updateOperations :: HashMap MSSQL.ColumnName (UpdateOperator v)
|
||||
updateOperations :: HashMap ColumnName (UpdateOperator v)
|
||||
}
|
||||
deriving (Functor, Foldable, Traversable, Generic, Data)
|
||||
|
||||
@ -29,3 +32,17 @@ data UpdateOperator v
|
||||
= UpdateSet v
|
||||
| UpdateInc v
|
||||
deriving (Functor, Foldable, Traversable, Generic, Data)
|
||||
|
||||
type UpdateSet = HashMap ColumnName (UpdateOperator Expression)
|
||||
|
||||
type UpdateOutput = Output Inserted
|
||||
|
||||
-- | UPDATE [table_alias] SET [table_alias].column = 'value' OUTPUT INSERTED.column INTO #updated
|
||||
-- FROM [table_name] AS [table_alias] WHERE <filter-expression>
|
||||
data Update = Update
|
||||
{ updateTable :: Aliased TableName,
|
||||
updateSet :: UpdateSet,
|
||||
updateOutput :: UpdateOutput,
|
||||
updateTempTable :: TempTable,
|
||||
updateWhere :: Where
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
description: Update an article with a column used in multiple operators
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_article(
|
||||
_set: {author_id: 2}
|
||||
_inc: {author_id: 1}
|
||||
where: {id: {_eq: 1}}
|
||||
){
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
response:
|
||||
errors:
|
||||
- extensions:
|
||||
path: "$.selectionSet.update_article.args"
|
||||
code: validation-failed
|
||||
message: 'Column found in multiple operators: "author_id".'
|
@ -0,0 +1,30 @@
|
||||
description: Increment author_id for the article which will re-assign the author
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_article(_inc: {author_id: 1}, where: {id: {_eq: 1}}){
|
||||
affected_rows
|
||||
returning{
|
||||
id
|
||||
title
|
||||
content
|
||||
author{
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_article:
|
||||
affected_rows: 1
|
||||
returning:
|
||||
- id: 1
|
||||
title: Article 1
|
||||
content: Sample article content 1
|
||||
author:
|
||||
id: 2
|
||||
name: Author 2
|
@ -0,0 +1,36 @@
|
||||
description: Update a row of author by primary key
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_author_by_pk(
|
||||
_set: {name: "Author 2 updated"}
|
||||
pk_columns: {id: 2}
|
||||
){
|
||||
id
|
||||
name
|
||||
articles_aggregate{
|
||||
aggregate{
|
||||
count
|
||||
}
|
||||
nodes{
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_author_by_pk:
|
||||
id: 2
|
||||
name: Author 2 updated
|
||||
articles_aggregate:
|
||||
aggregate:
|
||||
count: 1
|
||||
nodes:
|
||||
- id: 3
|
||||
title: Article 3
|
||||
content: Sample article content 3
|
@ -0,0 +1,17 @@
|
||||
description: Update a row of author by primary key
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_author_by_pk(
|
||||
_set: {name: "Author 2 updated"}
|
||||
pk_columns: {id: 123}
|
||||
){
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_author_by_pk: null
|
@ -0,0 +1,14 @@
|
||||
description: Update author mutation without any update operators should result in empty mutation response
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_author(where: {id: {_eq: 1}}){
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_author:
|
||||
affected_rows: 0
|
@ -0,0 +1,36 @@
|
||||
description: Update mutation on author
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_author(
|
||||
where: {id: {_eq: 1}},
|
||||
_set: {name: "Author 1 updated"}
|
||||
){
|
||||
affected_rows
|
||||
returning{
|
||||
id
|
||||
name
|
||||
articles{
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_author:
|
||||
affected_rows: 1
|
||||
returning:
|
||||
- id: 1
|
||||
name: Author 1 updated
|
||||
articles:
|
||||
- id: 1
|
||||
title: Article 1
|
||||
content: Sample article content 1
|
||||
- id: 2
|
||||
title: Article 2
|
||||
content: Sample article content 2
|
@ -0,0 +1,45 @@
|
||||
description: Updated numerics data using _inc operator
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_numerics(
|
||||
where: {id: {_eq: 1}}
|
||||
_inc: {
|
||||
numeric_col: -1.1
|
||||
decimal_col: -1.1
|
||||
int_col: -1
|
||||
smallint_col: -1
|
||||
float_col: -1.1
|
||||
real_col: -1.1
|
||||
bigint_col: -1
|
||||
tinyint_col: -1
|
||||
}
|
||||
){
|
||||
affected_rows
|
||||
returning{
|
||||
numeric_col
|
||||
decimal_col
|
||||
int_col
|
||||
smallint_col
|
||||
float_col
|
||||
real_col
|
||||
bigint_col
|
||||
tinyint_col
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_numerics:
|
||||
affected_rows: 1
|
||||
returning:
|
||||
- numeric_col: 0
|
||||
decimal_col: 122
|
||||
int_col: 2147483646
|
||||
smallint_col: 3276
|
||||
float_col: -306.87
|
||||
real_col: -37.919998
|
||||
bigint_col: 9223372036854775806
|
||||
tinyint_col: 253
|
@ -0,0 +1,34 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
#Create tables
|
||||
- type: mssql_run_sql
|
||||
args:
|
||||
source: mssql
|
||||
sql: |
|
||||
-- author table
|
||||
create table author(
|
||||
id int identity not null primary key,
|
||||
name text
|
||||
);
|
||||
-- article table
|
||||
CREATE TABLE article (
|
||||
id INT IDENTITY NOT NULL PRIMARY KEY,
|
||||
title TEXT,
|
||||
content TEXT,
|
||||
author_id INTEGER REFERENCES author(id),
|
||||
is_published BIT,
|
||||
published_on TIMESTAMP
|
||||
);
|
||||
-- table with numeric columns
|
||||
CREATE TABLE numerics (
|
||||
id INT IDENTITY NOT NULL PRIMARY KEY,
|
||||
numeric_col numeric,
|
||||
decimal_col decimal,
|
||||
int_col int,
|
||||
smallint_col smallint,
|
||||
float_col float,
|
||||
real_col real,
|
||||
bigint_col bigint,
|
||||
tinyint_col tinyint
|
||||
);
|
@ -0,0 +1,11 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
- type: mssql_run_sql
|
||||
args:
|
||||
source: mssql
|
||||
sql: |
|
||||
DROP TABLE article;
|
||||
DROP TABLE author;
|
||||
DROP TABLE numerics;
|
||||
cascade: true
|
@ -0,0 +1,40 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
- type: mssql_track_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: author
|
||||
|
||||
- type: mssql_track_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: article
|
||||
|
||||
- type: mssql_track_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: numerics
|
||||
|
||||
#Object relationship article <-> author
|
||||
- type: mssql_create_object_relationship
|
||||
args:
|
||||
source: mssql
|
||||
table: article
|
||||
name: author
|
||||
using:
|
||||
foreign_key_constraint_on: author_id
|
||||
|
||||
#Array relationship author <-> article
|
||||
- type: mssql_create_array_relationship
|
||||
args:
|
||||
source: mssql
|
||||
table: author
|
||||
name: articles
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
table: article
|
||||
column: author_id
|
@ -0,0 +1,39 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
#Drop relationships
|
||||
- type: mssql_drop_relationship
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: author
|
||||
relationship: articles
|
||||
|
||||
- type: mssql_drop_relationship
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: article
|
||||
relationship: author
|
||||
|
||||
#Untrack tables
|
||||
- type: mssql_untrack_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: author
|
||||
cascade: true
|
||||
|
||||
- type: mssql_untrack_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: article
|
||||
cascade: true
|
||||
|
||||
- type: mssql_untrack_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: numerics
|
||||
cascade: true
|
@ -0,0 +1,54 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
- type: mssql_run_sql
|
||||
args:
|
||||
source: mssql
|
||||
sql: |
|
||||
-- insert data
|
||||
INSERT INTO author (name)
|
||||
VALUES
|
||||
('Author 1'),
|
||||
('Author 2')
|
||||
;
|
||||
INSERT INTO article (title,content,author_id,is_published)
|
||||
VALUES
|
||||
(
|
||||
'Article 1',
|
||||
'Sample article content 1',
|
||||
1,
|
||||
0
|
||||
),
|
||||
(
|
||||
'Article 2',
|
||||
'Sample article content 2',
|
||||
1,
|
||||
1
|
||||
),
|
||||
(
|
||||
'Article 3',
|
||||
'Sample article content 3',
|
||||
2,
|
||||
1
|
||||
)
|
||||
;
|
||||
|
||||
INSERT INTO numerics (
|
||||
numeric_col,
|
||||
decimal_col,
|
||||
int_col,
|
||||
smallint_col,
|
||||
float_col,
|
||||
real_col,
|
||||
bigint_col,
|
||||
tinyint_col
|
||||
) VALUES ( 1.234
|
||||
, 123.45
|
||||
, 2147483647
|
||||
, 3277
|
||||
, 2.23E -308
|
||||
, 1.18E - 38
|
||||
, 9223372036854775807
|
||||
, 254
|
||||
)
|
||||
;
|
@ -0,0 +1,16 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
#truncate data
|
||||
- type: mssql_run_sql
|
||||
args:
|
||||
source: mssql
|
||||
sql: |
|
||||
-- delete rows from table
|
||||
DELETE FROM article;
|
||||
DELETE FROM author;
|
||||
DELETE FROM numerics;
|
||||
-- reset identity columns
|
||||
DBCC CHECKIDENT ('article', RESEED, 0);
|
||||
DBCC CHECKIDENT ('author', RESEED, 0);
|
||||
DBCC CHECKIDENT ('numerics', RESEED, 0);
|
@ -0,0 +1,22 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
#Create tables
|
||||
- type: mssql_run_sql
|
||||
args:
|
||||
source: mssql
|
||||
sql: |
|
||||
-- author table
|
||||
create table author(
|
||||
id int identity not null primary key,
|
||||
name text
|
||||
);
|
||||
-- article table
|
||||
CREATE TABLE article (
|
||||
id INT IDENTITY NOT NULL PRIMARY KEY,
|
||||
title TEXT,
|
||||
content TEXT,
|
||||
author_id INTEGER REFERENCES author(id),
|
||||
is_published BIT,
|
||||
published_on TIMESTAMP
|
||||
);
|
@ -0,0 +1,10 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
- type: mssql_run_sql
|
||||
args:
|
||||
source: mssql
|
||||
sql: |
|
||||
DROP TABLE article;
|
||||
DROP TABLE author;
|
||||
cascade: true
|
@ -0,0 +1,90 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
- type: mssql_track_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: author
|
||||
|
||||
- type: mssql_track_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: article
|
||||
|
||||
#Object relationship article <-> author
|
||||
- type: mssql_create_object_relationship
|
||||
args:
|
||||
source: mssql
|
||||
table: article
|
||||
name: author
|
||||
using:
|
||||
foreign_key_constraint_on: author_id
|
||||
|
||||
#Array relationship author <-> article
|
||||
- type: mssql_create_array_relationship
|
||||
args:
|
||||
source: mssql
|
||||
table: author
|
||||
name: articles
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
table: article
|
||||
column: author_id
|
||||
|
||||
#Author select permission for user
|
||||
- type: mssql_create_select_permission
|
||||
args:
|
||||
source: mssql
|
||||
table: author
|
||||
role: user
|
||||
permission:
|
||||
columns: [id, name]
|
||||
filter:
|
||||
id: X-HASURA-USER-ID
|
||||
|
||||
#Author update permission for user
|
||||
- type: mssql_create_update_permission
|
||||
args:
|
||||
source: mssql
|
||||
table: author
|
||||
role: user
|
||||
permission:
|
||||
columns:
|
||||
- name
|
||||
filter:
|
||||
id: X-Hasura-User-Id
|
||||
|
||||
#Article select permission for user
|
||||
- type: mssql_create_select_permission
|
||||
args:
|
||||
source: mssql
|
||||
table: article
|
||||
role: user
|
||||
permission:
|
||||
columns: '*'
|
||||
filter:
|
||||
$or:
|
||||
- author_id: X-HASURA-USER-ID
|
||||
- is_published: 1
|
||||
|
||||
#Article update permission for user
|
||||
#Allow modifications only on unpublished articles
|
||||
- type: mssql_create_update_permission
|
||||
args:
|
||||
source: mssql
|
||||
table: article
|
||||
role: user
|
||||
permission:
|
||||
columns:
|
||||
- title
|
||||
- content
|
||||
- is_published
|
||||
- published_on
|
||||
filter:
|
||||
$and:
|
||||
- author_id: X-HASURA-USER-ID
|
||||
- is_published: 0
|
||||
check:
|
||||
is_published: 0
|
@ -0,0 +1,32 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
#Drop relationships
|
||||
- type: mssql_drop_relationship
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: author
|
||||
relationship: articles
|
||||
|
||||
- type: mssql_drop_relationship
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: article
|
||||
relationship: author
|
||||
|
||||
#Untrack tables
|
||||
- type: mssql_untrack_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: author
|
||||
cascade: true
|
||||
|
||||
- type: mssql_untrack_table
|
||||
args:
|
||||
source: mssql
|
||||
table:
|
||||
name: article
|
||||
cascade: true
|
@ -0,0 +1,38 @@
|
||||
description: Update mutation on article
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
headers:
|
||||
X-Hasura-Role: user
|
||||
X-Hasura-User-Id: '1'
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_article(
|
||||
where: {id: {_eq: 1}}
|
||||
_set: {content: "Author 1 article content updated"}
|
||||
){
|
||||
affected_rows
|
||||
returning{
|
||||
id
|
||||
title
|
||||
content
|
||||
is_published
|
||||
author{
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_article:
|
||||
affected_rows: 1
|
||||
returning:
|
||||
- id: 1
|
||||
title: Article 1
|
||||
content: Author 1 article content updated
|
||||
is_published: false
|
||||
author:
|
||||
id: 1
|
||||
name: Author 1
|
@ -0,0 +1,32 @@
|
||||
description: Update mutation on article whose check constraint is not met. Trying to publish the article with user role.
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
headers:
|
||||
X-Hasura-Role: user
|
||||
X-Hasura-User-Id: '1'
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_article(
|
||||
where: {id: {_eq: 1}}
|
||||
_set: {is_published: true}
|
||||
){
|
||||
affected_rows
|
||||
returning{
|
||||
id
|
||||
title
|
||||
content
|
||||
is_published
|
||||
author{
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
errors:
|
||||
- extensions:
|
||||
path: "$"
|
||||
code: permission-error
|
||||
message: check constraint of an update permission has failed
|
@ -0,0 +1,31 @@
|
||||
description: Update mutation on article where filter condition is not met. Article with id = 1 belongs to author id = 1 and it is published. Trying to update the article as author id = 2.
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
headers:
|
||||
X-Hasura-Role: user
|
||||
X-Hasura-User-Id: '2'
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_article(
|
||||
where: {id: {_eq: 1}}
|
||||
_set: {content: "Author 1 article content updated"}
|
||||
){
|
||||
affected_rows
|
||||
returning{
|
||||
id
|
||||
title
|
||||
content
|
||||
is_published
|
||||
author{
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_article:
|
||||
affected_rows: 0
|
||||
returning: []
|
@ -0,0 +1,32 @@
|
||||
description: Update mutation on article where updating a column has been restricted.
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
headers:
|
||||
X-Hasura-Role: user
|
||||
X-Hasura-User-Id: '1'
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_article(
|
||||
where: {id: {_eq: 1}}
|
||||
_set: {id: 2}
|
||||
){
|
||||
affected_rows
|
||||
returning{
|
||||
id
|
||||
title
|
||||
content
|
||||
is_published
|
||||
author{
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
errors:
|
||||
- extensions:
|
||||
path: "$.selectionSet.update_article.args._set.id"
|
||||
code: validation-failed
|
||||
message: 'field "id" not found in type: ''article_set_input'''
|
@ -0,0 +1,31 @@
|
||||
description: Update mutation on article where filter condition is not met. Article with id = 2 belongs to author id = 2 and it is unpublished.
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
headers:
|
||||
X-Hasura-Role: user
|
||||
X-Hasura-User-Id: '2'
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_article(
|
||||
where: {id: {_eq: 2}}
|
||||
_set: {content: "Author 2 article content updated"}
|
||||
){
|
||||
affected_rows
|
||||
returning{
|
||||
id
|
||||
title
|
||||
content
|
||||
is_published
|
||||
author{
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_article:
|
||||
affected_rows: 0
|
||||
returning: []
|
@ -0,0 +1,39 @@
|
||||
description: Update name of an author
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
headers:
|
||||
X-Hasura-Role: user
|
||||
X-Hasura-User-Id: '1'
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_author(
|
||||
where: {id: {_eq: 1}}
|
||||
_set: {name: "Author 1 updated"}
|
||||
){
|
||||
affected_rows
|
||||
returning{
|
||||
id
|
||||
name
|
||||
articles{
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_author:
|
||||
affected_rows: 1
|
||||
returning:
|
||||
- id: 1
|
||||
name: Author 1 updated
|
||||
articles:
|
||||
- id: 1
|
||||
title: Article 1
|
||||
content: Sample article content 1
|
||||
- id: 2
|
||||
title: Article 2
|
||||
content: Sample article content 2
|
@ -0,0 +1,30 @@
|
||||
description: Trying to update name of author whose id /= X-Hasura-User-Id. This shouldn't mutate any data as permission filter condition is not met.
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
headers:
|
||||
X-Hasura-Role: user
|
||||
X-Hasura-User-Id: '1'
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
update_author(
|
||||
where: {id: {_eq: 2}}
|
||||
_set: {name: "Author 1 updated"}
|
||||
){
|
||||
affected_rows
|
||||
returning{
|
||||
id
|
||||
name
|
||||
articles{
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
update_author:
|
||||
affected_rows: 0
|
||||
returning: []
|
@ -0,0 +1,34 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
- type: mssql_run_sql
|
||||
args:
|
||||
source: mssql
|
||||
sql: |
|
||||
-- insert data
|
||||
INSERT INTO author (name)
|
||||
VALUES
|
||||
('Author 1'),
|
||||
('Author 2')
|
||||
;
|
||||
INSERT INTO article (title,content,author_id,is_published)
|
||||
VALUES
|
||||
(
|
||||
'Article 1',
|
||||
'Sample article content 1',
|
||||
1,
|
||||
0
|
||||
),
|
||||
(
|
||||
'Article 2',
|
||||
'Sample article content 2',
|
||||
1,
|
||||
1
|
||||
),
|
||||
(
|
||||
'Article 3',
|
||||
'Sample article content 3',
|
||||
2,
|
||||
1
|
||||
)
|
||||
;
|
@ -0,0 +1,14 @@
|
||||
type: bulk
|
||||
args:
|
||||
|
||||
#truncate data
|
||||
- type: mssql_run_sql
|
||||
args:
|
||||
source: mssql
|
||||
sql: |
|
||||
-- delete rows from table
|
||||
DELETE FROM article;
|
||||
DELETE FROM author;
|
||||
-- reset identity columns
|
||||
DBCC CHECKIDENT ('article', RESEED, 0);
|
||||
DBCC CHECKIDENT ('author', RESEED, 0);
|
@ -512,6 +512,63 @@ class TestGraphqlUpdatePermissions:
|
||||
def dir(cls):
|
||||
return "queries/graphql_mutation/update/permissions"
|
||||
|
||||
@pytest.mark.parametrize("backend", ['mssql'])
|
||||
@use_mutation_fixtures
|
||||
class TestGraphqlUpdateBasicMssql:
|
||||
|
||||
def test_set_author_name(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/author_set_name_mssql.yaml")
|
||||
|
||||
def test_article_inc(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/article_inc_mssql.yaml")
|
||||
|
||||
def test_author_no_operator(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/author_no_operator_mssql.yaml")
|
||||
|
||||
def test_article_column_multiple_operators(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/article_column_multiple_operators_mssql.yaml")
|
||||
|
||||
def test_author_by_pk(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/author_by_pk_mssql.yaml")
|
||||
|
||||
def test_author_by_pk_null(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/author_by_pk_null_mssql.yaml")
|
||||
|
||||
def test_numerics_inc(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/numerics_inc_mssql.yaml")
|
||||
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return "queries/graphql_mutation/update/basic"
|
||||
|
||||
@pytest.mark.parametrize("backend", ['mssql'])
|
||||
@use_mutation_fixtures
|
||||
class TestGraphqlUpdatePermissionsMssql:
|
||||
|
||||
def test_user_update_author(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/user_update_author_mssql.yaml")
|
||||
|
||||
def test_user_update_author_other_userid(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/user_update_author_other_userid_mssql.yaml")
|
||||
|
||||
def test_user_can_update_unpublished_article(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/user_can_update_unpublished_article_mssql.yaml")
|
||||
|
||||
def test_user_cannot_update_published_article(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_published_article_mssql.yaml")
|
||||
|
||||
def test_user_cannot_update_id_col_article(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_id_col_article_mssql.yaml")
|
||||
|
||||
def test_user_cannot_update_another_users_article(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/user_cannot_update_another_users_article_mssql.yaml")
|
||||
|
||||
def test_user_cannot_publish(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + "/user_cannot_publish_mssql.yaml")
|
||||
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return "queries/graphql_mutation/update/permissions"
|
||||
|
||||
@pytest.mark.parametrize("transport", ['http', 'websocket'])
|
||||
@use_mutation_fixtures
|
||||
|
Loading…
Reference in New Issue
Block a user