postgrest 0.3.0.2 → 0.3.0.3
raw patch · 6 files changed
+43/−20 lines, 6 files
Files
- postgrest.cabal +1/−1
- src/PostgREST/DbStructure.hs +3/−1
- src/PostgREST/QueryBuilder.hs +6/−7
- test/Feature/InsertSpec.hs +10/−10
- test/Feature/QuerySpec.hs +20/−0
- test/Feature/StructureSpec.hs +3/−1
postgrest.cabal view
@@ -2,7 +2,7 @@ description: Reads the schema of a PostgreSQL database and creates RESTful routes for the tables and views, supporting all HTTP verbs that security permits.-version: 0.3.0.2+version: 0.3.0.3 synopsis: REST API for any Postgres database license: MIT license-file: LICENSE
src/PostgREST/DbStructure.hs view
@@ -127,12 +127,14 @@ addParentRelations (rel@(Relation t c ft fc _ _ _ _):rels) = Relation ft fc t c Parent Nothing Nothing Nothing : rel : addParentRelations rels addManyToManyRelations :: [Relation] -> [Relation]-addManyToManyRelations rels = rels ++ mapMaybe link2Relation links+addManyToManyRelations rels = rels ++ addMirrorRelation (mapMaybe link2Relation links) where links = join $ map (combinations 2) $ filter (not . null) $ groupWith groupFn $ filter ( (==Child). relType) rels groupFn :: Relation -> Text groupFn (Relation{relTable=Table{tableSchema=s, tableName=t}}) = s<>"_"<>t combinations k ns = filter ((k==).length) (subsequences ns)+ addMirrorRelation [] = []+ addMirrorRelation (rel@(Relation t c ft fc _ lt lc1 lc2):rels') = Relation ft fc t c Many lt lc2 lc1 : rel : addMirrorRelation rels' link2Relation [ Relation{relTable=lt, relColumns=lc1, relFTable=t, relFColumns=c}, Relation{ relColumns=lc2, relFTable=ft, relFColumns=fc}
src/PostgREST/QueryBuilder.hs view
@@ -129,7 +129,6 @@ where rel = note ("no relation between " <> parentTable <> " and " <> name) $ findRelationByTable schema name parentTable- <|> findRelationByTable schema parentTable name <|> findRelationByColumn schema parentTable name addRel :: (ReadQuery, (NodeName, Maybe Relation)) -> Relation -> (ReadQuery, (NodeName, Maybe Relation)) addRel (q, (n, _)) r = (q {from=fromRelation}, (n, Just r))@@ -272,20 +271,20 @@ getQueryParts (Node n@(_, (name, Just (Relation {relType=Child,relTable=Table{tableName=table}}))) forst) (j,s) = (j,sel:s) where sel = "COALESCE(("- <> "SELECT array_to_json(array_agg(row_to_json("<>table<>"))) "- <> "FROM (" <> subquery <> ") " <> table+ <> "SELECT array_to_json(array_agg(row_to_json("<>pgFmtIdent table<>"))) "+ <> "FROM (" <> subquery <> ") " <> pgFmtIdent table <> "), '[]') AS " <> pgFmtIdent name where subquery = requestToQuery schema (DbRead (Node n forst)) getQueryParts (Node n@(_, (name, Just (Relation {relType=Parent,relTable=Table{tableName=table}}))) forst) (j,s) = (joi:j,sel:s) where- sel = "row_to_json(" <> table <> ".*) AS "<>pgFmtIdent name --TODO must be singular- joi = ("( " <> subquery <> " ) AS " <> table, table)+ sel = "row_to_json(" <> pgFmtIdent table <> ".*) AS "<>pgFmtIdent name --TODO must be singular+ joi = ("( " <> subquery <> " ) AS " <> pgFmtIdent table, table) where subquery = requestToQuery schema (DbRead (Node n forst)) getQueryParts (Node n@(_, (name, Just (Relation {relType=Many,relTable=Table{tableName=table}}))) forst) (j,s) = (j,sel:s) where sel = "COALESCE (("- <> "SELECT array_to_json(array_agg(row_to_json("<>table<>"))) "- <> "FROM (" <> subquery <> ") " <> table+ <> "SELECT array_to_json(array_agg(row_to_json("<>pgFmtIdent table<>"))) "+ <> "FROM (" <> subquery <> ") " <> pgFmtIdent table <> "), '[]') AS " <> pgFmtIdent name where subquery = requestToQuery schema (DbRead (Node n forst)) --the following is just to remove the warning
test/Feature/InsertSpec.hs view
@@ -23,7 +23,7 @@ spec :: DbStructure -> H.Pool P.Postgres -> Spec spec struct pool = beforeAll_ resetDb $ around (withApp cfgDefault struct pool) $ do describe "Posting new record" $ do- after_ (clearTable "menagerie") . context "disparate csv types" $ do+ context "disparate csv types" $ do it "accepts disparate json types" $ do p <- post "/menagerie" [json| {@@ -57,7 +57,7 @@ context "with no pk supplied" $ do- context "into a table with auto-incrementing pk" . after_ (clearTable "auto_incrementing_pk") $+ context "into a table with auto-incrementing pk" $ it "succeeds with 201 and link" $ do p <- post "/auto_incrementing_pk" [json| { "non_nullable_string":"not null"} |] liftIO $ do@@ -76,7 +76,7 @@ post "/simple_pk" [json| { "extra":"foo"} |] `shouldRespondWith` 400 - context "into a table with no pk" . after_ (clearTable "no_pk") $ do+ context "into a table with no pk" $ do it "succeeds with 201 and a link including all fields" $ do p <- post "/no_pk" [json| { "a":"foo", "b":"bar" } |] liftIO $ do@@ -111,7 +111,7 @@ simpleHeaders p `shouldSatisfy` matchHeader hLocation "/no_pk\\?a=is.null&b=eq.foo" simpleStatus p `shouldBe` created201 - context "with compound pk supplied" . after_ (clearTable "compound_pk") $+ context "with compound pk supplied" $ it "builds response location header appropriately" $ post "/compound_pk" [json| { "k1":12, "k2":42 } |] `shouldRespondWith` ResponseMatcher {@@ -124,7 +124,7 @@ it "fails with 400 and error" $ post "/simple_pk" "}{ x = 2" `shouldRespondWith` 400 - context "jsonb" . after_ (clearTable "json") $ do+ context "jsonb" $ do it "serializes nested object" $ do let inserted = [json| { "data": { "foo":"bar" } } |] request methodPost "/json"@@ -162,7 +162,7 @@ describe "CSV insert" $ do - after_ (clearTable "menagerie") . context "disparate csv types" $+ context "disparate csv types" $ it "succeeds with multipart response" $ do pendingWith "Decide on what to do with CSV insert" let inserted = [str|integer,double,varchar,boolean,date,money,enum@@ -185,7 +185,7 @@ -- simpleBody p `shouldBe` "Content-Type: application/json\nLocation: /menagerie?integer=eq.13\n\n\n--postgrest_boundary\nContent-Type: application/json\nLocation: /menagerie?integer=eq.12\n\n" -- simpleStatus p `shouldBe` created201 - after_ (clearTable "no_pk") . context "requesting full representation" $ do+ context "requesting full representation" $ do it "returns full details of inserted record" $ request methodPost "/no_pk" [("Content-Type", "text/csv"), ("Accept", "text/csv"), ("Prefer", "return=representation")]@@ -220,7 +220,7 @@ } - after_ (clearTable "no_pk") . context "with wrong number of columns" $+ context "with wrong number of columns" $ it "fails for too few" $ do p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz" liftIO $ simpleStatus p `shouldBe` badRequest400@@ -255,7 +255,7 @@ [json| { "k1":12, "k2":42 } |] `shouldRespondWith` 400 - context "specifying every column in the table" . after_ (clearTable "compound_pk") $ do+ context "specifying every column in the table" $ do it "can create a new record" $ do pendingWith "Decide on PUT usefullness" p <- request methodPut "/compound_pk?k1=eq.12&k2=eq.42" []@@ -287,7 +287,7 @@ let record = head rows compoundExtra record `shouldBe` Just 5 - context "with an auto-incrementing primary key" . after_ (clearTable "auto_incrementing_pk") $+ context "with an auto-incrementing primary key"$ it "succeeds with 204" $ do pendingWith "Decide on PUT usefullness"
test/Feature/QuerySpec.hs view
@@ -214,6 +214,11 @@ get "/tasks?select=id,users{id}" `shouldRespondWith` [str|[{"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":[]}]|] ++ it "requesting many<->many relation reverse" $+ get "/users?select=id,tasks{id}" `shouldRespondWith`+ [str|[{"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}]}]|]+ it "requesting parents and children on views" $ get "/projects_view?id=eq.1&select=id, name, clients{*}, tasks{id, name}" `shouldRespondWith` [str|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|]@@ -382,3 +387,18 @@ it "returns proper json" $ post "/rpc/sayhello" [json| { "name": "world" } |] `shouldRespondWith` [json| [{"sayhello":"Hello, world"}] |]++ describe "weird requests" $ do+ it "can query as normal" $ do+ get "/Escap3e;" `shouldRespondWith`+ [json| [{"so6meIdColumn":1},{"so6meIdColumn":2},{"so6meIdColumn":3},{"so6meIdColumn":4},{"so6meIdColumn":5}] |]+ get "/ghostBusters" `shouldRespondWith`+ [json| [{"escapeId":1},{"escapeId":3},{"escapeId":5}] |]++ it "will embed a collection" $+ get "/Escap3e;?select=ghostBusters{*}" `shouldRespondWith`+ [json| [{"ghostBusters":[{"escapeId":1}]},{"ghostBusters":[]},{"ghostBusters":[{"escapeId":3}]},{"ghostBusters":[]},{"ghostBusters":[{"escapeId":5}]}] |]++ it "will embed using a column" $+ get "/ghostBusters?select=escapeId{*}" `shouldRespondWith`+ [json| [{"escapeId":{"so6meIdColumn":1}},{"escapeId":{"so6meIdColumn":3}},{"escapeId":{"so6meIdColumn":5}}] |]
test/Feature/StructureSpec.hs view
@@ -18,13 +18,15 @@ it "lists views in schema" $ request methodGet "/" [] "" `shouldRespondWith` [json| [- {"schema":"test","name":"articleStars","insertable":true}+ {"schema":"test","name":"Escap3e;","insertable":true}+ , {"schema":"test","name":"articleStars","insertable":true} , {"schema":"test","name":"articles","insertable":true} , {"schema":"test","name":"auto_incrementing_pk","insertable":true} , {"schema":"test","name":"clients","insertable":true} , {"schema":"test","name":"comments","insertable":true} , {"schema":"test","name":"complex_items","insertable":true} , {"schema":"test","name":"compound_pk","insertable":true}+ , {"schema":"test","name":"ghostBusters","insertable":true} , {"schema":"test","name":"has_count_column","insertable":false} , {"schema":"test","name":"has_fk","insertable":true} , {"schema":"test","name":"insertable_view_with_join","insertable":true}