diff --git a/CHANGELOG.md b/CHANGELOG.md index 8067d5b2c..1813aa82e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,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 ### Fixed diff --git a/src/PostgREST/DbRequestBuilder.hs b/src/PostgREST/DbRequestBuilder.hs index 2c7d6ef26..e664a11ea 100644 --- a/src/PostgREST/DbRequestBuilder.hs +++ b/src/PostgREST/DbRequestBuilder.hs @@ -169,18 +169,32 @@ findRelation schema allRelations nodeTableName parentNodeTableName relationDetai Just rd -> - -- (request) => clients { ..., project.client_id{...} } + -- (request) => clients { ..., projects.client_id{...} } -- will match - -- (relation type) => parent + -- (relation type) => child -- (entity) => clients {id} -- (foriegn entity) => projects {client_id} ( + relType == Child && nodeTableName == tableName relTable && -- match relation table name - parentNodeTableName == tableName relFTable && -- && -- match relation foreign table name + parentNodeTableName == tableName relFTable && -- match relation foreign table name length relColumns == 1 && rd == colName (unsafeHead relColumns) ) || + -- (request) => message { ..., person_detail.sender{...} } + -- will match + -- (relation type) => parent + -- (entity) => message {sender} + -- (foriegn entity) => person_detail {id} + ( + relType == Parent && + nodeTableName == tableName relTable && -- match relation table name + parentNodeTableName == tableName relFTable && -- match relation foreign table name + length relFColumns == 1 && + rd == colName (unsafeHead relFColumns) + ) || + -- (request) => tasks { ..., users.tasks_users{...} } -- will match -- (relation type) => many diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 86b9a1a23..346e9f7fc 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -362,27 +362,16 @@ spec = do [json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|] { matchHeaders = [matchContentTypeJson] } - it "requesting children 2 levels (with relation path fixed)" $ - get "/clients?id=eq.1&select=id,projects:projects.client_id{id,tasks{id}}" `shouldRespondWith` - [json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|] - { matchHeaders = [matchContentTypeJson] } - it "requesting many<->many relation" $ get "/tasks?select=id,users{id}" `shouldRespondWith` [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] } - it "requesting many<->many relation (with relation path fixed)" $ - get "/tasks?select=id,users:users.users_tasks{id}" `shouldRespondWith` - [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] } - it "requesting many<->many relation with rename" $ get "/tasks?id=eq.1&select=id,theUsers:users{id}" `shouldRespondWith` [json|[{"id":1,"theUsers":[{"id":1},{"id":3}]}]|] { matchHeaders = [matchContentTypeJson] } - it "requesting many<->many relation reverse" $ get "/users?select=id,tasks{id}" `shouldRespondWith` [json|[{"id":1,"tasks":[{"id":1},{"id":2},{"id":3},{"id":4}]},{"id":2,"tasks":[{"id":5},{"id":6},{"id":7}]},{"id":3,"tasks":[{"id":1},{"id":5}]}]|] @@ -426,7 +415,7 @@ spec = do get "/projects?id=in.1,3&select=id,name,client_id{id,name}" `shouldRespondWith` [json|[{"id":1,"name":"Windows 7","client_id":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":{"id":2,"name":"Apple"}}]|] { matchHeaders = [matchContentTypeJson] } - + it "can embed by FK column name and select the FK value at the same time, if aliased" $ get "/projects?id=in.1,3&select=id,name,client_id,client:client_id{id,name}" `shouldRespondWith` [json|[{"id":1,"name":"Windows 7","client_id":1,"client":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client":{"id":2,"name":"Apple"}}]|] @@ -440,6 +429,31 @@ spec = do it "can detect fk relations through views to tables in the public schema" $ get "/consumers_view?select=*,orders_view{*}" `shouldRespondWith` 200 + context "path fixed" $ do + it "works when requesting children 2 levels" $ + get "/clients?id=eq.1&select=id,projects:projects.client_id{id,tasks{id}}" `shouldRespondWith` + [json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|] + { matchHeaders = [matchContentTypeJson] } + + it "works with parent relation" $ do + get "/message?select=id,body,sender:person_detail.sender(name,sent),recipient:person_detail.recipient(name,received)&id=lt.4" `shouldRespondWith` + [json| + [{"id":1,"body":"Hello Jane","sender":{"name":"John","sent":2},"recipient":{"name":"Jane","received":2}}, + {"id":2,"body":"Hi John","sender":{"name":"Jane","sent":1},"recipient":{"name":"John","received":1}}, + {"id":3,"body":"How are you doing?","sender":{"name":"John","sent":2},"recipient":{"name":"Jane","received":2}}] |] + { matchHeaders = [matchContentTypeJson] } + get "/message?select=id,body,sender:person.sender(name),recipient:person.recipient(name)&id=lt.4" `shouldRespondWith` + [json| + [{"id":1,"body":"Hello Jane","sender":{"name":"John"},"recipient":{"name":"Jane"}}, + {"id":2,"body":"Hi John","sender":{"name":"Jane"},"recipient":{"name":"John"}}, + {"id":3,"body":"How are you doing?","sender":{"name":"John"},"recipient":{"name":"Jane"}}] |] + { matchHeaders = [matchContentTypeJson] } + + it "works with many<->many relation" $ + get "/tasks?select=id,users:users.users_tasks{id}" `shouldRespondWith` + [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 "tables with self reference foreign keys" $ do context "one self reference foreign key" $ do it "embeds parents recursively" $ @@ -799,7 +813,7 @@ spec = do { matchStatus = 200 , matchHeaders = [ matchContentTypeJson ] } - + describe "values with quotes in IN and NOT IN" $ do it "succeeds when only quoted values are present" $ do get "/w_or_wo_comma_names?name=in.\"Hebdon, John\"" `shouldRespondWith` diff --git a/test/fixtures/data.sql b/test/fixtures/data.sql index 725838ca3..833b2ac0b 100644 --- a/test/fixtures/data.sql +++ b/test/fixtures/data.sql @@ -383,3 +383,19 @@ INSERT INTO books VALUES (6, 'Lord of the Flies', 1954, 6); INSERT INTO books VALUES (7, 'To Kill a Mockingbird', 1960, 7); INSERT INTO books VALUES (8, 'Slaughterhouse-Five', 1969, 8); INSERT INTO books VALUES (9, 'One Flew Over the Cuckoo''s Nest', 1962, 9); + +SET search_path = test, pg_catalog; + +TRUNCATE TABLE person CASCADE; + +INSERT INTO person VALUES (1, 'John'); +INSERT INTO person VALUES (2, 'Jane'); +INSERT INTO person VALUES (3, 'Jake'); +INSERT INTO person VALUES (4, 'Julie'); + +TRUNCATE TABLE message CASCADE; +INSERT INTO message VALUES (1, 'Hello Jane', 1, 2); +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); diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index 57aeda663..1a60c3fd8 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -73,6 +73,9 @@ GRANT ALL ON TABLE , forties_books , fifties_books , sixties_books + , person + , message + , person_detail 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 8dc31fcb3..fbc7bf020 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -1409,3 +1409,19 @@ create view test.books as select id, title, publication_year, author_id from pri create view test.forties_books as select id, title, publication_year, author_id from private.books where publication_year >= 1940 and publication_year < 1950; create view test.fifties_books as select id, title, publication_year, author_id from private.books where publication_year >= 1950 and publication_year < 1960; create view test.sixties_books as select id, title, publication_year, author_id from private.books where publication_year >= 1960 and publication_year < 1970; + +create table person ( + id integer primary key, + name character varying not null); + +create table message ( + id integer primary key, + body text not null default '', + sender bigint not null references person(id), + recipient bigint not null references person(id)); + +create view person_detail as + select p.id, p.name, s.count as sent, r.count as received + 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;