From 190e429eef9f94270eebfcbfa7953dc579e2bdd2 Mon Sep 17 00:00:00 2001 From: Daniel Chambers Date: Thu, 23 Feb 2023 10:40:10 +1100 Subject: [PATCH] Add tests for delete mutations to the Data Connector agent test suite [GDC-721]: https://hasurahq.atlassian.net/browse/GDC-721?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8067 GitOrigin-RevId: bf5b7e7c20c6011d438888be6017331def7063bf --- dc-agents/sqlite/README.md | 2 +- server/lib/dc-api/dc-api.cabal | 1 + .../DataConnector/API/V0/Mutations.hs | 4 + server/lib/dc-api/test/Test/Data.hs | 14 +- .../dc-api/test/Test/Specs/MutationSpec.hs | 2 + .../Test/Specs/MutationSpec/DeleteSpec.hs | 385 ++++++++++++++++++ 6 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 server/lib/dc-api/test/Test/Specs/MutationSpec/DeleteSpec.hs diff --git a/dc-agents/sqlite/README.md b/dc-agents/sqlite/README.md index b9c85f65906..c8eb9258f75 100644 --- a/dc-agents/sqlite/README.md +++ b/dc-agents/sqlite/README.md @@ -127,7 +127,7 @@ cabal run dc-api:test:tests-dc-api -- test --agent-base-url http://localhost:810 From the HGE repo. ## Known Issues -* Using "returning" in update mutations where you join across relationships that are affected by the update mutation itself may return inconsistent results. This is because of this issue with SQLite: https://sqlite.org/forum/forumpost/9470611066 +* Using "returning" in update/delete mutations where you join across relationships that are affected by the update/delete mutation itself may return inconsistent results. This is because of this issue with SQLite: https://sqlite.org/forum/forumpost/9470611066 ## TODO diff --git a/server/lib/dc-api/dc-api.cabal b/server/lib/dc-api/dc-api.cabal index c0987565b91..c66e5c78072 100644 --- a/server/lib/dc-api/dc-api.cabal +++ b/server/lib/dc-api/dc-api.cabal @@ -161,6 +161,7 @@ test-suite tests-dc-api Test.Specs.HealthSpec Test.Specs.MetricsSpec Test.Specs.MutationSpec + Test.Specs.MutationSpec.DeleteSpec Test.Specs.MutationSpec.InsertSpec Test.Specs.MutationSpec.UpdateSpec Test.Specs.QuerySpec diff --git a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Mutations.hs b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Mutations.hs index ea04c530649..9d641687877 100644 --- a/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Mutations.hs +++ b/server/lib/dc-api/src/Hasura/Backends/DataConnector/API/V0/Mutations.hs @@ -39,6 +39,9 @@ module Hasura.Backends.DataConnector.API.V0.Mutations RowUpdate (..), RowColumnOperatorValue (..), DeleteMutationOperation (..), + dmoTable, + dmoWhere, + dmoReturningFields, MutationResponse (..), mrOperationResults, MutationOperationResults (..), @@ -512,5 +515,6 @@ instance HasCodec MutationOperationResults where $(makeLenses ''MutationRequest) $(makeLenses ''InsertMutationOperation) $(makeLenses ''UpdateMutationOperation) +$(makeLenses ''DeleteMutationOperation) $(makeLenses ''MutationResponse) $(makeLenses ''MutationOperationResults) diff --git a/server/lib/dc-api/test/Test/Data.hs b/server/lib/dc-api/test/Test/Data.hs index 9c893f34a1b..2072d7491bc 100644 --- a/server/lib/dc-api/test/Test/Data.hs +++ b/server/lib/dc-api/test/Test/Data.hs @@ -276,13 +276,15 @@ tracksTableRelationships = mediaTypeJoinFieldMapping = HashMap.fromList [(API.ColumnName "MediaTypeId", API.ColumnName "MediaTypeId")] albumJoinFieldMapping = HashMap.fromList [(API.ColumnName "AlbumId", API.ColumnName "AlbumId")] genreJoinFieldMapping = HashMap.fromList [(API.ColumnName "GenreId", API.ColumnName "GenreId")] + playlistTracksJoinFieldMapping = HashMap.fromList [(API.ColumnName "TrackId", API.ColumnName "TrackId")] in API.TableRelationships tracksTableName ( HashMap.fromList [ (invoiceLinesRelationshipName, API.Relationship invoiceLinesTableName API.ArrayRelationship invoiceLinesJoinFieldMapping), (mediaTypeRelationshipName, API.Relationship mediaTypesTableName API.ObjectRelationship mediaTypeJoinFieldMapping), (albumRelationshipName, API.Relationship albumsTableName API.ObjectRelationship albumJoinFieldMapping), - (genreRelationshipName, API.Relationship genresTableName API.ObjectRelationship genreJoinFieldMapping) + (genreRelationshipName, API.Relationship genresTableName API.ObjectRelationship genreJoinFieldMapping), + (playlistTracksRelationshipName, API.Relationship playlistTracksTableName API.ArrayRelationship playlistTracksJoinFieldMapping) ] ) @@ -298,6 +300,9 @@ albumRelationshipName = API.RelationshipName "Album" genreRelationshipName :: API.RelationshipName genreRelationshipName = API.RelationshipName "Genre" +playlistTracksRelationshipName :: API.RelationshipName +playlistTracksRelationshipName = API.RelationshipName "PlaylistTracks" + genresTableName :: API.TableName genresTableName = mkTableName "Genre" @@ -393,6 +398,10 @@ data TestData = TestData _tdMediaTypeRelationshipName :: API.RelationshipName, _tdAlbumRelationshipName :: API.RelationshipName, _tdGenreRelationshipName :: API.RelationshipName, + _tdPlaylistTracksRelationshipName :: API.RelationshipName, + -- = PlaylistTracks table + _tdPlaylistTracksTableName :: API.TableName, + _tdPlaylistTracksRows :: [HashMap API.FieldName API.FieldValue], -- = Genres table _tdGenresTableName :: API.TableName, _tdGenresRows :: [HashMap API.FieldName API.FieldValue], @@ -454,6 +463,9 @@ mkTestData schemaResponse testConfig = _tdMediaTypeRelationshipName = mediaTypeRelationshipName, _tdAlbumRelationshipName = albumRelationshipName, _tdGenreRelationshipName = genreRelationshipName, + _tdPlaylistTracksRelationshipName = playlistTracksRelationshipName, + _tdPlaylistTracksTableName = formatTableName testConfig playlistTracksTableName, + _tdPlaylistTracksRows = playlistTracksRows, _tdGenresTableName = formatTableName testConfig genresTableName, _tdGenresRows = genresRows, _tdGenresTableRelationships = formatTableRelationships genresTableRelationships, diff --git a/server/lib/dc-api/test/Test/Specs/MutationSpec.hs b/server/lib/dc-api/test/Test/Specs/MutationSpec.hs index bf80304328e..759be7da7f4 100644 --- a/server/lib/dc-api/test/Test/Specs/MutationSpec.hs +++ b/server/lib/dc-api/test/Test/Specs/MutationSpec.hs @@ -7,6 +7,7 @@ import Data.Foldable (for_) import Hasura.Backends.DataConnector.API import Test.Data (EdgeCasesTestData, TestData) import Test.Sandwich (describe) +import Test.Specs.MutationSpec.DeleteSpec qualified as DeleteSpec import Test.Specs.MutationSpec.InsertSpec qualified as InsertSpec import Test.Specs.MutationSpec.UpdateSpec qualified as UpdateSpec import Test.TestHelpers (AgentTestSpec) @@ -18,3 +19,4 @@ spec testData edgeCasesTestData capabilities@Capabilities {..} = do for_ (_cMutations >>= _mcInsertCapabilities) $ \_insertCapabilities -> do InsertSpec.spec testData edgeCasesTestData capabilities UpdateSpec.spec testData edgeCasesTestData capabilities + DeleteSpec.spec testData edgeCasesTestData capabilities diff --git a/server/lib/dc-api/test/Test/Specs/MutationSpec/DeleteSpec.hs b/server/lib/dc-api/test/Test/Specs/MutationSpec/DeleteSpec.hs new file mode 100644 index 00000000000..2fad32b1cfe --- /dev/null +++ b/server/lib/dc-api/test/Test/Specs/MutationSpec/DeleteSpec.hs @@ -0,0 +1,385 @@ +module Test.Specs.MutationSpec.DeleteSpec (spec) where + +import Control.Lens (ix, (&), (.~), (?~), (^?)) +import Control.Monad (when) +import Data.Aeson qualified as J +import Data.Functor ((<&>)) +import Data.HashMap.Strict (HashMap) +import Data.List.NonEmpty (NonEmpty (..)) +import Data.Maybe (fromMaybe, maybeToList) +import Hasura.Backends.DataConnector.API +import Test.AgentAPI (mutationGuarded, queryGuarded) +import Test.AgentDatasets (chinookTemplate, usesDataset) +import Test.Data (EdgeCasesTestData (..), TestData (..)) +import Test.Data qualified as Data +import Test.Expectations (mutationResponseShouldBe, rowsShouldBe) +import Test.Sandwich (describe) +import Test.TestHelpers (AgentTestSpec, it) +import Test.TestHelpers qualified as Test +import Prelude + +spec :: TestData -> Maybe EdgeCasesTestData -> Capabilities -> AgentTestSpec +spec TestData {..} edgeCasesTestData Capabilities {..} = describe "Delete Mutations" $ do + usesDataset chinookTemplate $ it "can delete all rows" $ do + let deleteOperation = mkDeleteOperation _tdInvoiceLinesTableName + let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [DeleteOperation deleteOperation] + + response <- mutationGuarded mutationRequest + + let expectedResult = MutationOperationResults 2240 Nothing + response `mutationResponseShouldBe` MutationResponse [expectedResult] + + receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded invoiceLinesQueryRequest + Data.responseRows receivedInvoiceLines `rowsShouldBe` [] + + usesDataset chinookTemplate $ it "can delete a specific row" $ do + let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceLineId" invoiceLineIdScalarType) (ScalarValue (J.Number 420) invoiceLineIdScalarType) + let deleteOperation = + mkDeleteOperation _tdInvoiceLinesTableName + & dmoWhere ?~ whereExp + let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [DeleteOperation deleteOperation] + + response <- mutationGuarded mutationRequest + + let expectedResult = MutationOperationResults 1 Nothing + response `mutationResponseShouldBe` MutationResponse [expectedResult] + + let expectedRemainingRows = + _tdInvoiceLinesRows + & filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceLineId" . Data._ColumnFieldNumber /= Just 420) + + receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded invoiceLinesQueryRequest + Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedRemainingRows + + usesDataset chinookTemplate $ it "can delete a range of rows" $ do + let whereExp = + And + [ ApplyBinaryComparisonOperator GreaterThan (_tdCurrentComparisonColumn "InvoiceLineId" invoiceLineIdScalarType) (ScalarValue (J.Number 10) invoiceLineIdScalarType), + ApplyBinaryComparisonOperator LessThanOrEqual (_tdCurrentComparisonColumn "InvoiceLineId" invoiceLineIdScalarType) (ScalarValue (J.Number 20) invoiceLineIdScalarType) + ] + let deleteOperation = + mkDeleteOperation _tdInvoiceLinesTableName + & dmoWhere ?~ whereExp + let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [DeleteOperation deleteOperation] + + response <- mutationGuarded mutationRequest + + let expectedResult = MutationOperationResults 10 Nothing + response `mutationResponseShouldBe` MutationResponse [expectedResult] + + let expectedRemainingRows = + _tdInvoiceLinesRows + & filter + ( \invoiceLine -> + invoiceLine ^? Data.field "InvoiceLineId" . Data._ColumnFieldNumber <= Just 10 + || invoiceLine ^? Data.field "InvoiceLineId" . Data._ColumnFieldNumber > Just 20 + ) + + receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded invoiceLinesQueryRequest + Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedRemainingRows + + usesDataset chinookTemplate $ it "can perform multiple delete operations" $ do + let whereExp1 = ApplyBinaryComparisonOperator LessThan (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 3) invoiceIdScalarType) + let whereExp2 = ApplyBinaryComparisonOperator GreaterThan (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 410) invoiceIdScalarType) + let deleteOperation1 = + mkDeleteOperation _tdInvoiceLinesTableName + & dmoWhere ?~ whereExp1 + let deleteOperation2 = + mkDeleteOperation _tdInvoiceLinesTableName + & dmoWhere ?~ whereExp2 + let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [DeleteOperation deleteOperation1, DeleteOperation deleteOperation2] + + response <- mutationGuarded mutationRequest + + let expectedResult1 = MutationOperationResults 6 Nothing + let expectedResult2 = MutationOperationResults 15 Nothing + response `mutationResponseShouldBe` MutationResponse [expectedResult1, expectedResult2] + + let expectedRemainingRows = + _tdInvoiceLinesRows + & filter + ( \invoiceLine -> + invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber >= Just 3 + && invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber <= Just 410 + ) + + receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded invoiceLinesQueryRequest + Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedRemainingRows + + when ((_cComparisons >>= _ccSubqueryComparisonCapabilities <&> _ctccSupportsRelations) == Just True) $ do + usesDataset chinookTemplate $ it "can delete rows filtered by a related table" $ do + let whereExp = + Exists (RelatedTable _tdTrackRelationshipName) $ + ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "Composer" composerScalarType) (ScalarValue (J.String "Eric Clapton") composerScalarType) + let deleteOperation = + mkDeleteOperation _tdInvoiceLinesTableName + & dmoWhere ?~ whereExp + let tableRelationships = [Data.onlyKeepRelationships [_tdTrackRelationshipName] _tdInvoiceLinesTableRelationships] + let mutationRequest = + Data.emptyMutationRequest + & mrOperations .~ [DeleteOperation deleteOperation] + & mrTableRelationships .~ tableRelationships + + response <- mutationGuarded mutationRequest + + let expectedResult = MutationOperationResults 3 Nothing + response `mutationResponseShouldBe` MutationResponse [expectedResult] + + let expectedRemainingRows = + _tdInvoiceLinesRows + & filter + ( \invoiceLine -> fromMaybe True $ do + trackId <- invoiceLine ^? Data.field "TrackId" . Data._ColumnFieldNumber + track <- _tdTracksRowsById ^? ix trackId + pure $ track ^? Data.field "Composer" . Data._ColumnFieldString /= Just "Eric Clapton" + ) + + receivedInvoiceLines <- Data.sortResponseRowsBy "InvoiceLineId" <$> queryGuarded (invoiceLinesQueryRequest & qrTableRelationships .~ tableRelationships) + Data.responseRows receivedInvoiceLines `rowsShouldBe` expectedRemainingRows + + describe "returning" $ do + usesDataset chinookTemplate $ it "returns deleted rows" $ do + let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 10) invoiceIdScalarType) + let deleteOperation = + mkDeleteOperation _tdInvoiceLinesTableName + & dmoWhere ?~ whereExp + & dmoReturningFields .~ invoiceLinesFields + let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [DeleteOperation deleteOperation] + + response <- mutationGuarded mutationRequest + + let expectedDeletedRows = + _tdInvoiceLinesRows + & filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 10) + + let expectedResult = MutationOperationResults 6 (Just expectedDeletedRows) + response `mutationResponseShouldBe` MutationResponse [expectedResult] + + usesDataset chinookTemplate $ it "can return deleted rows joined to additional rows from an object relationship" $ do + let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceId" invoiceIdScalarType) (ScalarValue (J.Number 37) invoiceIdScalarType) + let deleteOperation = + mkDeleteOperation _tdInvoiceLinesTableName + & dmoWhere ?~ whereExp + & dmoReturningFields + .~ invoiceLinesFields + <> Data.mkFieldsMap + [ ( "Track", + ( RelField + ( RelationshipField _tdTrackRelationshipName $ + Data.emptyQuery + & qFields + ?~ Data.mkFieldsMap + [ ("TrackId", _tdColumnField _tdTracksTableName "TrackId"), + ("Name", _tdColumnField _tdTracksTableName "Name") + ] + ) + ) + ) + ] + let tableRelationships = [Data.onlyKeepRelationships [_tdTrackRelationshipName] _tdInvoiceLinesTableRelationships] + let mutationRequest = + Data.emptyMutationRequest + & mrOperations .~ [DeleteOperation deleteOperation] + & mrTableRelationships .~ tableRelationships + + response <- mutationGuarded mutationRequest + + let joinInTrack (invoiceLine :: HashMap FieldName FieldValue) = + let track = (invoiceLine ^? Data.field "TrackId" . Data._ColumnFieldNumber) >>= \trackId -> _tdTracksRowsById ^? ix trackId + trackTrimmed = Data.filterColumns ["TrackId", "Name"] $ maybeToList track + in Data.insertField "Track" (mkSubqueryResponse trackTrimmed) invoiceLine + + let expectedDeletedRows = + _tdInvoiceLinesRows + & filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == Just 37) + & fmap joinInTrack + + let expectedResult = MutationOperationResults 4 (Just expectedDeletedRows) + response `mutationResponseShouldBe` MutationResponse [expectedResult] + + usesDataset chinookTemplate $ it "can return deleted rows joined to additional rows from an array relationship" $ do + let whereExp = ApplyBinaryComparisonOperator Equal (_tdCurrentComparisonColumn "InvoiceLineId" invoiceLineIdScalarType) (ScalarValue (J.Number 2) invoiceLineIdScalarType) + let deleteOperation = + mkDeleteOperation _tdInvoiceLinesTableName + & dmoWhere ?~ whereExp + & dmoReturningFields + .~ invoiceLinesFields + <> Data.mkFieldsMap + [ ( "Track", + ( RelField + ( RelationshipField _tdTrackRelationshipName $ + Data.emptyQuery + & qFields + ?~ Data.mkFieldsMap + [ ("TrackId", _tdColumnField _tdTracksTableName "TrackId"), + ("Name", _tdColumnField _tdTracksTableName "Name"), + ( "PlaylistTracks", + ( RelField + ( RelationshipField _tdPlaylistTracksRelationshipName $ + Data.emptyQuery + & qFields + ?~ Data.mkFieldsMap + [ ("PlaylistId", _tdColumnField _tdPlaylistTracksTableName "PlaylistId"), + ("TrackId", _tdColumnField _tdPlaylistTracksTableName "TrackId") + ] + ) + ) + ) + ] + ) + ) + ) + ] + let tableRelationships = + [ Data.onlyKeepRelationships [_tdTrackRelationshipName] _tdInvoiceLinesTableRelationships, + Data.onlyKeepRelationships [_tdPlaylistTracksRelationshipName] _tdTracksTableRelationships + ] + let mutationRequest = + Data.emptyMutationRequest + & mrOperations .~ [DeleteOperation deleteOperation] + & mrTableRelationships .~ tableRelationships + + response <- mutationGuarded mutationRequest + + let joinInPlaylistTracks (track :: HashMap FieldName FieldValue) = + let trackId = track ^? Data.field "TrackId" . Data._ColumnFieldNumber + playlistTracks = _tdPlaylistTracksRows & filter (\playlistTrack -> playlistTrack ^? Data.field "TrackId" . Data._ColumnFieldNumber == trackId) + in Data.insertField "PlaylistTracks" (mkSubqueryResponse playlistTracks) track + + let joinInTrack (invoiceLine :: HashMap FieldName FieldValue) = + let track = (invoiceLine ^? Data.field "TrackId" . Data._ColumnFieldNumber) >>= \trackId -> _tdTracksRowsById ^? ix trackId + trackTrimmed = Data.filterColumns ["TrackId", "Name"] $ maybeToList track + in Data.insertField "Track" (mkSubqueryResponse (joinInPlaylistTracks <$> trackTrimmed)) invoiceLine + + let expectedDeletedRows = + _tdInvoiceLinesRows + & filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceLineId" . Data._ColumnFieldNumber == Just 2) + & fmap joinInTrack + + let expectedResult = MutationOperationResults 1 (Just expectedDeletedRows) + response `mutationResponseShouldBe` MutationResponse [expectedResult] + + usesDataset chinookTemplate $ it "deleted rows are not returned when returning them again across a relationship" $ do + -- In this scenario we delete two invoice lines off an invoice, then we + -- return those deleted lines, joining to their invoice and then back again + -- to the invoice's lines, which should _not_ contain the two lines that were deleted + let whereExp = ApplyBinaryArrayComparisonOperator In (_tdCurrentComparisonColumn "InvoiceLineId" invoiceLineIdScalarType) [J.Number 4, J.Number 5] invoiceLineIdScalarType + let deleteOperation = + mkDeleteOperation _tdInvoiceLinesTableName + & dmoWhere ?~ whereExp + & dmoReturningFields + .~ invoiceLinesFields + <> Data.mkFieldsMap + [ ( "Invoice", + ( RelField + ( RelationshipField _tdInvoiceRelationshipName $ + Data.emptyQuery + & qFields + ?~ Data.mkFieldsMap + [ ("InvoiceId", _tdColumnField _tdInvoicesTableName "InvoiceId"), + ("Total", _tdColumnField _tdInvoicesTableName "Total"), + ( "InvoiceLines", + ( RelField + ( RelationshipField _tdInvoiceLinesRelationshipName $ + Data.emptyQuery + & qFields ?~ invoiceLinesFields + & qOrderBy ?~ OrderBy mempty (_tdOrderByColumn [] "InvoiceLineId" Ascending :| []) + ) + ) + ) + ] + ) + ) + ) + ] + let tableRelationships = + [ Data.onlyKeepRelationships [_tdInvoiceRelationshipName] _tdInvoiceLinesTableRelationships, + Data.onlyKeepRelationships [_tdInvoiceLinesRelationshipName] _tdInvoicesTableRelationships + ] + let mutationRequest = + Data.emptyMutationRequest + & mrOperations .~ [DeleteOperation deleteOperation] + & mrTableRelationships .~ tableRelationships + + response <- mutationGuarded mutationRequest + + let remainingInvoiceLines = + _tdInvoiceLinesRows & filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceLineId" . Data._ColumnFieldNumber `notElem` [Just 4, Just 5]) + + let joinInInvoiceLines (invoice :: HashMap FieldName FieldValue) = + let invoiceId = invoice ^? Data.field "InvoiceId" . Data._ColumnFieldNumber + invoiceLines = remainingInvoiceLines & filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber == invoiceId) + in Data.insertField "InvoiceLines" (mkSubqueryResponse invoiceLines) invoice + + let joinInInvoice (invoiceLine :: HashMap FieldName FieldValue) = + let invoice = (invoiceLine ^? Data.field "InvoiceId" . Data._ColumnFieldNumber) >>= \invoiceId -> _tdInvoicesRowsById ^? ix invoiceId + invoiceTrimmed = Data.filterColumns ["InvoiceId", "Total"] $ maybeToList invoice + in Data.insertField "Invoice" (mkSubqueryResponse (joinInInvoiceLines <$> invoiceTrimmed)) invoiceLine + + let expectedDeletedRows = + _tdInvoiceLinesRows + & filter (\invoiceLine -> invoiceLine ^? Data.field "InvoiceLineId" . Data._ColumnFieldNumber `elem` [Just 4, Just 5]) + & fmap joinInInvoice + + let expectedResult = MutationOperationResults 2 (Just expectedDeletedRows) + response `mutationResponseShouldBe` MutationResponse [expectedResult] + + describe "edge cases" $ + edgeCaseTest _ectdNoPrimaryKeyTableName "can delete rows in a table with no primary key" $ \EdgeCasesTestData {..} -> do + let firstNameScalarType = _ectdFindColumnScalarType _ectdNoPrimaryKeyTableName "FirstName" + let lastNameScalarType = _ectdFindColumnScalarType _ectdNoPrimaryKeyTableName "LastName" + let whereExp = + And + [ ApplyBinaryComparisonOperator Equal (_ectdCurrentComparisonColumn "FirstName" firstNameScalarType) (ScalarValue (J.String "Beverly") firstNameScalarType), + ApplyBinaryComparisonOperator Equal (_ectdCurrentComparisonColumn "LastName" lastNameScalarType) (ScalarValue (J.String "Crusher") lastNameScalarType) + ] + let returning = + Data.mkFieldsMap + [ ("FirstName", _ectdColumnField _ectdNoPrimaryKeyTableName "FirstName"), + ("LastName", _ectdColumnField _ectdNoPrimaryKeyTableName "LastName") + ] + let deleteOperation = + mkDeleteOperation _ectdNoPrimaryKeyTableName + & dmoWhere ?~ whereExp + & dmoReturningFields .~ returning + let mutationRequest = Data.emptyMutationRequest & mrOperations .~ [DeleteOperation deleteOperation] + + response <- mutationGuarded mutationRequest + + let expectedDeletedRows = + [ Data.mkFieldsMap $ + [ ("FirstName", mkColumnFieldValue $ J.String "Beverly"), + ("LastName", mkColumnFieldValue $ J.String "Crusher") + ] + ] + + let expectedResult = MutationOperationResults 1 (Just expectedDeletedRows) + response `mutationResponseShouldBe` MutationResponse [expectedResult] + where + edgeCaseTest = Test.edgeCaseTest edgeCasesTestData + + mkDeleteOperation :: TableName -> DeleteMutationOperation + mkDeleteOperation tableName = DeleteMutationOperation tableName Nothing mempty + + mkSubqueryResponse :: [HashMap FieldName FieldValue] -> FieldValue + mkSubqueryResponse rows = + mkRelationshipFieldValue $ QueryResponse (Just rows) Nothing + + invoiceLinesFields :: HashMap FieldName Field + invoiceLinesFields = + Data.mkFieldsMap + [ ("InvoiceId", _tdColumnField _tdInvoiceLinesTableName "InvoiceId"), + ("InvoiceLineId", _tdColumnField _tdInvoiceLinesTableName "InvoiceLineId"), + ("TrackId", _tdColumnField _tdInvoiceLinesTableName "TrackId"), + ("UnitPrice", _tdColumnField _tdInvoiceLinesTableName "UnitPrice"), + ("Quantity", _tdColumnField _tdInvoiceLinesTableName "Quantity") + ] + + invoiceLinesQueryRequest :: QueryRequest + invoiceLinesQueryRequest = + let query = Data.emptyQuery & qFields ?~ invoiceLinesFields & qOrderBy ?~ OrderBy mempty (_tdOrderByColumn [] "InvoiceId" Ascending :| []) + in QueryRequest _tdInvoiceLinesTableName [] query + + invoiceIdScalarType = _tdFindColumnScalarType _tdInvoiceLinesTableName "InvoiceId" + invoiceLineIdScalarType = _tdFindColumnScalarType _tdInvoiceLinesTableName "InvoiceLineId" + composerScalarType = _tdFindColumnScalarType _tdTracksTableName "Composer"