mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-10-05 14:28:08 +03:00
Data Connectors update mutations support [GDC-713]
[GDC-713]: https://hasurahq.atlassian.net/browse/GDC-713?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ PR-URL: https://github.com/hasura/graphql-engine-mono/pull/7604 GitOrigin-RevId: e0d496b425bed48f2d53a66584f4c7ecd40b485e
This commit is contained in:
parent
ded69b361e
commit
d6cbbe3e49
@ -104,6 +104,7 @@ library
|
||||
Test.DataConnector.MockAgent.MetadataApiSpec
|
||||
Test.DataConnector.MockAgent.QueryRelationshipsSpec
|
||||
Test.DataConnector.MockAgent.TransformedConfigurationSpec
|
||||
Test.DataConnector.MockAgent.UpdateMutationsSpec
|
||||
Test.DataConnector.QuerySpec
|
||||
Test.DataConnector.SelectPermissionsSpec
|
||||
Test.EventTriggers.EventTriggersSpecialCharactersSpec
|
||||
|
@ -0,0 +1,501 @@
|
||||
module Test.DataConnector.MockAgent.UpdateMutationsSpec
|
||||
( spec,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Aeson qualified as Aeson
|
||||
import Data.ByteString (ByteString)
|
||||
import Data.HashMap.Strict qualified as HashMap
|
||||
import Data.List.NonEmpty qualified as NE
|
||||
import Harness.Backend.DataConnector.Mock (AgentRequest (..), MockRequestResults (..), mockAgentTest, mockMutationResponse)
|
||||
import Harness.Backend.DataConnector.Mock qualified as Mock
|
||||
import Harness.Quoter.Graphql (graphql)
|
||||
import Harness.Quoter.Yaml (yaml)
|
||||
import Harness.Test.BackendType qualified as BackendType
|
||||
import Harness.Test.Fixture qualified as Fixture
|
||||
import Harness.TestEnvironment (GlobalTestEnvironment, TestEnvironment)
|
||||
import Harness.Yaml
|
||||
import Hasura.Backends.DataConnector.API qualified as API
|
||||
import Hasura.Prelude
|
||||
import Language.GraphQL.Draft.Syntax.QQ qualified as G
|
||||
import Test.Hspec
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
spec :: SpecWith GlobalTestEnvironment
|
||||
spec =
|
||||
Fixture.runWithLocalTestEnvironment
|
||||
( NE.fromList
|
||||
[ (Fixture.fixture $ Fixture.Backend Mock.backendTypeMetadata)
|
||||
{ Fixture.mkLocalTestEnvironment = Mock.mkLocalTestEnvironment,
|
||||
Fixture.setupTeardown = \(testEnv, mockEnv) ->
|
||||
[Mock.setupAction sourceMetadata Mock.agentConfig (testEnv, mockEnv)]
|
||||
}
|
||||
]
|
||||
)
|
||||
tests
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
testRoleName :: ByteString
|
||||
testRoleName = "test-role"
|
||||
|
||||
sourceMetadata :: Aeson.Value
|
||||
sourceMetadata =
|
||||
let source = BackendType.backendSourceName Mock.backendTypeMetadata
|
||||
backendType = BackendType.backendTypeString Mock.backendTypeMetadata
|
||||
in [yaml|
|
||||
name : *source
|
||||
kind: *backendType
|
||||
tables:
|
||||
- table: [Track]
|
||||
object_relationships:
|
||||
- name: Genre
|
||||
using:
|
||||
manual_configuration:
|
||||
remote_table: [Genre]
|
||||
column_mapping:
|
||||
GenreId: GenreId
|
||||
select_permissions:
|
||||
- role: *testRoleName
|
||||
permission:
|
||||
columns:
|
||||
- TrackId
|
||||
- Name
|
||||
- AlbumId
|
||||
- MediaTypeId
|
||||
- GenreId
|
||||
- Composer
|
||||
- Milliseconds
|
||||
- Bytes
|
||||
- UnitPrice
|
||||
filter: {}
|
||||
update_permissions:
|
||||
- role: *testRoleName
|
||||
permission:
|
||||
filter:
|
||||
AlbumId: "X-Hasura-AlbumId"
|
||||
check:
|
||||
UnitPrice:
|
||||
_gt: 0
|
||||
set:
|
||||
AlbumId: "X-Hasura-AlbumId"
|
||||
columns:
|
||||
- Name
|
||||
- AlbumId
|
||||
- MediaTypeId
|
||||
- GenreId
|
||||
- Composer
|
||||
- Milliseconds
|
||||
- Bytes
|
||||
- UnitPrice
|
||||
- table: [Genre]
|
||||
select_permissions:
|
||||
- role: *testRoleName
|
||||
permission:
|
||||
columns:
|
||||
- GenreId
|
||||
- Name
|
||||
filter: {}
|
||||
configuration: {}
|
||||
|]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
tests :: Fixture.Options -> SpecWith (TestEnvironment, Mock.MockAgentEnvironment)
|
||||
tests _opts = do
|
||||
mockAgentTest "update rows with update permissions" $ \performGraphqlRequest -> do
|
||||
let headers = [("X-Hasura-AlbumId", "3"), ("X-Hasura-Role", testRoleName)]
|
||||
let graphqlRequest =
|
||||
[graphql|
|
||||
mutation UpdateMutation {
|
||||
update_Track(where: {GenreId: {_eq: 1}}, _set: {Name: "Another Name"}, _inc: {Milliseconds: 1000}) {
|
||||
updatedRowCount: affected_rows
|
||||
updatedRows: returning {
|
||||
TrackId
|
||||
Name
|
||||
Genre {
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|]
|
||||
let mockAgentResponse =
|
||||
API.MutationResponse
|
||||
[ API.MutationOperationResults
|
||||
{ API._morAffectedRows = 3,
|
||||
API._morReturning =
|
||||
Just
|
||||
[ HashMap.fromList
|
||||
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 3),
|
||||
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
|
||||
( API.FieldName "updatedRows_Genre",
|
||||
API.mkRelationshipFieldValue $
|
||||
rowsResponse
|
||||
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
|
||||
]
|
||||
]
|
||||
)
|
||||
],
|
||||
HashMap.fromList
|
||||
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 4),
|
||||
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
|
||||
( API.FieldName "updatedRows_Genre",
|
||||
API.mkRelationshipFieldValue $
|
||||
rowsResponse
|
||||
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
|
||||
]
|
||||
]
|
||||
)
|
||||
],
|
||||
HashMap.fromList
|
||||
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 5),
|
||||
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
|
||||
( API.FieldName "updatedRows_Genre",
|
||||
API.mkRelationshipFieldValue $
|
||||
rowsResponse
|
||||
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
let mockConfig = Mock.chinookMock & mockMutationResponse mockAgentResponse
|
||||
|
||||
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
|
||||
|
||||
_mrrGraphqlResponse
|
||||
`shouldBeYaml` [yaml|
|
||||
data:
|
||||
update_Track:
|
||||
updatedRowCount: 3
|
||||
updatedRows:
|
||||
- TrackId: 3
|
||||
Name: Another Name
|
||||
Genre:
|
||||
Name: Rock
|
||||
- TrackId: 4
|
||||
Name: Another Name
|
||||
Genre:
|
||||
Name: Rock
|
||||
- TrackId: 5
|
||||
Name: Another Name
|
||||
Genre:
|
||||
Name: Rock
|
||||
|]
|
||||
|
||||
let expectedRequest =
|
||||
API.MutationRequest
|
||||
{ API._mrTableRelationships =
|
||||
[ API.TableRelationships
|
||||
{ API._trSourceTable = API.TableName ("Track" :| []),
|
||||
API._trRelationships =
|
||||
HashMap.fromList
|
||||
[ ( API.RelationshipName "Genre",
|
||||
API.Relationship
|
||||
{ API._rTargetTable = API.TableName ("Genre" :| []),
|
||||
API._rRelationshipType = API.ObjectRelationship,
|
||||
API._rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")]
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
],
|
||||
API._mrInsertSchema = [],
|
||||
API._mrOperations =
|
||||
[ API.UpdateOperation $
|
||||
API.UpdateMutationOperation
|
||||
{ API._umoTable = API.TableName ("Track" :| []),
|
||||
API._umoUpdates =
|
||||
[ API.SetColumn $
|
||||
API.RowColumnOperatorValue
|
||||
{ API._rcovColumn = API.ColumnName "Name",
|
||||
API._rcovValue = Aeson.String "Another Name",
|
||||
API._rcovValueType = API.ScalarType "string"
|
||||
},
|
||||
API.CustomUpdateColumnOperator (API.UpdateColumnOperatorName [G.name|inc|]) $
|
||||
API.RowColumnOperatorValue
|
||||
{ API._rcovColumn = API.ColumnName "Milliseconds",
|
||||
API._rcovValue = Aeson.Number 1000,
|
||||
API._rcovValueType = API.ScalarType "number"
|
||||
},
|
||||
API.SetColumn $
|
||||
API.RowColumnOperatorValue
|
||||
{ API._rcovColumn = API.ColumnName "AlbumId",
|
||||
API._rcovValue = Aeson.Number 3,
|
||||
API._rcovValueType = API.ScalarType "number"
|
||||
}
|
||||
],
|
||||
API._umoWhere =
|
||||
Just $
|
||||
API.And
|
||||
[ API.ApplyBinaryComparisonOperator
|
||||
API.Equal
|
||||
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
|
||||
(API.ScalarValue (Aeson.Number 3) $ API.ScalarType "number"),
|
||||
API.ApplyBinaryComparisonOperator
|
||||
API.Equal
|
||||
(API.ComparisonColumn API.CurrentTable (API.ColumnName "GenreId") $ API.ScalarType "number")
|
||||
(API.ScalarValue (Aeson.Number 1) $ API.ScalarType "number")
|
||||
],
|
||||
API._umoPostUpdateCheck =
|
||||
Just $
|
||||
API.ApplyBinaryComparisonOperator
|
||||
API.GreaterThan
|
||||
(API.ComparisonColumn API.CurrentTable (API.ColumnName "UnitPrice") $ API.ScalarType "number")
|
||||
(API.ScalarValue (Aeson.Number 0) $ API.ScalarType "number"),
|
||||
API._umoReturningFields =
|
||||
HashMap.fromList
|
||||
[ (API.FieldName "updatedRows_TrackId", API.ColumnField (API.ColumnName "TrackId") (API.ScalarType "number")),
|
||||
(API.FieldName "updatedRows_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
|
||||
( API.FieldName "updatedRows_Genre",
|
||||
API.RelField
|
||||
( API.RelationshipField
|
||||
(API.RelationshipName "Genre")
|
||||
API.Query
|
||||
{ _qFields =
|
||||
Just $
|
||||
HashMap.fromList
|
||||
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
|
||||
],
|
||||
_qAggregates = Nothing,
|
||||
_qLimit = Nothing,
|
||||
_qOffset = Nothing,
|
||||
_qWhere = Nothing,
|
||||
_qOrderBy = Nothing
|
||||
}
|
||||
)
|
||||
)
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
_mrrRecordedRequest `shouldBe` Just (Mutation expectedRequest)
|
||||
|
||||
mockAgentTest "update_many rows with update permissions" $ \performGraphqlRequest -> do
|
||||
let headers = [("X-Hasura-AlbumId", "3"), ("X-Hasura-Role", testRoleName)]
|
||||
let graphqlRequest =
|
||||
[graphql|
|
||||
mutation UpdateMutation {
|
||||
update_Track_many(updates: [
|
||||
{ where: {TrackId: {_eq: 3}}, _set: {Name: "Another Name"}, _inc: {Milliseconds: 1000} },
|
||||
{ where: {TrackId: {_gt: 3}}, _set: {Name: "Better Name"}, _inc: {UnitPrice: 1} }
|
||||
]) {
|
||||
updatedRowCount: affected_rows
|
||||
updatedRows: returning {
|
||||
TrackId
|
||||
Name
|
||||
Genre {
|
||||
Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|]
|
||||
let mockAgentResponse =
|
||||
API.MutationResponse
|
||||
[ API.MutationOperationResults
|
||||
{ API._morAffectedRows = 1,
|
||||
API._morReturning =
|
||||
Just
|
||||
[ HashMap.fromList
|
||||
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 3),
|
||||
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Another Name"),
|
||||
( API.FieldName "updatedRows_Genre",
|
||||
API.mkRelationshipFieldValue $
|
||||
rowsResponse
|
||||
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
},
|
||||
API.MutationOperationResults
|
||||
{ API._morAffectedRows = 2,
|
||||
API._morReturning =
|
||||
Just
|
||||
[ HashMap.fromList
|
||||
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 4),
|
||||
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Better Name"),
|
||||
( API.FieldName "updatedRows_Genre",
|
||||
API.mkRelationshipFieldValue $
|
||||
rowsResponse
|
||||
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
|
||||
]
|
||||
]
|
||||
)
|
||||
],
|
||||
HashMap.fromList
|
||||
[ (API.FieldName "updatedRows_TrackId", API.mkColumnFieldValue $ Aeson.Number 5),
|
||||
(API.FieldName "updatedRows_Name", API.mkColumnFieldValue $ Aeson.String "Better Name"),
|
||||
( API.FieldName "updatedRows_Genre",
|
||||
API.mkRelationshipFieldValue $
|
||||
rowsResponse
|
||||
[ [ (API.FieldName "Name", API.mkColumnFieldValue $ Aeson.String "Rock")
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
let mockConfig = Mock.chinookMock & mockMutationResponse mockAgentResponse
|
||||
|
||||
MockRequestResults {..} <- performGraphqlRequest mockConfig headers graphqlRequest
|
||||
|
||||
_mrrGraphqlResponse
|
||||
`shouldBeYaml` [yaml|
|
||||
data:
|
||||
update_Track_many:
|
||||
- updatedRowCount: 1
|
||||
updatedRows:
|
||||
- TrackId: 3
|
||||
Name: Another Name
|
||||
Genre:
|
||||
Name: Rock
|
||||
- updatedRowCount: 2
|
||||
updatedRows:
|
||||
- TrackId: 4
|
||||
Name: Better Name
|
||||
Genre:
|
||||
Name: Rock
|
||||
- TrackId: 5
|
||||
Name: Better Name
|
||||
Genre:
|
||||
Name: Rock
|
||||
|]
|
||||
|
||||
let sharedPostUpdateCheck =
|
||||
Just $
|
||||
API.ApplyBinaryComparisonOperator
|
||||
API.GreaterThan
|
||||
(API.ComparisonColumn API.CurrentTable (API.ColumnName "UnitPrice") $ API.ScalarType "number")
|
||||
(API.ScalarValue (Aeson.Number 0) $ API.ScalarType "number")
|
||||
let sharedReturning =
|
||||
HashMap.fromList
|
||||
[ (API.FieldName "updatedRows_TrackId", API.ColumnField (API.ColumnName "TrackId") (API.ScalarType "number")),
|
||||
(API.FieldName "updatedRows_Name", API.ColumnField (API.ColumnName "Name") (API.ScalarType "string")),
|
||||
( API.FieldName "updatedRows_Genre",
|
||||
API.RelField
|
||||
( API.RelationshipField
|
||||
(API.RelationshipName "Genre")
|
||||
API.Query
|
||||
{ _qFields =
|
||||
Just $
|
||||
HashMap.fromList
|
||||
[ (API.FieldName "Name", API.ColumnField (API.ColumnName "Name") $ API.ScalarType "string")
|
||||
],
|
||||
_qAggregates = Nothing,
|
||||
_qLimit = Nothing,
|
||||
_qOffset = Nothing,
|
||||
_qWhere = Nothing,
|
||||
_qOrderBy = Nothing
|
||||
}
|
||||
)
|
||||
)
|
||||
]
|
||||
let expectedRequest =
|
||||
API.MutationRequest
|
||||
{ API._mrTableRelationships =
|
||||
[ API.TableRelationships
|
||||
{ API._trSourceTable = API.TableName ("Track" :| []),
|
||||
API._trRelationships =
|
||||
HashMap.fromList
|
||||
[ ( API.RelationshipName "Genre",
|
||||
API.Relationship
|
||||
{ API._rTargetTable = API.TableName ("Genre" :| []),
|
||||
API._rRelationshipType = API.ObjectRelationship,
|
||||
API._rColumnMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")]
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
],
|
||||
API._mrInsertSchema = [],
|
||||
API._mrOperations =
|
||||
[ API.UpdateOperation $
|
||||
API.UpdateMutationOperation
|
||||
{ API._umoTable = API.TableName ("Track" :| []),
|
||||
API._umoUpdates =
|
||||
[ API.SetColumn $
|
||||
API.RowColumnOperatorValue
|
||||
{ API._rcovColumn = API.ColumnName "Name",
|
||||
API._rcovValue = Aeson.String "Another Name",
|
||||
API._rcovValueType = API.ScalarType "string"
|
||||
},
|
||||
API.CustomUpdateColumnOperator (API.UpdateColumnOperatorName [G.name|inc|]) $
|
||||
API.RowColumnOperatorValue
|
||||
{ API._rcovColumn = API.ColumnName "Milliseconds",
|
||||
API._rcovValue = Aeson.Number 1000,
|
||||
API._rcovValueType = API.ScalarType "number"
|
||||
},
|
||||
API.SetColumn $
|
||||
API.RowColumnOperatorValue
|
||||
{ API._rcovColumn = API.ColumnName "AlbumId",
|
||||
API._rcovValue = Aeson.Number 3,
|
||||
API._rcovValueType = API.ScalarType "number"
|
||||
}
|
||||
],
|
||||
API._umoWhere =
|
||||
Just $
|
||||
API.And
|
||||
[ API.ApplyBinaryComparisonOperator
|
||||
API.Equal
|
||||
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
|
||||
(API.ScalarValue (Aeson.Number 3) $ API.ScalarType "number"),
|
||||
API.ApplyBinaryComparisonOperator
|
||||
API.Equal
|
||||
(API.ComparisonColumn API.CurrentTable (API.ColumnName "TrackId") $ API.ScalarType "number")
|
||||
(API.ScalarValue (Aeson.Number 3) $ API.ScalarType "number")
|
||||
],
|
||||
API._umoPostUpdateCheck = sharedPostUpdateCheck,
|
||||
API._umoReturningFields = sharedReturning
|
||||
},
|
||||
API.UpdateOperation $
|
||||
API.UpdateMutationOperation
|
||||
{ API._umoTable = API.TableName ("Track" :| []),
|
||||
API._umoUpdates =
|
||||
[ API.SetColumn $
|
||||
API.RowColumnOperatorValue
|
||||
{ API._rcovColumn = API.ColumnName "Name",
|
||||
API._rcovValue = Aeson.String "Better Name",
|
||||
API._rcovValueType = API.ScalarType "string"
|
||||
},
|
||||
API.CustomUpdateColumnOperator (API.UpdateColumnOperatorName [G.name|inc|]) $
|
||||
API.RowColumnOperatorValue
|
||||
{ API._rcovColumn = API.ColumnName "UnitPrice",
|
||||
API._rcovValue = Aeson.Number 1,
|
||||
API._rcovValueType = API.ScalarType "number"
|
||||
},
|
||||
API.SetColumn $
|
||||
API.RowColumnOperatorValue
|
||||
{ API._rcovColumn = API.ColumnName "AlbumId",
|
||||
API._rcovValue = Aeson.Number 3,
|
||||
API._rcovValueType = API.ScalarType "number"
|
||||
}
|
||||
],
|
||||
API._umoWhere =
|
||||
Just $
|
||||
API.And
|
||||
[ API.ApplyBinaryComparisonOperator
|
||||
API.Equal
|
||||
(API.ComparisonColumn API.CurrentTable (API.ColumnName "AlbumId") $ API.ScalarType "number")
|
||||
(API.ScalarValue (Aeson.Number 3) $ API.ScalarType "number"),
|
||||
API.ApplyBinaryComparisonOperator
|
||||
API.GreaterThan
|
||||
(API.ComparisonColumn API.CurrentTable (API.ColumnName "TrackId") $ API.ScalarType "number")
|
||||
(API.ScalarValue (Aeson.Number 3) $ API.ScalarType "number")
|
||||
],
|
||||
API._umoPostUpdateCheck = sharedPostUpdateCheck,
|
||||
API._umoReturningFields = sharedReturning
|
||||
}
|
||||
]
|
||||
}
|
||||
_mrrRecordedRequest `shouldBe` Just (Mutation expectedRequest)
|
||||
|
||||
rowsResponse :: [[(API.FieldName, API.FieldValue)]] -> API.QueryResponse
|
||||
rowsResponse rows = API.QueryResponse (Just $ HashMap.fromList <$> rows) Nothing
|
@ -11,16 +11,19 @@ import Data.HashMap.Strict qualified as HashMap
|
||||
import Data.Text.Extended (toTxt)
|
||||
import Hasura.Backends.DataConnector.API qualified as API
|
||||
import Hasura.Backends.DataConnector.Adapter.Types
|
||||
import Hasura.Backends.DataConnector.Adapter.Types.Mutations
|
||||
import Hasura.Backends.DataConnector.Plan.Common
|
||||
import Hasura.Backends.DataConnector.Plan.QueryPlan (reshapeAnnFields, translateAnnFields)
|
||||
import Hasura.Base.Error (Code (..), QErr, throw400, throw500)
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR.BoolExp (GBoolExp (..))
|
||||
import Hasura.RQL.IR.Delete
|
||||
import Hasura.RQL.IR.Insert hiding (Single)
|
||||
import Hasura.RQL.IR.Returning
|
||||
import Hasura.RQL.IR.Root
|
||||
import Hasura.RQL.IR.Select
|
||||
import Hasura.RQL.IR.Update
|
||||
import Hasura.RQL.IR.Update.Batch
|
||||
import Hasura.RQL.IR.Value
|
||||
import Hasura.RQL.Types.Column
|
||||
import Hasura.RQL.Types.Common
|
||||
@ -80,8 +83,15 @@ translateMutationDB sessionVariables = \case
|
||||
_mrInsertSchema = apiTableInsertSchema,
|
||||
_mrOperations = [API.InsertOperation insertOperation]
|
||||
}
|
||||
MDBUpdate _update ->
|
||||
throw400 NotSupported "translateMutationDB: update mutations not implemented for the Data Connector backend."
|
||||
MDBUpdate update -> do
|
||||
(updateOperations, tableRelationships) <- CPS.runWriterT $ translateUpdate sessionVariables update
|
||||
let apiTableRelationships = uncurry API.TableRelationships <$> HashMap.toList (unTableRelationships tableRelationships)
|
||||
pure $
|
||||
API.MutationRequest
|
||||
{ _mrTableRelationships = apiTableRelationships,
|
||||
_mrInsertSchema = [],
|
||||
_mrOperations = API.UpdateOperation <$> updateOperations
|
||||
}
|
||||
MDBDelete _delete ->
|
||||
throw400 NotSupported "translateMutationDB: delete mutations not implemented for the Data Connector backend."
|
||||
MDBFunction _returnsSet _select ->
|
||||
@ -158,12 +168,71 @@ translateInsertRow sessionVariables tableName tableColumns defaultColumnValues i
|
||||
& fmap (\(AIColumn columnNameAndValue) -> columnNameAndValue)
|
||||
& HashMap.fromList
|
||||
|
||||
translateMutationOutputToReturningFields ::
|
||||
translateUpdate ::
|
||||
MonadError QErr m =>
|
||||
SessionVariables ->
|
||||
AnnotatedUpdateG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
|
||||
CPS.WriterT TableRelationships m [API.UpdateMutationOperation]
|
||||
translateUpdate sessionVariables annUpdate@AnnotatedUpdateG {..} = do
|
||||
case _auUpdateVariant of
|
||||
SingleBatch batch -> (: []) <$> translateUpdateBatch sessionVariables annUpdate batch
|
||||
MultipleBatches batches -> traverse (translateUpdateBatch sessionVariables annUpdate) batches
|
||||
|
||||
translateUpdateBatch ::
|
||||
MonadError QErr m =>
|
||||
SessionVariables ->
|
||||
AnnotatedUpdateG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
|
||||
UpdateBatch 'DataConnector UpdateOperator (UnpreparedValue 'DataConnector) ->
|
||||
CPS.WriterT TableRelationships m API.UpdateMutationOperation
|
||||
translateUpdateBatch sessionVariables AnnotatedUpdateG {..} UpdateBatch {..} = do
|
||||
updates <- lift $ translateUpdateOperations sessionVariables _ubOperations
|
||||
whereExp <- translateBoolExpToExpression sessionVariables tableName (BoolAnd [_auUpdatePermissions, _ubWhere])
|
||||
postUpdateCheck <- translateBoolExpToExpression sessionVariables tableName _auCheck
|
||||
returningFields <- translateMutationOutputToReturningFields sessionVariables tableName _auOutput
|
||||
|
||||
pure $
|
||||
API.UpdateMutationOperation
|
||||
{ API._umoTable = tableName,
|
||||
API._umoWhere = whereExp,
|
||||
API._umoUpdates = updates,
|
||||
API._umoPostUpdateCheck = postUpdateCheck,
|
||||
API._umoReturningFields = HashMap.mapKeys (API.FieldName . getFieldNameTxt) returningFields
|
||||
}
|
||||
where
|
||||
tableName = Witch.from _auTable
|
||||
|
||||
translateUpdateOperations ::
|
||||
forall m.
|
||||
MonadError QErr m =>
|
||||
SessionVariables ->
|
||||
HashMap ColumnName (UpdateOperator (UnpreparedValue 'DataConnector)) ->
|
||||
m [API.RowUpdate]
|
||||
translateUpdateOperations sessionVariables columnUpdates =
|
||||
forM (HashMap.toList columnUpdates) $ \(columnName, updateOperator) -> do
|
||||
let (mkRowUpdate, value) =
|
||||
case updateOperator of
|
||||
UpdateSet value' -> (API.SetColumn, value')
|
||||
UpdateCustomOperator operatorName value' -> (API.CustomUpdateColumnOperator operatorName, value')
|
||||
(scalarType, literalValue) <- prepareAndExtractLiteralValue value
|
||||
let operatorValue = API.RowColumnOperatorValue (Witch.from columnName) literalValue (Witch.from scalarType)
|
||||
pure $ mkRowUpdate operatorValue
|
||||
where
|
||||
prepareAndExtractLiteralValue :: UnpreparedValue 'DataConnector -> m (ScalarType, J.Value)
|
||||
prepareAndExtractLiteralValue unpreparedValue = do
|
||||
preparedLiteral <- prepareLiteral sessionVariables unpreparedValue
|
||||
case preparedLiteral of
|
||||
ValueLiteral scalarType value -> pure (scalarType, value)
|
||||
ArrayLiteral _scalarType _values -> throw400 NotSupported "translateUpdateOperations: Array literals are not supported as column update values"
|
||||
|
||||
translateMutationOutputToReturningFields ::
|
||||
( MonadError QErr m,
|
||||
Has TableRelationships writerOutput,
|
||||
Monoid writerOutput
|
||||
) =>
|
||||
SessionVariables ->
|
||||
API.TableName ->
|
||||
MutationOutputG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
|
||||
CPS.WriterT (TableRelationships, TableInsertSchemas) m (HashMap FieldName API.Field)
|
||||
CPS.WriterT writerOutput m (HashMap FieldName API.Field)
|
||||
translateMutationOutputToReturningFields sessionVariables tableName = \case
|
||||
MOutSinglerowObject annFields ->
|
||||
translateAnnFields sessionVariables noPrefix tableName annFields
|
||||
@ -171,12 +240,15 @@ translateMutationOutputToReturningFields sessionVariables tableName = \case
|
||||
HashMap.unions <$> traverse (uncurry $ translateMutField sessionVariables tableName) mutFields
|
||||
|
||||
translateMutField ::
|
||||
MonadError QErr m =>
|
||||
( MonadError QErr m,
|
||||
Has TableRelationships writerOutput,
|
||||
Monoid writerOutput
|
||||
) =>
|
||||
SessionVariables ->
|
||||
API.TableName ->
|
||||
FieldName ->
|
||||
MutFldG 'DataConnector Void (UnpreparedValue 'DataConnector) ->
|
||||
CPS.WriterT (TableRelationships, TableInsertSchemas) m (HashMap FieldName API.Field)
|
||||
CPS.WriterT writerOutput m (HashMap FieldName API.Field)
|
||||
translateMutField sessionVariables tableName fieldName = \case
|
||||
MCount ->
|
||||
-- All mutation operations in a request return their affected rows count.
|
||||
@ -197,20 +269,51 @@ reshapeResponseToMutationGqlShape ::
|
||||
MutationDB 'DataConnector Void v ->
|
||||
API.MutationResponse ->
|
||||
m J.Encoding
|
||||
reshapeResponseToMutationGqlShape mutationDb API.MutationResponse {..} = do
|
||||
reshapeResponseToMutationGqlShape mutationDb mutationResponse = do
|
||||
case mutationDb of
|
||||
MDBInsert AnnotatedInsert {..} ->
|
||||
reshapeOutputForSingleBatchOperation _aiOutput mutationResponse
|
||||
MDBUpdate AnnotatedUpdateG {..} ->
|
||||
case _auUpdateVariant of
|
||||
SingleBatch _batch ->
|
||||
reshapeOutputForSingleBatchOperation _auOutput mutationResponse
|
||||
MultipleBatches batches ->
|
||||
let outputs = replicate (length batches) _auOutput
|
||||
in reshapeOutputForMultipleBatchOperation outputs mutationResponse
|
||||
MDBDelete AnnDel {..} ->
|
||||
reshapeOutputForSingleBatchOperation _adOutput mutationResponse
|
||||
MDBFunction _returnsSet _select ->
|
||||
throw400 NotSupported "reshapeResponseToMutationGqlShape: function mutations not implemented for the Data Connector backend."
|
||||
|
||||
reshapeOutputForSingleBatchOperation ::
|
||||
MonadError QErr m =>
|
||||
MutationOutputG 'DataConnector Void v ->
|
||||
API.MutationResponse ->
|
||||
m J.Encoding
|
||||
reshapeOutputForSingleBatchOperation mutationOutput API.MutationResponse {..} = do
|
||||
mutationOperationResult <-
|
||||
listToMaybe _mrOperationResults
|
||||
`onNothing` throw500 "Unable to find expected mutation operation results"
|
||||
|
||||
mutationOutput <-
|
||||
case mutationDb of
|
||||
MDBInsert AnnotatedInsert {..} -> pure _aiOutput
|
||||
MDBUpdate AnnotatedUpdateG {..} -> pure _auOutput
|
||||
MDBDelete AnnDel {..} -> pure _adOutput
|
||||
MDBFunction _returnsSet _select -> throw400 NotSupported "reshapeResponseToMutationGqlShape: function mutations not implemented for the Data Connector backend."
|
||||
|
||||
reshapeMutationOutput mutationOutput mutationOperationResult
|
||||
|
||||
reshapeOutputForMultipleBatchOperation ::
|
||||
MonadError QErr m =>
|
||||
[MutationOutputG 'DataConnector Void v] ->
|
||||
API.MutationResponse ->
|
||||
m J.Encoding
|
||||
reshapeOutputForMultipleBatchOperation mutationOutputs API.MutationResponse {..} = do
|
||||
unless (operationResultCount >= requiredResultCount) $
|
||||
throw500 ("Data Connector agent returned " <> tshow operationResultCount <> " mutation operation results where at least " <> tshow requiredResultCount <> " was expected")
|
||||
|
||||
reshapedResults <-
|
||||
zip mutationOutputs _mrOperationResults
|
||||
& traverse (uncurry reshapeMutationOutput)
|
||||
|
||||
pure $ JE.list id reshapedResults
|
||||
where
|
||||
requiredResultCount = length mutationOutputs
|
||||
operationResultCount = length _mrOperationResults
|
||||
|
||||
reshapeMutationOutput ::
|
||||
MonadError QErr m =>
|
||||
MutationOutputG 'DataConnector Void v ->
|
||||
|
Loading…
Reference in New Issue
Block a user