From 3f690ec78f9f66b906f7bd2c7053685af2b770ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20M=C3=A9sz=C3=A1ros?= Date: Wed, 18 Nov 2020 00:49:02 +0100 Subject: [PATCH] Removing single column limit from join table M2M mapping detection. (#1593) --- CHANGELOG.md | 3 ++- src/PostgREST/DbStructure.hs | 8 ++++---- test/Feature/QuerySpec.hs | 15 +++++++++++++++ test/fixtures/data.sql | 20 ++++++++++++++++++++ test/fixtures/privileges.sql | 2 ++ test/fixtures/schema.sql | 22 ++++++++++++++++++++++ 6 files changed, 65 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f4efc92..a91e6c12d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #1607, Enable embedding through multiple views recursively - @wolfgangwalther ### Fixed - + + - #1592, Removed single column restriction to allow composite foreign keys in join tables - @goteguru - #1530, Fix how the PostgREST version is shown in the help text when the `.git` directory is not available - @monacoremo - #1094, Fix expired JWTs starting an empty transaction on the db - @steve-chavez - #1162, Fix location header for POST request with select= without PK - @wolfgangwalther diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index d75863e48..0caf949f3 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -358,9 +358,9 @@ addO2MRels = concatMap (\rel@(Relation t c cn ft fc _ _) -> [rel, Relation ft fc addM2MRels :: [Relation] -> [Relation] addM2MRels rels = rels ++ addMirrorRel (mapMaybe junction2Rel junctions) where - junctions = join $ map (combinations 2) $ filter (not . null) $ groupWith groupFn $ filter ( (==M2O). relType) rels - groupFn :: Relation -> Text - groupFn Relation{relTable=Table{tableSchema=s, tableName=t}} = s <> "_" <> t + junctions = join $ map (combinations 2) $ groupWith groupFn $ filter ( (==M2O). relType) rels + groupFn :: Relation -> (Text,Text) + groupFn Relation{relTable=Table{tableSchema=s, tableName=t}} = (s,t) -- Reference : https://wiki.haskell.org/99_questions/Solutions/26 combinations :: Int -> [a] -> [[a]] combinations 0 _ = [ [] ] @@ -370,7 +370,7 @@ addM2MRels rels = rels ++ addMirrorRel (mapMaybe junction2Rel junctions) Relation{relTable=jt, relColumns=jc1, relConstraint=const1, relFTable=t, relFColumns=c}, Relation{ relColumns=jc2, relConstraint=const2, relFTable=ft, relFColumns=fc} ] - | jc1 /= jc2 && length jc1 == 1 && length jc2 == 1 = Just $ Relation t c Nothing ft fc M2M (Just $ Junction jt const1 jc1 const2 jc2) + | jc1 /= jc2 = Just $ Relation t c Nothing ft fc M2M (Just $ Junction jt const1 jc1 const2 jc2) | otherwise = Nothing junction2Rel _ = Nothing addMirrorRel = concatMap (\rel@(Relation t c _ ft fc _ (Just (Junction jt const1 jc1 const2 jc2))) -> diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 53c1c6895..9a4c67dde 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -351,6 +351,21 @@ spec actualPgVersion = do [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}]}]|] { matchHeaders = [matchContentTypeJson] } + it "requesting many<->many relation using composite key" $ + get "/files?filename=eq.autoexec.bat&project_id=eq.1&select=filename,users_tasks(user_id,task_id)" `shouldRespondWith` + [json|[{"filename":"autoexec.bat","users_tasks":[{"user_id":1,"task_id":1},{"user_id":3,"task_id":1}]}]|] + { matchHeaders = [matchContentTypeJson] } + + it "requesting data using many<->many relation defined by composite keys" $ + get "/users_tasks?user_id=eq.1&task_id=eq.1&select=user_id,files(filename,content)" `shouldRespondWith` + [json|[{"user_id":1,"files":[{"filename":"command.com","content":"#include "},{"filename":"autoexec.bat","content":"@ECHO OFF"},{"filename":"README.md","content":"# make $$$!"}]}]|] + { matchHeaders = [matchContentTypeJson] } + + it "requesting data using many<->many (composite keys) relation using hint" $ + get "/users_tasks?user_id=eq.1&task_id=eq.1&select=user_id,files!touched_files(filename,content)" `shouldRespondWith` + [json|[{"user_id":1,"files":[{"filename":"command.com","content":"#include "},{"filename":"autoexec.bat","content":"@ECHO OFF"},{"filename":"README.md","content":"# make $$$!"}]}]|] + { matchHeaders = [matchContentTypeJson] } + it "requesting children with composite key" $ get "/users_tasks?user_id=eq.2&task_id=eq.6&select=*, comments(content)" `shouldRespondWith` [json|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|] diff --git a/test/fixtures/data.sql b/test/fixtures/data.sql index 594fadab0..f99a11a85 100644 --- a/test/fixtures/data.sql +++ b/test/fixtures/data.sql @@ -140,6 +140,26 @@ INSERT INTO users_tasks VALUES (3, 5); TRUNCATE TABLE comments CASCADE; INSERT INTO comments VALUES (1, 1, 2, 6, 'Needs to be delivered ASAP'); +-- +-- Data for Name: files; Type: TABLE DATA; Schema: test; Owner: - +-- + +TRUNCATE TABLE files CASCADE; +INSERT INTO files VALUES + (1, 'command.com', '#include ') + ,(1, 'autoexec.bat', '@ECHO OFF') + ,(1, 'io.sys', 'TODO') + ,(2, 'README.md', '# make $$$!') + ,(2, 'marketing.key', '$-$') + ; + +TRUNCATE TABLE touched_files CASCADE; +INSERT INTO touched_files VALUES + (1, 1, 1, 'command.com') + ,(1, 1, 1, 'autoexec.bat') + ,(1, 1, 2, 'README.md') + ,(3, 1, 1, 'autoexec.bat') + ; -- -- Data for Name: complex_items; Type: TABLE DATA; Schema: test; Owner: - diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index 6c2873c99..a1fc30b77 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -43,6 +43,8 @@ GRANT ALL ON TABLE , users , users_projects , users_tasks + , files + , touched_files , "Escap3e;" , "ghostBusters" , "withUnique" diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index 51c1a6a0f..519407c78 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -697,6 +697,28 @@ alter table only comments add constraint "user" foreign key (commenter_id) references users(id), add constraint comments_task_id_fkey foreign key (task_id, user_id) references users_tasks(task_id, user_id); +CREATE TABLE files ( + project_id integer NOT NULL, + filename text NOT NULL, + content text NOT NULL, + PRIMARY KEY (project_id, filename) +); + +CREATE TABLE touched_files ( + user_id integer NOT NULL, + task_id integer NOT NULL, + project_id integer NOT NULL, + filename text NOT NULL, + CONSTRAINT fk_users_tasks + FOREIGN KEY (user_id, task_id) + REFERENCES users_tasks (user_id, task_id) + ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT fk_upload + FOREIGN KEY (project_id, filename) + REFERENCES files (project_id,filename) + ON DELETE CASCADE ON UPDATE CASCADE +); + create table private.articles ( id integer primary key, body text,