diff --git a/server/lib/dc-api/test/Test/Data.hs b/server/lib/dc-api/test/Test/Data.hs index 7e9d1b37924..22498e70e28 100644 --- a/server/lib/dc-api/test/Test/Data.hs +++ b/server/lib/dc-api/test/Test/Data.hs @@ -25,6 +25,7 @@ module Test.Data _ColumnFieldNumber, _ColumnFieldString, _ColumnFieldBoolean, + _ColumnFieldNull, _RelationshipFieldRows, orderByColumn, ) @@ -36,7 +37,7 @@ import Control.Arrow (first, (>>>)) import Control.Lens (Index, IxValue, Ixed, Traversal', ix, lens, (%~), (&), (^.), (^..), (^?), _Just) import Data.Aeson (eitherDecodeStrict) import Data.Aeson qualified as J -import Data.Aeson.Lens (_Bool, _Number, _String) +import Data.Aeson.Lens (_Bool, _Null, _Number, _String) import Data.Bifunctor (bimap) import Data.ByteString (ByteString) import Data.ByteString.Lazy qualified as BSL @@ -518,6 +519,9 @@ _ColumnFieldString = API._ColumnFieldValue . _String _ColumnFieldBoolean :: Traversal' API.FieldValue Bool _ColumnFieldBoolean = API._ColumnFieldValue . _Bool +_ColumnFieldNull :: Traversal' API.FieldValue () +_ColumnFieldNull = API._ColumnFieldValue . _Null + _RelationshipFieldRows :: Traversal' API.FieldValue [HashMap API.FieldName API.FieldValue] _RelationshipFieldRows = API._RelationshipFieldValue . API.qrRows . _Just diff --git a/server/lib/dc-api/test/Test/Specs/QuerySpec/OrderBySpec.hs b/server/lib/dc-api/test/Test/Specs/QuerySpec/OrderBySpec.hs index e2e991ee243..efece60415d 100644 --- a/server/lib/dc-api/test/Test/Specs/QuerySpec/OrderBySpec.hs +++ b/server/lib/dc-api/test/Test/Specs/QuerySpec/OrderBySpec.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE QuasiQuotes #-} + module Test.Specs.QuerySpec.OrderBySpec (spec) where import Control.Arrow ((>>>)) @@ -12,6 +14,7 @@ import Data.List.NonEmpty qualified as NonEmpty import Data.Maybe (fromMaybe, isJust) import Data.Ord (Down (..)) import Hasura.Backends.DataConnector.API +import Language.GraphQL.Draft.Syntax.QQ qualified as G import Test.AgentAPI (queryGuarded) import Test.Data (TestData (..)) import Test.Data qualified as Data @@ -357,6 +360,49 @@ spec TestData {..} Capabilities {..} = describe "Order By in Queries" $ do Data.responseRows receivedAlbums `rowsShouldBe` expectedArtists _qrAggregates receivedAlbums `jsonShouldBe` Nothing + + it "can order results by an aggregate function applied to a column of a related table" $ do + -- Order artists by their highest album id descending, but only artists that have any albums + let orderByRelations = + HashMap.fromList + [ ( _tdAlbumsRelationshipName, + OrderByRelation + Nothing + mempty + ) + ] + let orderBy = + OrderBy orderByRelations $ + NonEmpty.fromList + [ OrderByElement [_tdAlbumsRelationshipName] (orderBySingleColumnAggregateMax (_tdColumnName "AlbumId") albumIdNameScalarType) Descending + ] + let whereExp = + Exists + (RelatedTable _tdAlbumsRelationshipName) + (Not $ ApplyUnaryComparisonOperator IsNull (_tdCurrentComparisonColumn "AlbumId" albumIdNameScalarType)) + let query = + artistsQueryRequest + & qrQuery . qOrderBy ?~ orderBy + & qrQuery . qWhere ?~ whereExp + & qrTableRelationships + .~ [ Data.onlyKeepRelationships [_tdAlbumsRelationshipName] _tdArtistsTableRelationships + ] + receivedArtists <- queryGuarded query + + let findRelatedAlbums (artist :: HashMap FieldName FieldValue) = fromMaybe [] do + artistId <- artist ^? Data.field "ArtistId" . Data._ColumnFieldNumber + pure $ filter (\album -> album ^? Data.field "ArtistId" . Data._ColumnFieldNumber == Just artistId) _tdAlbumsRows + + let expectedArtists = + _tdArtistsRows + & fmap (\artist -> (artist, findRelatedAlbums artist)) + & filter (\(_artist, albums) -> any (\album -> album ^? Data.field "AlbumId" . Data._ColumnFieldNull /= Just ()) albums) + & fmap (\(artist, albums) -> (artist, maximum $ (albums & fmap (\album -> album ^? Data.field "AlbumId" . Data._ColumnFieldNumber)))) + & sortOn (\row -> (Down (row ^. _2))) + & fmap (^. _1) + + Data.responseRows receivedArtists `rowsShouldBe` expectedArtists + _qrAggregates receivedArtists `jsonShouldBe` Nothing where albumsQuery :: Query albumsQuery = @@ -388,7 +434,11 @@ spec TestData {..} Capabilities {..} = describe "Order By in Queries" $ do query = Data.emptyQuery & qFields ?~ fields in QueryRequest _tdInvoicesTableName [] query + orderBySingleColumnAggregateMax :: ColumnName -> ScalarType -> OrderByTarget + orderBySingleColumnAggregateMax columnName resultType = OrderBySingleColumnAggregate $ SingleColumnAggregate (SingleColumnAggregateFunction [G.name|max|]) columnName resultType + albumTitleScalarType = _tdFindColumnScalarType _tdAlbumsTableName "Title" + albumIdNameScalarType = _tdFindColumnScalarType _tdAlbumsTableName "AlbumId" artistNameScalarType = _tdFindColumnScalarType _tdArtistsTableName "Name" data NullableOrdered a