From a5465e20d0a087ebcdde6f54c1fc1d10ebd0ca4b Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Mon, 5 Dec 2022 18:31:02 -0500 Subject: [PATCH] correct spread embed to '...' --- CHANGELOG.md | 2 +- src/PostgREST/ApiRequest/QueryParams.hs | 16 ++++++++-------- .../Feature/Query/EmbedDisambiguationSpec.hs | 12 ++++++------ test/spec/Feature/Query/SpreadQueriesSpec.hs | 18 +++++++++--------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b6fb0e8d..3347dc76c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #1414, Add related orders - @steve-chavez + On a many-to-one or one-to-one relationship, you can order a parent by a child column `/projects?select=*,clients(*)&order=clients(name).desc.nullsfirst` - #1233, #1907, #2566, Allow spreading embedded resources - @steve-chavez - + On a many-to-one or one-to-one relationship, you can unnest a json object with `/projects?select=*,..clients(client_name:name)` + + On a many-to-one or one-to-one relationship, you can unnest a json object with `/projects?select=*,...clients(client_name:name)` + Allows including the join table columns when resource embedding + Allows disambiguating a recursive m2m embed + Allows disambiguating an embed that has a many-to-many relationship using two foreign keys on a junction diff --git a/src/PostgREST/ApiRequest/QueryParams.hs b/src/PostgREST/ApiRequest/QueryParams.hs index 9bc0a299e..0fd1e3255 100644 --- a/src/PostgREST/ApiRequest/QueryParams.hs +++ b/src/PostgREST/ApiRequest/QueryParams.hs @@ -320,7 +320,7 @@ pTreePath = do -- >>> P.parse pFieldForest "" "*,client(*,nested(*))" -- Right [Node {rootLabel = SelectField {selField = ("*",[]), selCast = Nothing, selAlias = Nothing}, subForest = []},Node {rootLabel = SelectRelation {selRelation = "client", selAlias = Nothing, selHint = Nothing, selJoinType = Nothing}, subForest = [Node {rootLabel = SelectField {selField = ("*",[]), selCast = Nothing, selAlias = Nothing}, subForest = []},Node {rootLabel = SelectRelation {selRelation = "nested", selAlias = Nothing, selHint = Nothing, selJoinType = Nothing}, subForest = [Node {rootLabel = SelectField {selField = ("*",[]), selCast = Nothing, selAlias = Nothing}, subForest = []}]}]}] -- --- >>> P.parse pFieldForest "" "*,..client(*),other(*)" +-- >>> P.parse pFieldForest "" "*,...client(*),other(*)" -- Right [Node {rootLabel = SelectField {selField = ("*",[]), selCast = Nothing, selAlias = Nothing}, subForest = []},Node {rootLabel = SpreadRelation {selRelation = "client", selHint = Nothing, selJoinType = Nothing}, subForest = [Node {rootLabel = SelectField {selField = ("*",[]), selCast = Nothing, selAlias = Nothing}, subForest = []}]},Node {rootLabel = SelectRelation {selRelation = "other", selAlias = Nothing, selHint = Nothing, selJoinType = Nothing}, subForest = [Node {rootLabel = SelectField {selField = ("*",[]), selCast = Nothing, selAlias = Nothing}, subForest = []}]}] -- -- >>> P.parse pFieldForest "" "" @@ -542,10 +542,10 @@ pFieldSelect = lexeme $ try (do -- | -- Parse spread relations in select -- --- >>> P.parse pSpreadRelationSelect "" "..rel(*)" +-- >>> P.parse pSpreadRelationSelect "" "...rel(*)" -- Right (SpreadRelation {selRelation = "rel", selHint = Nothing, selJoinType = Nothing}) -- --- >>> P.parse pSpreadRelationSelect "" "..rel!hint!inner(*)" +-- >>> P.parse pSpreadRelationSelect "" "...rel!hint!inner(*)" -- Right (SpreadRelation {selRelation = "rel", selHint = Just "hint", selJoinType = Just JTInner}) -- -- >>> P.parse pSpreadRelationSelect "" "rel(*)" @@ -553,17 +553,17 @@ pFieldSelect = lexeme $ try (do -- unexpected "r" -- expecting "..." -- --- >>> P.parse pSpreadRelationSelect "" "alias:..rel(*)" +-- >>> P.parse pSpreadRelationSelect "" "alias:...rel(*)" -- Left (line 1, column 1): -- unexpected "a" --- expecting ".." +-- expecting "..." -- --- >>> P.parse pSpreadRelationSelect "" "..rel->jsonpath(*)" --- Left (line 1, column 8): +-- >>> P.parse pSpreadRelationSelect "" "...rel->jsonpath(*)" +-- Left (line 1, column 9): -- unexpected '>' pSpreadRelationSelect :: Parser SelectItem pSpreadRelationSelect = lexeme $ do - name <- string ".." >> pFieldName + name <- string "..." >> pFieldName (hint, jType) <- pEmbedParams try (void $ lookAhead (string "(")) return $ SpreadRelation name hint jType diff --git a/test/spec/Feature/Query/EmbedDisambiguationSpec.hs b/test/spec/Feature/Query/EmbedDisambiguationSpec.hs index a72b1c592..ffacc04e8 100644 --- a/test/spec/Feature/Query/EmbedDisambiguationSpec.hs +++ b/test/spec/Feature/Query/EmbedDisambiguationSpec.hs @@ -104,18 +104,18 @@ spec = , matchHeaders = [matchContentTypeJson] } -- Each of those 4 possibilities can be done with spread embeds, by following the details in the error above - get "/whatev_sites?select=*,whatev_jobs!site_id_1(..whatev_projects!project_id_1(*))" `shouldRespondWith` [json|[]|] - get "/whatev_sites?select=*,whatev_jobs!site_id_1(..whatev_projects!project_id_2(*))" `shouldRespondWith` [json|[]|] - get "/whatev_sites?select=*,whatev_jobs!site_id_2(..whatev_projects!project_id_1(*))" `shouldRespondWith` [json|[]|] - get "/whatev_sites?select=*,whatev_jobs!site_id_2(..whatev_projects!project_id_2(*))" `shouldRespondWith` [json|[]|] + get "/whatev_sites?select=*,whatev_jobs!site_id_1(...whatev_projects!project_id_1(*))" `shouldRespondWith` [json|[]|] + get "/whatev_sites?select=*,whatev_jobs!site_id_1(...whatev_projects!project_id_2(*))" `shouldRespondWith` [json|[]|] + get "/whatev_sites?select=*,whatev_jobs!site_id_2(...whatev_projects!project_id_1(*))" `shouldRespondWith` [json|[]|] + get "/whatev_sites?select=*,whatev_jobs!site_id_2(...whatev_projects!project_id_2(*))" `shouldRespondWith` [json|[]|] it "can disambiguate a recursive m2m with spread embeds" $ do - get "/posters?select=*,subscribers:subscriptions!subscribed(..posters!subscriber(*))&limit=1" `shouldRespondWith` + get "/posters?select=*,subscribers:subscriptions!subscribed(...posters!subscriber(*))&limit=1" `shouldRespondWith` [json| [ {"id":1,"name":"Mark","subscribers":[{"id":3,"name":"Bill"}, {"id":4,"name":"Jeff"}]}]|] { matchStatus = 200 , matchHeaders = [matchContentTypeJson] } - get "/posters?select=*,subscriptions!subscriber(..posters!subscribed(*))&limit=1" `shouldRespondWith` + get "/posters?select=*,subscriptions!subscriber(...posters!subscribed(*))&limit=1" `shouldRespondWith` [json| [{"id":1,"name":"Mark","subscriptions":[{"id":2,"name":"Elon"}]}]|] { matchStatus = 200 , matchHeaders = [matchContentTypeJson] diff --git a/test/spec/Feature/Query/SpreadQueriesSpec.hs b/test/spec/Feature/Query/SpreadQueriesSpec.hs index c9369e28f..07a9c9d6d 100644 --- a/test/spec/Feature/Query/SpreadQueriesSpec.hs +++ b/test/spec/Feature/Query/SpreadQueriesSpec.hs @@ -13,7 +13,7 @@ spec :: SpecWith ((), Application) spec = describe "spread embeds" $ do it "works on a many-to-one relationship" $ do - get "/projects?select=id,..clients(client_name:name)" `shouldRespondWith` + get "/projects?select=id,...clients(client_name:name)" `shouldRespondWith` [json|[ {"id":1,"client_name":"Microsoft"}, {"id":2,"client_name":"Microsoft"}, @@ -24,7 +24,7 @@ spec = { matchStatus = 200 , matchHeaders = [matchContentTypeJson] } - get "/grandchild_entities?select=name,..child_entities(parent_name:name,..entities(grandparent_name:name))&limit=3" `shouldRespondWith` + get "/grandchild_entities?select=name,...child_entities(parent_name:name,...entities(grandparent_name:name))&limit=3" `shouldRespondWith` [json|[ {"name":"grandchild entity 1","parent_name":"child entity 1","grandparent_name":"entity 1"}, {"name":"grandchild entity 2","parent_name":"child entity 1","grandparent_name":"entity 1"}, @@ -33,7 +33,7 @@ spec = { matchStatus = 200 , matchHeaders = [matchContentTypeJson] } - get "/videogames?select=name,..computed_designers(designer_name:name)" `shouldRespondWith` + get "/videogames?select=name,...computed_designers(designer_name:name)" `shouldRespondWith` [json|[ {"name":"Civilization I","designer_name":"Sid Meier"}, {"name":"Civilization II","designer_name":"Sid Meier"}, @@ -45,7 +45,7 @@ spec = } it "works inside a normal embed" $ - get "/grandchild_entities?select=name,child_entity:child_entities(name,..entities(parent_name:name))&limit=1" `shouldRespondWith` + get "/grandchild_entities?select=name,child_entity:child_entities(name,...entities(parent_name:name))&limit=1" `shouldRespondWith` [json|[ {"name":"grandchild entity 1","child_entity":{"name":"child entity 1","parent_name":"entity 1"}} ]|] @@ -54,7 +54,7 @@ spec = } it "works on a one-to-one relationship" $ - get "/country?select=name,..capital(capital:name)" `shouldRespondWith` + get "/country?select=name,...capital(capital:name)" `shouldRespondWith` [json|[ {"name":"Afghanistan","capital":"Kabul"}, {"name":"Algeria","capital":"Algiers"} @@ -64,7 +64,7 @@ spec = } it "fails when is not a to-one relationship" $ do - get "/clients?select=*,..projects(*)" `shouldRespondWith` + get "/clients?select=*,...projects(*)" `shouldRespondWith` [json|{ "code":"PGRST119", "details":"'clients' and 'projects' do not form a many-to-one or one-to-one relationship", @@ -74,7 +74,7 @@ spec = { matchStatus = 400 , matchHeaders = [matchContentTypeJson] } - get "/designers?select=*,..computed_videogames(*)" `shouldRespondWith` + get "/designers?select=*,...computed_videogames(*)" `shouldRespondWith` [json|{ "code":"PGRST119", "details":"'designers' and 'computed_videogames' do not form a many-to-one or one-to-one relationship", @@ -86,7 +86,7 @@ spec = } it "can include or exclude attributes of the junction on a m2m" $ do - get "/users?select=*,tasks:users_tasks(*,..tasks(*))&limit=1" `shouldRespondWith` + get "/users?select=*,tasks:users_tasks(*,...tasks(*))&limit=1" `shouldRespondWith` [json|[{ "id":1,"name":"Angela Martin", "tasks": [ @@ -99,7 +99,7 @@ spec = { matchStatus = 200 , matchHeaders = [matchContentTypeJson] } - get "/users?select=*,tasks:users_tasks(..tasks(*))&limit=1" `shouldRespondWith` + get "/users?select=*,tasks:users_tasks(...tasks(*))&limit=1" `shouldRespondWith` [json|[{ "id":1,"name":"Angela Martin", "tasks":[