From c7666c0a672b376721e9ff0729a6a0fbd807b4e0 Mon Sep 17 00:00:00 2001 From: Ruslan Talpa Date: Thu, 1 Oct 2015 14:20:40 +0300 Subject: [PATCH] tests for table relations & bugfix for not detecting child relations of view --- src/PostgREST/PgStructure.hs | 71 +++++++++++++++++----------- test/Feature/QuerySpec.hs | 20 ++++++++ test/Feature/StructureSpec.hs | 7 +++ test/fixtures/schema.sql | 87 +++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 28 deletions(-) diff --git a/src/PostgREST/PgStructure.hs b/src/PostgREST/PgStructure.hs index 3d4f7f0c1..8ed08873b 100644 --- a/src/PostgREST/PgStructure.hs +++ b/src/PostgREST/PgStructure.hs @@ -94,34 +94,49 @@ allTables = do allRelations :: H.Tx P.Postgres s [Relation] allRelations = do rels <- H.listEx $ [H.stmt| - WITH table_fk AS ( - SELECT DISTINCT - tc.table_schema, tc.table_name, kcu.column_name, - ccu.table_name AS foreign_table_name, - ccu.column_name AS foreign_column_name - FROM information_schema.table_constraints AS tc - JOIN information_schema.key_column_usage AS kcu on tc.constraint_name = kcu.constraint_name - JOIN information_schema.constraint_column_usage AS ccu on ccu.constraint_name = tc.constraint_name - WHERE constraint_type = 'FOREIGN KEY' - AND tc.table_schema NOT IN ('pg_catalog', 'information_schema') - ORDER BY tc.table_schema, tc.table_name, kcu.column_name - ) - SELECT * FROM table_fk - UNION - ( - SELECT DISTINCT - vcu.table_schema, vcu.view_name AS table_name, vcu.column_name, - table_fk.foreign_table_name, - table_fk.foreign_column_name - FROM information_schema.view_column_usage as vcu - JOIN table_fk ON - table_fk.table_schema = vcu.view_schema AND - table_fk.table_name = vcu.table_name AND - table_fk.column_name = vcu.column_name - WHERE vcu.view_schema NOT IN ('pg_catalog', 'information_schema') - ORDER BY vcu.table_schema, vcu.view_name, vcu.column_name - ) - + WITH table_fk AS ( + SELECT + tc.table_schema, tc.table_name, kcu.column_name, + ccu.table_name AS foreign_table_name, + ccu.column_name AS foreign_column_name + FROM information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu on tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu on ccu.constraint_name = tc.constraint_name + WHERE constraint_type = 'FOREIGN KEY' + AND tc.table_schema NOT IN ('pg_catalog', 'information_schema') + ORDER BY tc.table_schema, tc.table_name, kcu.column_name + ) + SELECT * FROM table_fk + UNION + ( + SELECT + vcu.table_schema, vcu.view_name AS table_name, vcu.column_name, + table_fk.foreign_table_name, + table_fk.foreign_column_name + FROM information_schema.view_column_usage as vcu + JOIN table_fk ON + table_fk.table_schema = vcu.view_schema AND + table_fk.table_name = vcu.table_name AND + table_fk.column_name = vcu.column_name + WHERE vcu.view_schema NOT IN ('pg_catalog', 'information_schema') + ORDER BY vcu.table_schema, vcu.view_name, vcu.column_name + ) + UNION + ( + SELECT + vcu.view_schema as table_schema, + table_fk.table_name, + table_fk.column_name, + vcu.view_name as foreign_table_name, + vcu.column_name as foreign_column_name + FROM information_schema.view_column_usage as vcu + JOIN table_fk ON + table_fk.table_schema = vcu.view_schema AND + table_fk.foreign_table_name = vcu.table_name AND + table_fk.foreign_column_name = vcu.column_name + WHERE vcu.view_schema NOT IN ('pg_catalog', 'information_schema') + ORDER BY vcu.table_schema, vcu.view_name, vcu.column_name + ) |] let simpleRelations = foldr (addParentRelation.relationFromRow) [] rels let links = filter ((==2).length) $ groupWith groupFn $ filter ( (==Child). relType) simpleRelations diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 4593c7fa8..71de94751 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -130,6 +130,10 @@ spec = get "/items?always_true=eq.true" `shouldRespondWith` [json| [{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}] |] + it "matches filtering nested items" $ + get "/clients?select=id,projects(id,tasks(id,name))&projects.tasks.name=like.Design*" `shouldRespondWith` + "[{\"id\":1,\"projects\":[{\"id\":1,\"tasks\":[{\"id\":1,\"name\":\"Design w7\"}]},{\"id\":2,\"tasks\":[{\"id\":3,\"name\":\"Design w10\"}]}]},{\"id\":2,\"projects\":[{\"id\":3,\"tasks\":[{\"id\":5,\"name\":\"Design IOS\"}]},{\"id\":4,\"tasks\":[{\"id\":7,\"name\":\"Design OSX\"}]}]}]" + describe "Shaping response with select parameter" $ do it "selectStar works in absense of parameter" $ @@ -178,6 +182,22 @@ spec = get "/complex_items?id=eq.1&select=settings->foo->>int::integer" `shouldRespondWith` [json| [{"int":1}] |] -- the value in the db is an int, but here we expect a string for now + it "requesting parents and children" $ + get "/projects?id=eq.1&select=id, name, clients(*), tasks(id, name)" `shouldRespondWith` + "[{\"id\":1,\"name\":\"Windows 7\",\"clients\":{\"id\":1,\"name\":\"Microsoft\"},\"tasks\":[{\"id\":1,\"name\":\"Design w7\"},{\"id\":2,\"name\":\"Code w7\"}]}]" + + it "requesting children 2 levels" $ + get "/clients?id=eq.1&select=id,projects(id,tasks(id))" `shouldRespondWith` + "[{\"id\":1,\"projects\":[{\"id\":1,\"tasks\":[{\"id\":1},{\"id\":2}]},{\"id\":2,\"tasks\":[{\"id\":3},{\"id\":4}]}]}]" + + it "requesting many<->many relation" $ + get "/tasks?select=id,users(id)" `shouldRespondWith` + "[{\"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\":null}]" + + it "requesting parents and children on views" $ + get "/projects_view?id=eq.1&select=id, name, clients(*), tasks(id, name)" `shouldRespondWith` + "[{\"id\":1,\"name\":\"Windows 7\",\"clients\":{\"id\":1,\"name\":\"Microsoft\"},\"tasks\":[{\"id\":1,\"name\":\"Design w7\"},{\"id\":2,\"name\":\"Code w7\"}]}]" + describe "ordering response" $ do it "by a column asc" $ diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs index eb63e5724..03b7b010c 100644 --- a/test/Feature/StructureSpec.hs +++ b/test/Feature/StructureSpec.hs @@ -15,6 +15,7 @@ spec = around withApp $ do request methodGet "/" [] "" `shouldRespondWith` [json| [ {"schema":"1","name":"auto_incrementing_pk","insertable":true} + , {"schema":"1","name":"clients","insertable":true} , {"schema":"1","name":"complex_items","insertable":true} , {"schema":"1","name":"compound_pk","insertable":true} , {"schema":"1","name":"has_count_column","insertable":false} @@ -26,8 +27,14 @@ spec = around withApp $ do , {"schema":"1","name":"menagerie","insertable":true} , {"schema":"1","name":"no_pk","insertable":true} , {"schema":"1","name":"nullable_integer","insertable":true} + , {"schema":"1","name":"projects","insertable":true} + , {"schema":"1","name":"projects_view","insertable":true} , {"schema":"1","name":"simple_pk","insertable":true} + , {"schema":"1","name":"tasks","insertable":true} , {"schema":"1","name":"tsearch","insertable":true} + , {"schema":"1","name":"users","insertable":true} + , {"schema":"1","name":"users_projects","insertable":true} + , {"schema":"1","name":"users_tasks","insertable":true} ] |] {matchStatus = 200} diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index 0f5cb5068..f70f6265d 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -210,6 +210,62 @@ CREATE TABLE complex_items ( ALTER TABLE "1".complex_items OWNER TO postgrest_test; +--- Structure for testing table relations +CREATE TABLE clients( + id INT PRIMARY KEY NOT NULL, + name TEXT NOT NULL +); +ALTER TABLE "1".clients OWNER TO postgrest_test; + +CREATE TABLE projects( + id INT PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + client_id INT REFERENCES clients(id) +); +ALTER TABLE "1".projects OWNER TO postgrest_test; + +CREATE TABLE tasks( + id INT PRIMARY KEY NOT NULL, + name TEXT NOT NULL, + project_id INT REFERENCES projects(id) +); +ALTER TABLE "1".tasks OWNER TO postgrest_test; + +CREATE TABLE users( + id INT PRIMARY KEY NOT NULL, + name TEXT NOT NULL +); +ALTER TABLE "1".users OWNER TO postgrest_test; + +CREATE TABLE users_tasks( + user_id INT REFERENCES users(id), + task_id INT REFERENCES tasks(id), + CONSTRAINT task_user PRIMARY KEY (task_id,user_id) +); +ALTER TABLE "1".users_tasks OWNER TO postgrest_test; + +CREATE TABLE users_projects( + user_id INT REFERENCES users(id), + project_id INT REFERENCES projects(id), + CONSTRAINT project_user PRIMARY KEY (project_id, user_id) +); +ALTER TABLE "1".users_projects OWNER TO postgrest_test; + +CREATE VIEW "1".projects_view AS + SELECT + projects.id, + projects.name, + projects.client_id + FROM projects; +ALTER TABLE "1".projects_view OWNER TO postgrest_test; +------- SAMPLE DATA ----- +INSERT INTO clients VALUES (1, 'Microsoft'),(2, 'Apple'); +INSERT INTO projects VALUES (1,'Windows 7', 1),(2,'Windows 10', 1),(3,'IOS', 2),(4,'OSX', 2); +INSERT INTO tasks VALUES (1,'Design w7',1),(2,'Code w7',1),(3,'Design w10',2),(4,'Code w10',2),(5,'Design IOS',3),(6,'Code IOS',3),(7,'Design OSX',4),(8,'Code OSX',4); +INSERT INTO users VALUES (1, 'Angela Martin'),(2, 'Michael Scott'),(3, 'Dwight Schrute'); +INSERT INTO users_projects VALUES(1,1),(1,2),(2,3),(2,4),(3,1),(3,3); +INSERT INTO users_tasks VALUES(1,1),(1,2),(1,3),(1,4),(2,5),(2,6),(2,7),(3,1),(3,5); +---------------- CREATE SEQUENCE items_id_seq START WITH 1 @@ -555,6 +611,37 @@ REVOKE ALL ON TABLE complex_items FROM postgrest_test; GRANT ALL ON TABLE complex_items TO postgrest_test; GRANT ALL ON TABLE complex_items TO postgrest_anonymous; +--------- +REVOKE ALL ON TABLE clients FROM PUBLIC; +REVOKE ALL ON TABLE clients FROM postgrest_test; +GRANT ALL ON TABLE clients TO postgrest_test; +GRANT ALL ON TABLE clients TO postgrest_anonymous; +REVOKE ALL ON TABLE projects FROM PUBLIC; +REVOKE ALL ON TABLE projects FROM postgrest_test; +GRANT ALL ON TABLE projects TO postgrest_test; +GRANT ALL ON TABLE projects TO postgrest_anonymous; +REVOKE ALL ON TABLE tasks FROM PUBLIC; +REVOKE ALL ON TABLE tasks FROM postgrest_test; +GRANT ALL ON TABLE tasks TO postgrest_test; +GRANT ALL ON TABLE tasks TO postgrest_anonymous; +REVOKE ALL ON TABLE users FROM PUBLIC; +REVOKE ALL ON TABLE users FROM postgrest_test; +GRANT ALL ON TABLE users TO postgrest_test; +GRANT ALL ON TABLE users TO postgrest_anonymous; +REVOKE ALL ON TABLE users_tasks FROM PUBLIC; +REVOKE ALL ON TABLE users_tasks FROM postgrest_test; +GRANT ALL ON TABLE users_tasks TO postgrest_test; +GRANT ALL ON TABLE users_tasks TO postgrest_anonymous; +REVOKE ALL ON TABLE users_projects FROM PUBLIC; +REVOKE ALL ON TABLE users_projects FROM postgrest_test; +GRANT ALL ON TABLE users_projects TO postgrest_test; +GRANT ALL ON TABLE users_projects TO postgrest_anonymous; +REVOKE ALL ON TABLE projects_view FROM PUBLIC; +REVOKE ALL ON TABLE projects_view FROM postgrest_test; +GRANT ALL ON TABLE projects_view TO postgrest_test; +GRANT ALL ON TABLE projects_view TO postgrest_anonymous; +--------- + REVOKE ALL ON FUNCTION getitemrange(bigint, bigint) FROM PUBLIC; REVOKE ALL ON FUNCTION getitemrange(bigint, bigint) FROM postgrest_test;