From 32c7e32bdfc9146a11c818d83af84953d43edd8e Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Fri, 23 Mar 2018 12:37:07 -0500 Subject: [PATCH] Allow embeds alias to be used in filters, fix #821 --- CHANGELOG.md | 1 + src/PostgREST/DbRequestBuilder.hs | 22 +++--------- test/Feature/QuerySpec.hs | 57 +++++++++++++++++++++++++++++++ test/fixtures/data.sql | 9 +++++ test/fixtures/privileges.sql | 2 ++ test/fixtures/schema.sql | 10 ++++++ 6 files changed, 84 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1813aa82e..4089a040e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - The configuration (e.g. `postgrest.conf`) now accepts arbitrary settings that will be passed through as session-local database settings. This can be used to pass in secret keys directly as strings, or via OS environment variables. For instance: `app.settings.jwt_secret = "$(MYAPP_JWT_SECRET)"` will take `MYAPP_JWT_SECRET` from the environment and make it available to postgresql functions as `current_setting('app.settings.jwt_secret')`. Only `app.settings.*` values in the configuration file are treated in this way. - @canadaduane - #256, Add support for bulk UPSERT with POST and single UPSERT with PUT - @steve-chavez - #1078, Add ability to specify source column in embed - @steve-chavez +- #821, Allow embeds alias to be used in filters - @steve-chavez ### Fixed diff --git a/src/PostgREST/DbRequestBuilder.hs b/src/PostgREST/DbRequestBuilder.hs index e664a11ea..495b5b692 100644 --- a/src/PostgREST/DbRequestBuilder.hs +++ b/src/PostgREST/DbRequestBuilder.hs @@ -291,25 +291,13 @@ addLogicTree :: (EmbedPath, LogicTree) -> ReadRequest -> ReadRequest addLogicTree = addProperty addLogicTreeToNode addProperty :: (a -> ReadRequest -> ReadRequest) -> (EmbedPath, a) -> ReadRequest -> ReadRequest -addProperty f ([], a) n = f a n -addProperty f (path, a) (Node rn forest) = - case targetNode of +addProperty f ([], a) rr = f a rr +addProperty f (targetNodeName:remainingPath, a) (Node rn forest) = + case pathNode of Nothing -> Node rn forest -- the property is silenty dropped in the Request does not contain the required path - Just tn -> Node rn (addProperty f (remainingPath, a) tn:restForest) + Just tn -> Node rn (addProperty f (remainingPath, a) tn:delete tn forest) where - targetNodeName:remainingPath = path - (targetNode,restForest) = splitForest targetNodeName forest - splitForest :: NodeName -> Forest ReadNode -> (Maybe ReadRequest, Forest ReadNode) - splitForest name forst = - case maybeNode of - Nothing -> (Nothing,forest) - Just node -> (Just node, delete node forest) - where - maybeNode :: Maybe ReadRequest - maybeNode = find fnd forst - where - fnd :: ReadRequest -> Bool - fnd (Node (_,(n,_,_,_,_)) _) = n == name + pathNode = find (\(Node (_,(nodeName,_,alias,_,_)) _) -> nodeName == targetNodeName || alias == Just targetNodeName) forest mutateRequest :: ApiRequest -> TableName -> [Text] -> [FieldName] -> Either Response MutateRequest mutateRequest apiRequest tName pkCols fldNames = mapLeft apiRequestError $ diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 346e9f7fc..426c19153 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -454,6 +454,63 @@ spec = do [json|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|] { matchHeaders = [matchContentTypeJson] } + context "aliased embeds" $ do + it "works with child relation" $ + get "/space?select=id,zones:zone(id,name),stores:zone(id,name)&zones.zone_type_id=eq.2&stores.zone_type_id=eq.3" `shouldRespondWith` + [json|[ + { "id":1, + "zones": [ {"id":1,"name":"zone 1"}, {"id":2,"name":"zone 2"}], + "stores": [ {"id":3,"name":"store 3"}, {"id":4,"name":"store 4"}]} + ]|] { matchHeaders = [matchContentTypeJson] } + + it "works with many to many relation" $ + get "/users?select=id,designTasks:tasks(id,name),codeTasks:tasks(id,name)&designTasks.name=like.*Design*&codeTasks.name=like.*Code*" `shouldRespondWith` + [json|[ + { "id":1, + "designTasks":[ { "id":1, "name":"Design w7" }, { "id":3, "name":"Design w10" } ], + "codeTasks":[ { "id":2, "name":"Code w7" }, { "id":4, "name":"Code w10" } ] }, + { "id":2, + "designTasks":[ { "id":5, "name":"Design IOS" }, { "id":7, "name":"Design OSX" } ], + "codeTasks":[ { "id":6, "name":"Code IOS" } ] }, + { "id":3, + "designTasks":[ { "id":1, "name":"Design w7" }, { "id":5, "name":"Design IOS" } ], + "codeTasks":[ ] } + ]|] { matchHeaders = [matchContentTypeJson] } + + it "works with an aliased child plus non aliased child" $ + get "/projects?select=id,name,designTasks:tasks{name,users{id,name}}&designTasks.name=like.*Design*&designTasks.users.id=in.(1,2)" `shouldRespondWith` + [json|[ + { + "id":1, "name":"Windows 7", + "designTasks":[ { "name":"Design w7", "users":[ { "id":1, "name":"Angela Martin" } ] } ] }, + { + "id":2, "name":"Windows 10", + "designTasks":[ { "name":"Design w10", "users":[ { "id":1, "name":"Angela Martin" } ] } ] }, + { + "id":3, "name":"IOS", + "designTasks":[ { "name":"Design IOS", "users":[ { "id":2, "name":"Michael Scott" } ] } ] }, + { + "id":4, "name":"OSX", + "designTasks":[ { "name":"Design OSX", "users":[ { "id":2, "name":"Michael Scott" } ] } ] }, + { + "id":5, "name":"Orphan", + "designTasks":[ ] } + ]|] { matchHeaders = [matchContentTypeJson] } + + it "works with two aliased childs embeds plus and/or" $ + get "/entities?select=id,childs:child_entities{id,gChilds:grandchild_entities{id}}&childs.and=(id.in.(1,2,3))&childs.gChilds.or=(id.eq.1,id.eq.2)" `shouldRespondWith` + [json|[ + { "id":1, + "childs":[ + {"id":1,"gChilds":[{"id":1}, {"id":2}]}, + {"id":2,"gChilds":[]}]}, + { "id":2, + "childs":[ + {"id":3,"gChilds":[]}]}, + { "id":3,"childs":[]}, + { "id":4,"childs":[]} + ]|] { matchHeaders = [matchContentTypeJson] } + context "tables with self reference foreign keys" $ do context "one self reference foreign key" $ do it "embeds parents recursively" $ diff --git a/test/fixtures/data.sql b/test/fixtures/data.sql index 833b2ac0b..02b1522fe 100644 --- a/test/fixtures/data.sql +++ b/test/fixtures/data.sql @@ -399,3 +399,12 @@ INSERT INTO message VALUES (2, 'Hi John', 2, 1); INSERT INTO message VALUES (3, 'How are you doing?', 1, 2); INSERT INTO message VALUES (4, 'Hey Julie', 3, 4); INSERT INTO message VALUES (5, 'What''s up Jake', 4, 3); + +TRUNCATE TABLE space CASCADE; +INSERT INTO space VALUES (1, 'space 1'); + +TRUNCATE TABLE zone CASCADE; +INSERT INTO zone VALUES (1, 'zone 1', 2, 1); +INSERT INTO zone VALUES (2, 'zone 2', 2, 1); +INSERT INTO zone VALUES (3, 'store 3', 3, 1); +INSERT INTO zone VALUES (4, 'store 4', 3, 1); diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index 1a60c3fd8..8550b3d01 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -76,6 +76,8 @@ GRANT ALL ON TABLE , person , message , person_detail + , space + , zone TO postgrest_test_anonymous; GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous; diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index fbc7bf020..ffce6249f 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -1425,3 +1425,13 @@ create view person_detail as from person p join lateral (select message.sender, count(message.id) as count from message group by message.sender) s on s.sender = p.id join lateral (select message.recipient, count(message.id) as count from message group by message.recipient) r on r.recipient = p.id; + +create table space( + id integer primary key, + name text); + +create table zone( + id integer primary key, + name text, + zone_type_id integer, + space_id integer references space(id));