Allow specifying source column in embed, fix #1078
This commit is contained in:
committed by
Steve Chávez
parent
50512e1117
commit
062a5581f5
@@ -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
|
- 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
|
- #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
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -169,18 +169,32 @@ findRelation schema allRelations nodeTableName parentNodeTableName relationDetai
|
|||||||
|
|
||||||
Just rd ->
|
Just rd ->
|
||||||
|
|
||||||
-- (request) => clients { ..., project.client_id{...} }
|
-- (request) => clients { ..., projects.client_id{...} }
|
||||||
-- will match
|
-- will match
|
||||||
-- (relation type) => parent
|
-- (relation type) => child
|
||||||
-- (entity) => clients {id}
|
-- (entity) => clients {id}
|
||||||
-- (foriegn entity) => projects {client_id}
|
-- (foriegn entity) => projects {client_id}
|
||||||
(
|
(
|
||||||
|
relType == Child &&
|
||||||
nodeTableName == tableName relTable && -- match relation table name
|
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 &&
|
length relColumns == 1 &&
|
||||||
rd == colName (unsafeHead relColumns)
|
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{...} }
|
-- (request) => tasks { ..., users.tasks_users{...} }
|
||||||
-- will match
|
-- will match
|
||||||
-- (relation type) => many
|
-- (relation type) => many
|
||||||
|
|||||||
+27
-13
@@ -362,27 +362,16 @@ spec = do
|
|||||||
[json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
[json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ 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" $
|
it "requesting many<->many relation" $
|
||||||
get "/tasks?select=id,users{id}" `shouldRespondWith`
|
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":[]}]|]
|
[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] }
|
{ 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" $
|
it "requesting many<->many relation with rename" $
|
||||||
get "/tasks?id=eq.1&select=id,theUsers:users{id}" `shouldRespondWith`
|
get "/tasks?id=eq.1&select=id,theUsers:users{id}" `shouldRespondWith`
|
||||||
[json|[{"id":1,"theUsers":[{"id":1},{"id":3}]}]|]
|
[json|[{"id":1,"theUsers":[{"id":1},{"id":3}]}]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
|
||||||
it "requesting many<->many relation reverse" $
|
it "requesting many<->many relation reverse" $
|
||||||
get "/users?select=id,tasks{id}" `shouldRespondWith`
|
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}]}]|]
|
[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`
|
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"}}]|]
|
[json|[{"id":1,"name":"Windows 7","client_id":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":{"id":2,"name":"Apple"}}]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "can embed by FK column name and select the FK value at the same time, if aliased" $
|
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`
|
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"}}]|]
|
[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" $
|
it "can detect fk relations through views to tables in the public schema" $
|
||||||
get "/consumers_view?select=*,orders_view{*}" `shouldRespondWith` 200
|
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 "tables with self reference foreign keys" $ do
|
||||||
context "one self reference foreign key" $ do
|
context "one self reference foreign key" $ do
|
||||||
it "embeds parents recursively" $
|
it "embeds parents recursively" $
|
||||||
@@ -799,7 +813,7 @@ spec = do
|
|||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
, matchHeaders = [ matchContentTypeJson ]
|
, matchHeaders = [ matchContentTypeJson ]
|
||||||
}
|
}
|
||||||
|
|
||||||
describe "values with quotes in IN and NOT IN" $ do
|
describe "values with quotes in IN and NOT IN" $ do
|
||||||
it "succeeds when only quoted values are present" $ do
|
it "succeeds when only quoted values are present" $ do
|
||||||
get "/w_or_wo_comma_names?name=in.\"Hebdon, John\"" `shouldRespondWith`
|
get "/w_or_wo_comma_names?name=in.\"Hebdon, John\"" `shouldRespondWith`
|
||||||
|
|||||||
Vendored
+16
@@ -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 (7, 'To Kill a Mockingbird', 1960, 7);
|
||||||
INSERT INTO books VALUES (8, 'Slaughterhouse-Five', 1969, 8);
|
INSERT INTO books VALUES (8, 'Slaughterhouse-Five', 1969, 8);
|
||||||
INSERT INTO books VALUES (9, 'One Flew Over the Cuckoo''s Nest', 1962, 9);
|
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);
|
||||||
|
|||||||
Vendored
+3
@@ -73,6 +73,9 @@ GRANT ALL ON TABLE
|
|||||||
, forties_books
|
, forties_books
|
||||||
, fifties_books
|
, fifties_books
|
||||||
, sixties_books
|
, sixties_books
|
||||||
|
, person
|
||||||
|
, message
|
||||||
|
, person_detail
|
||||||
TO postgrest_test_anonymous;
|
TO postgrest_test_anonymous;
|
||||||
|
|
||||||
GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
|
GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
|
||||||
|
|||||||
Vendored
+16
@@ -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.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.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 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user