2022-04-10 07:47:15 +03:00
|
|
|
module Test.QuerySpec.RelationshipsSpec (spec) where
|
|
|
|
|
2022-07-20 08:20:49 +03:00
|
|
|
import Autodocodec.Extended (ValueWrapper (..), ValueWrapper3 (..), vwValue)
|
|
|
|
import Control.Lens (Traversal', ix, (&), (.~), (^.), (^..), (^?), _Just)
|
|
|
|
import Data.Aeson.KeyMap (KeyMap)
|
2022-06-08 18:31:28 +03:00
|
|
|
import Data.Aeson.KeyMap qualified as KeyMap
|
2022-04-10 07:47:15 +03:00
|
|
|
import Data.HashMap.Strict qualified as HashMap
|
|
|
|
import Data.List.NonEmpty (NonEmpty (..))
|
2022-07-20 08:20:49 +03:00
|
|
|
import Data.Maybe (maybeToList)
|
2022-04-10 07:47:15 +03:00
|
|
|
import Data.Text (Text)
|
2022-05-02 08:03:12 +03:00
|
|
|
import Hasura.Backends.DataConnector.API
|
2022-04-10 07:47:15 +03:00
|
|
|
import Servant.API (NamedRoutes)
|
|
|
|
import Servant.Client (Client, (//))
|
|
|
|
import Test.Data qualified as Data
|
|
|
|
import Test.Hspec (Spec, describe, it)
|
|
|
|
import Test.Hspec.Expectations.Pretty (shouldBe)
|
|
|
|
import Prelude
|
|
|
|
|
2022-06-02 08:22:44 +03:00
|
|
|
spec :: Client IO (NamedRoutes Routes) -> SourceName -> Config -> Spec
|
|
|
|
spec api sourceName config = describe "Relationship Queries" $ do
|
2022-06-24 09:58:25 +03:00
|
|
|
it "perform an object relationship query by joining artist to albums" $ do
|
2022-04-10 07:47:15 +03:00
|
|
|
let query = albumsWithArtistQuery id
|
2022-07-20 08:20:49 +03:00
|
|
|
receivedAlbums <- Data.sortResponseRowsBy "AlbumId" <$> (api // _query) sourceName config query
|
2022-04-10 07:47:15 +03:00
|
|
|
|
2022-07-20 08:20:49 +03:00
|
|
|
let joinInArtist (album :: KeyMap FieldValue) =
|
|
|
|
let artist = (album ^? ix "ArtistId" . Data._ColumnFieldNumber) >>= \artistId -> Data.artistsAsJsonById ^? ix artistId
|
|
|
|
artistPropVal = maybeToList artist
|
|
|
|
in KeyMap.insert "Artist" (mkSubqueryResponse artistPropVal) album
|
2022-06-13 23:58:44 +03:00
|
|
|
let removeArtistId = KeyMap.delete "ArtistId"
|
2022-04-10 07:47:15 +03:00
|
|
|
|
|
|
|
let expectedAlbums = (removeArtistId . joinInArtist) <$> Data.albumsAsJson
|
2022-07-20 08:20:49 +03:00
|
|
|
Data.responseRows receivedAlbums `shouldBe` expectedAlbums
|
|
|
|
_qrAggregates receivedAlbums `shouldBe` Nothing
|
2022-04-10 07:47:15 +03:00
|
|
|
|
2022-06-24 09:58:25 +03:00
|
|
|
it "perform an array relationship query by joining albums to artists" $ do
|
2022-04-10 07:47:15 +03:00
|
|
|
let query = artistsWithAlbumsQuery id
|
2022-07-20 08:20:49 +03:00
|
|
|
receivedArtists <- Data.sortResponseRowsBy "ArtistId" <$> (api // _query) sourceName config query
|
2022-04-10 07:47:15 +03:00
|
|
|
|
2022-07-20 08:20:49 +03:00
|
|
|
let joinInAlbums (artist :: KeyMap FieldValue) =
|
|
|
|
let artistId = artist ^? ix "ArtistId" . Data._ColumnFieldNumber
|
|
|
|
albumFilter artistId' album = album ^? ix "ArtistId" . Data._ColumnFieldNumber == Just artistId'
|
2022-06-02 05:06:45 +03:00
|
|
|
albums = maybe [] (\artistId' -> filter (albumFilter artistId') Data.albumsAsJson) artistId
|
2022-07-20 08:20:49 +03:00
|
|
|
albums' = KeyMap.delete "ArtistId" <$> albums
|
|
|
|
in KeyMap.insert "Albums" (mkSubqueryResponse albums') artist
|
2022-04-10 07:47:15 +03:00
|
|
|
|
|
|
|
let expectedAlbums = joinInAlbums <$> Data.artistsAsJson
|
2022-07-20 08:20:49 +03:00
|
|
|
Data.responseRows receivedArtists `shouldBe` expectedAlbums
|
|
|
|
_qrAggregates receivedArtists `shouldBe` Nothing
|
2022-04-10 07:47:15 +03:00
|
|
|
|
2022-06-24 09:58:25 +03:00
|
|
|
it "perform an object relationship query by joining employee to customers and filter comparing columns across the object relationship" $ do
|
|
|
|
-- Join Employee to Customers via SupportRep, and only get those customers that have a rep
|
|
|
|
-- that is in the same country as the customer
|
|
|
|
-- This sort of thing would come from a permissions filter on Customer that looks like:
|
|
|
|
-- { SupportRep: { Country: { _ceq: [ "$", "Country" ] } } }
|
|
|
|
let where' =
|
|
|
|
ApplyBinaryComparisonOperator
|
|
|
|
( ValueWrapper3
|
|
|
|
Equal
|
|
|
|
(comparisonColumn [supportRepRelationshipName] "Country")
|
|
|
|
(AnotherColumn (ValueWrapper (comparisonColumn [] "Country")))
|
|
|
|
)
|
|
|
|
let query = customersWithSupportRepQuery id & qrQuery . qWhere .~ Just where'
|
2022-07-20 08:20:49 +03:00
|
|
|
receivedCustomers <- Data.sortResponseRowsBy "CustomerId" <$> (api // _query) sourceName config query
|
2022-06-24 09:58:25 +03:00
|
|
|
|
2022-07-20 08:20:49 +03:00
|
|
|
let joinInSupportRep (customer :: KeyMap FieldValue) =
|
|
|
|
let supportRep = (customer ^? ix "SupportRepId" . Data._ColumnFieldNumber) >>= \employeeId -> Data.employeesAsJsonById ^? ix employeeId
|
|
|
|
supportRepPropVal = maybeToList $ Data.filterColumnsByQueryFields employeesQuery <$> supportRep
|
|
|
|
in KeyMap.insert "SupportRep" (mkSubqueryResponse supportRepPropVal) customer
|
2022-06-24 09:58:25 +03:00
|
|
|
|
2022-07-20 08:20:49 +03:00
|
|
|
let filterCustomersBySupportRepCountry (customer :: KeyMap FieldValue) =
|
|
|
|
let customerCountry = customer ^? ix "Country" . Data._ColumnFieldString
|
|
|
|
supportRepCountry = customer ^.. ix "SupportRep" . subqueryRows . ix "Country" . Data._ColumnFieldString
|
|
|
|
in maybe False (`elem` supportRepCountry) customerCountry
|
2022-06-24 09:58:25 +03:00
|
|
|
|
|
|
|
let expectedCustomers = filter filterCustomersBySupportRepCountry $ Data.filterColumnsByQueryFields (query ^. qrQuery) . joinInSupportRep <$> Data.customersAsJson
|
2022-07-20 08:20:49 +03:00
|
|
|
Data.responseRows receivedCustomers `shouldBe` expectedCustomers
|
|
|
|
_qrAggregates receivedCustomers `shouldBe` Nothing
|
2022-06-24 09:58:25 +03:00
|
|
|
|
|
|
|
it "perform an array relationship query by joining customers to employees and filter comparing columns across the array relationship" $ do
|
|
|
|
-- Join Customers to Employees via SupportRepForCustomers, and only get those employees that are reps for
|
|
|
|
-- customers that are in the same country as the employee
|
|
|
|
-- This sort of thing would come from a permissions filter on Employees that looks like:
|
|
|
|
-- { SupportRepForCustomers: { Country: { _ceq: [ "$", "Country" ] } } }
|
|
|
|
let where' =
|
|
|
|
ApplyBinaryComparisonOperator
|
|
|
|
( ValueWrapper3
|
|
|
|
Equal
|
|
|
|
(comparisonColumn [supportRepForCustomersRelationshipName] "Country")
|
|
|
|
(AnotherColumn (ValueWrapper (comparisonColumn [] "Country")))
|
|
|
|
)
|
|
|
|
let query = employeesWithCustomersQuery id & qrQuery . qWhere .~ Just where'
|
2022-07-20 08:20:49 +03:00
|
|
|
receivedEmployees <- Data.sortResponseRowsBy "EmployeeId" <$> (api // _query) sourceName config query
|
2022-06-24 09:58:25 +03:00
|
|
|
|
2022-07-20 08:20:49 +03:00
|
|
|
let joinInCustomers (employee :: KeyMap FieldValue) =
|
|
|
|
let employeeId = employee ^? ix "EmployeeId" . Data._ColumnFieldNumber
|
|
|
|
customerFilter employeeId' customer = customer ^? ix "SupportRepId" . Data._ColumnFieldNumber == Just employeeId'
|
2022-06-24 09:58:25 +03:00
|
|
|
customers = maybe [] (\employeeId' -> filter (customerFilter employeeId') Data.customersAsJson) employeeId
|
2022-07-20 08:20:49 +03:00
|
|
|
customers' = Data.filterColumnsByQueryFields customersQuery <$> customers
|
|
|
|
in KeyMap.insert "SupportRepForCustomers" (mkSubqueryResponse customers') employee
|
2022-06-24 09:58:25 +03:00
|
|
|
|
2022-07-20 08:20:49 +03:00
|
|
|
let filterEmployeesByCustomerCountry (employee :: KeyMap FieldValue) =
|
|
|
|
let employeeCountry = employee ^? ix "Country" . Data._ColumnFieldString
|
|
|
|
customerCountries = employee ^.. ix "SupportRepForCustomers" . subqueryRows . ix "Country" . Data._ColumnFieldString
|
2022-06-24 09:58:25 +03:00
|
|
|
in maybe False (`elem` customerCountries) employeeCountry
|
|
|
|
|
|
|
|
let expectedEmployees = filter filterEmployeesByCustomerCountry $ Data.filterColumnsByQueryFields (query ^. qrQuery) . joinInCustomers <$> Data.employeesAsJson
|
2022-07-20 08:20:49 +03:00
|
|
|
Data.responseRows receivedEmployees `shouldBe` expectedEmployees
|
|
|
|
_qrAggregates receivedEmployees `shouldBe` Nothing
|
2022-06-24 09:58:25 +03:00
|
|
|
|
|
|
|
albumsWithArtistQuery :: (Query -> Query) -> QueryRequest
|
2022-04-10 07:47:15 +03:00
|
|
|
albumsWithArtistQuery modifySubquery =
|
2022-06-24 09:58:25 +03:00
|
|
|
let artistsSubquery = modifySubquery artistsQuery
|
2022-04-10 07:47:15 +03:00
|
|
|
fields =
|
2022-06-08 18:31:28 +03:00
|
|
|
KeyMap.fromList
|
2022-06-13 23:58:44 +03:00
|
|
|
[ ("AlbumId", columnField "AlbumId"),
|
|
|
|
("Title", columnField "Title"),
|
2022-06-24 09:58:25 +03:00
|
|
|
("Artist", RelField $ RelationshipField artistRelationshipName artistsSubquery)
|
2022-04-10 07:47:15 +03:00
|
|
|
]
|
2022-07-20 08:20:49 +03:00
|
|
|
query = albumsQuery {_qFields = Just fields}
|
2022-06-24 09:58:25 +03:00
|
|
|
in QueryRequest albumsTableName [albumsTableRelationships] query
|
2022-04-10 07:47:15 +03:00
|
|
|
|
2022-06-24 09:58:25 +03:00
|
|
|
artistsWithAlbumsQuery :: (Query -> Query) -> QueryRequest
|
2022-04-10 07:47:15 +03:00
|
|
|
artistsWithAlbumsQuery modifySubquery =
|
2022-06-24 09:58:25 +03:00
|
|
|
let albumFields = KeyMap.fromList [("AlbumId", columnField "AlbumId"), ("Title", columnField "Title")]
|
2022-06-13 23:58:44 +03:00
|
|
|
albumsSort = OrderBy (ColumnName "AlbumId") Ascending :| []
|
2022-07-20 08:20:49 +03:00
|
|
|
albumsSubquery = albumsQuery & qFields .~ Just albumFields & qOrderBy .~ Just albumsSort & modifySubquery
|
2022-04-10 07:47:15 +03:00
|
|
|
fields =
|
2022-06-08 18:31:28 +03:00
|
|
|
KeyMap.fromList
|
2022-06-13 23:58:44 +03:00
|
|
|
[ ("ArtistId", columnField "ArtistId"),
|
|
|
|
("Name", columnField "Name"),
|
2022-06-24 09:58:25 +03:00
|
|
|
("Albums", RelField $ RelationshipField albumsRelationshipName albumsSubquery)
|
2022-04-10 07:47:15 +03:00
|
|
|
]
|
2022-07-20 08:20:49 +03:00
|
|
|
query = artistsQuery {_qFields = Just fields}
|
2022-06-24 09:58:25 +03:00
|
|
|
in QueryRequest artistsTableName [artistsTableRelationships] query
|
|
|
|
|
|
|
|
employeesWithCustomersQuery :: (Query -> Query) -> QueryRequest
|
|
|
|
employeesWithCustomersQuery modifySubquery =
|
|
|
|
let customersSort = OrderBy (ColumnName "CustomerId") Ascending :| []
|
|
|
|
customersSubquery = customersQuery & qOrderBy .~ Just customersSort & modifySubquery
|
|
|
|
fields =
|
2022-07-20 08:20:49 +03:00
|
|
|
Data.queryFields employeesQuery
|
2022-06-24 09:58:25 +03:00
|
|
|
<> KeyMap.fromList
|
|
|
|
[ ("SupportRepForCustomers", RelField $ RelationshipField supportRepForCustomersRelationshipName customersSubquery)
|
|
|
|
]
|
2022-07-20 08:20:49 +03:00
|
|
|
query = employeesQuery {_qFields = Just fields}
|
2022-06-24 09:58:25 +03:00
|
|
|
in QueryRequest employeesTableName [employeesTableRelationships] query
|
|
|
|
|
|
|
|
customersWithSupportRepQuery :: (Query -> Query) -> QueryRequest
|
|
|
|
customersWithSupportRepQuery modifySubquery =
|
|
|
|
let supportRepSubquery = employeesQuery & modifySubquery
|
|
|
|
fields =
|
2022-07-20 08:20:49 +03:00
|
|
|
Data.queryFields customersQuery
|
2022-06-24 09:58:25 +03:00
|
|
|
<> KeyMap.fromList
|
|
|
|
[ ("SupportRep", RelField $ RelationshipField supportRepRelationshipName supportRepSubquery)
|
|
|
|
]
|
2022-07-20 08:20:49 +03:00
|
|
|
query = customersQuery {_qFields = Just fields}
|
2022-06-24 09:58:25 +03:00
|
|
|
in QueryRequest customersTableName [customersTableRelationships] query
|
|
|
|
|
|
|
|
artistsTableName :: TableName
|
|
|
|
artistsTableName = TableName "Artist"
|
|
|
|
|
|
|
|
albumsTableName :: TableName
|
|
|
|
albumsTableName = TableName "Album"
|
|
|
|
|
|
|
|
customersTableName :: TableName
|
|
|
|
customersTableName = TableName "Customer"
|
|
|
|
|
|
|
|
employeesTableName :: TableName
|
|
|
|
employeesTableName = TableName "Employee"
|
|
|
|
|
|
|
|
artistRelationshipName :: RelationshipName
|
|
|
|
artistRelationshipName = RelationshipName "Artist"
|
|
|
|
|
|
|
|
albumsRelationshipName :: RelationshipName
|
|
|
|
albumsRelationshipName = RelationshipName "Albums"
|
|
|
|
|
|
|
|
supportRepForCustomersRelationshipName :: RelationshipName
|
|
|
|
supportRepForCustomersRelationshipName = RelationshipName "SupportRepForCustomers"
|
|
|
|
|
|
|
|
supportRepRelationshipName :: RelationshipName
|
|
|
|
supportRepRelationshipName = RelationshipName "SupportRep"
|
|
|
|
|
|
|
|
artistsTableRelationships :: TableRelationships
|
|
|
|
artistsTableRelationships =
|
|
|
|
let joinFieldMapping =
|
|
|
|
HashMap.fromList
|
|
|
|
[ (ColumnName "ArtistId", ColumnName "ArtistId")
|
|
|
|
]
|
|
|
|
in TableRelationships
|
|
|
|
artistsTableName
|
|
|
|
( HashMap.fromList
|
|
|
|
[ (albumsRelationshipName, Relationship albumsTableName ArrayRelationship joinFieldMapping)
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
albumsTableRelationships :: TableRelationships
|
|
|
|
albumsTableRelationships =
|
|
|
|
let joinFieldMapping =
|
|
|
|
HashMap.fromList
|
|
|
|
[ (ColumnName "ArtistId", ColumnName "ArtistId")
|
|
|
|
]
|
|
|
|
in TableRelationships
|
|
|
|
albumsTableName
|
|
|
|
( HashMap.fromList
|
|
|
|
[ (artistRelationshipName, Relationship artistsTableName ObjectRelationship joinFieldMapping)
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
employeesTableRelationships :: TableRelationships
|
|
|
|
employeesTableRelationships =
|
|
|
|
let joinFieldMapping =
|
|
|
|
HashMap.fromList
|
|
|
|
[ (ColumnName "EmployeeId", ColumnName "SupportRepId")
|
|
|
|
]
|
|
|
|
in TableRelationships
|
|
|
|
employeesTableName
|
|
|
|
( HashMap.fromList
|
|
|
|
[ (supportRepForCustomersRelationshipName, Relationship customersTableName ArrayRelationship joinFieldMapping)
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
customersTableRelationships :: TableRelationships
|
|
|
|
customersTableRelationships =
|
|
|
|
let joinFieldMapping =
|
|
|
|
HashMap.fromList
|
|
|
|
[ (ColumnName "SupportRepId", ColumnName "EmployeeId")
|
|
|
|
]
|
|
|
|
in TableRelationships
|
|
|
|
customersTableName
|
|
|
|
( HashMap.fromList
|
|
|
|
[ (supportRepRelationshipName, Relationship employeesTableName ObjectRelationship joinFieldMapping)
|
|
|
|
]
|
|
|
|
)
|
2022-04-10 07:47:15 +03:00
|
|
|
|
|
|
|
artistsQuery :: Query
|
|
|
|
artistsQuery =
|
2022-06-13 23:58:44 +03:00
|
|
|
let fields = KeyMap.fromList [("ArtistId", columnField "ArtistId"), ("Name", columnField "Name")]
|
2022-07-20 08:20:49 +03:00
|
|
|
in Query (Just fields) Nothing Nothing Nothing Nothing Nothing
|
2022-04-10 07:47:15 +03:00
|
|
|
|
|
|
|
albumsQuery :: Query
|
|
|
|
albumsQuery =
|
2022-06-13 23:58:44 +03:00
|
|
|
let fields = KeyMap.fromList [("AlbumId", columnField "AlbumId"), ("ArtistId", columnField "ArtistId"), ("Title", columnField "Title")]
|
2022-07-20 08:20:49 +03:00
|
|
|
in Query (Just fields) Nothing Nothing Nothing Nothing Nothing
|
2022-06-24 09:58:25 +03:00
|
|
|
|
|
|
|
customersQuery :: Query
|
|
|
|
customersQuery =
|
|
|
|
let fields =
|
|
|
|
KeyMap.fromList
|
|
|
|
[ ("CustomerId", columnField "CustomerId"),
|
|
|
|
("FirstName", columnField "FirstName"),
|
|
|
|
("LastName", columnField "LastName"),
|
|
|
|
("Country", columnField "Country"),
|
|
|
|
("SupportRepId", columnField "SupportRepId")
|
|
|
|
]
|
2022-07-20 08:20:49 +03:00
|
|
|
in Query (Just fields) Nothing Nothing Nothing Nothing Nothing
|
2022-06-24 09:58:25 +03:00
|
|
|
|
|
|
|
employeesQuery :: Query
|
|
|
|
employeesQuery =
|
|
|
|
let fields =
|
|
|
|
KeyMap.fromList
|
|
|
|
[ ("EmployeeId", columnField "EmployeeId"),
|
|
|
|
("FirstName", columnField "FirstName"),
|
|
|
|
("LastName", columnField "LastName"),
|
|
|
|
("Country", columnField "Country")
|
|
|
|
]
|
2022-07-20 08:20:49 +03:00
|
|
|
in Query (Just fields) Nothing Nothing Nothing Nothing Nothing
|
2022-04-10 07:47:15 +03:00
|
|
|
|
|
|
|
columnField :: Text -> Field
|
|
|
|
columnField = ColumnField . ValueWrapper . ColumnName
|
2022-06-24 09:58:25 +03:00
|
|
|
|
|
|
|
comparisonColumn :: [RelationshipName] -> Text -> ComparisonColumn
|
|
|
|
comparisonColumn path columnName = ComparisonColumn path $ ColumnName columnName
|
2022-07-20 08:20:49 +03:00
|
|
|
|
|
|
|
mkSubqueryResponse :: [KeyMap FieldValue] -> FieldValue
|
|
|
|
mkSubqueryResponse rows =
|
|
|
|
RelationshipFieldValue . ValueWrapper $ QueryResponse (Just rows) Nothing
|
|
|
|
|
|
|
|
subqueryRows :: Traversal' FieldValue (KeyMap FieldValue)
|
|
|
|
subqueryRows = _RelationshipFieldValue . vwValue . qrRows . _Just . traverse
|