diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f68865fd..b6349f45f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #917, Add ability to map RAISE errorcode/message to http status - @steve-chavez - #940, Add ability to map GUC to http response headers - @steve-chavez - #1022, Include git sha in version report - @begriffs +- Faster queries using json_agg - @ruslantalpa ### Fixed diff --git a/src/PostgREST/QueryBuilder.hs b/src/PostgREST/QueryBuilder.hs index ef931b5bdf..2b2e7e0b1f 100644 --- a/src/PostgREST/QueryBuilder.hs +++ b/src/PostgREST/QueryBuilder.hs @@ -249,7 +249,7 @@ requestToQuery schema isParent (DbRead (Node (Select colSelects tbls logicForest getQueryParts (Node n@(_, (name, Just Relation{relType=Child,relTable=Table{tableName=table}}, alias, _)) forst) (j,s) = (j,sel:s) where sel = "COALESCE((" - <> "SELECT array_to_json(array_agg(row_to_json(" <> pgFmtIdent table <> ".*))) " + <> "SELECT json_agg(" <> pgFmtIdent table <> ".*) " <> "FROM (" <> subquery <> ") " <> pgFmtIdent table <> "), '[]') AS " <> pgFmtIdent (fromMaybe name alias) where subquery = requestToQuery schema False (DbRead (Node n forst)) @@ -263,7 +263,7 @@ requestToQuery schema isParent (DbRead (Node (Select colSelects tbls logicForest getQueryParts (Node n@(_, (name, Just Relation{relType=Many,relTable=Table{tableName=table}}, alias, _)) forst) (j,s) = (j,sel:s) where sel = "COALESCE ((" - <> "SELECT array_to_json(array_agg(row_to_json(" <> pgFmtIdent table <> ".*))) " + <> "SELECT json_agg(" <> pgFmtIdent table <> ".*) " <> "FROM (" <> subquery <> ") " <> pgFmtIdent table <> "), '[]') AS " <> pgFmtIdent (fromMaybe name alias) where subquery = requestToQuery schema False (DbRead (Node n forst)) @@ -336,7 +336,7 @@ asCsvF = asCsvHeaderF <> " || '\n' || " <> asCsvBodyF asCsvBodyF = "coalesce(string_agg(substring(_postgrest_t::text, 2, length(_postgrest_t::text) - 2), '\n'), '')" asJsonF :: SqlFragment -asJsonF = "coalesce(array_to_json(array_agg(row_to_json(_postgrest_t))), '[]')::character varying" +asJsonF = "coalesce(json_agg(_postgrest_t), '[]')::character varying" asJsonSingleF :: SqlFragment --TODO! unsafe when the query actually returns multiple rows, used only on inserting and returning single element asJsonSingleF = "coalesce(string_agg(row_to_json(_postgrest_t)::text, ','), '')::character varying " diff --git a/test/Feature/InsertSpec.hs b/test/Feature/InsertSpec.hs index e9d71f9570..68853d8718 100644 --- a/test/Feature/InsertSpec.hs +++ b/test/Feature/InsertSpec.hs @@ -423,9 +423,9 @@ spec = do matchStatus = 204, matchHeaders = ["Content-Range" <:> "*/*"] } - - g <- get "/items" - liftIO $ simpleBody g `shouldBe` [json| [{"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},{id:16},{"id":2},{"id":1}] |] + get "/items" `shouldRespondWith` + [json|[{"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},{id:16},{"id":2},{"id":1}]|] + { matchHeaders = [matchContentTypeJson] } it "makes no updates and and returns 200, when patching with an empty json object and return=rep" $ do request methodPatch "/items" [("Prefer", "return=representation")] [json| {} |] @@ -434,10 +434,10 @@ spec = do matchStatus = 200, matchHeaders = ["Content-Range" <:> "*/*"] } - - g <- get "/items" - liftIO $ simpleBody g `shouldBe` [json| [{"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},{id:16},{"id":2},{"id":1}] |] - + get "/items" `shouldRespondWith` + [json| [{"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},{id:16},{"id":2},{"id":1}] |] + { matchHeaders = [matchContentTypeJson] } + context "with unicode values" $ it "succeeds and returns values intact" $ do void $ request methodPost "/no_pk" [] diff --git a/test/Feature/QueryLimitedSpec.hs b/test/Feature/QueryLimitedSpec.hs index 080de140ba..d6555e8b22 100644 --- a/test/Feature/QueryLimitedSpec.hs +++ b/test/Feature/QueryLimitedSpec.hs @@ -5,7 +5,6 @@ import Test.Hspec.Wai import Test.Hspec.Wai.JSON import Network.HTTP.Types import Network.Wai.Test (SResponse(simpleHeaders, simpleStatus)) -import Text.Heredoc import SpecHelper import Network.Wai (Application) @@ -31,14 +30,14 @@ spec = it "limit works on all levels" $ get "/users?select=id,tasks{id}&order=id.asc&tasks.order=id.asc" - `shouldRespondWith` [str|[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":5},{"id":6}]}]|] + `shouldRespondWith` [json|[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":5},{"id":6}]}]|] { matchStatus = 200 , matchHeaders = ["Content-Range" <:> "0-1/*"] } it "limit is not applied to parent embeds" $ get "/tasks?select=id,project{id}&id=gt.5" - `shouldRespondWith` [str|[{"id":6,"project":{"id":3}},{"id":7,"project":{"id":4}}]|] + `shouldRespondWith` [json|[{"id":6,"project":{"id":3}},{"id":7,"project":{"id":4}}]|] { matchStatus = 200 , matchHeaders = ["Content-Range" <:> "0-1/*"] } diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index b292fe8e6a..eff82e7bcb 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -85,9 +85,11 @@ spec = do it "matches with ilike" $ do get "/simple_pk?k=ilike.xy*&order=extra.asc" `shouldRespondWith` - [str|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|] + [json|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|] + { matchHeaders = [matchContentTypeJson] } get "/simple_pk?k=ilike.*YY*&order=extra.asc" `shouldRespondWith` - [str|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|] + [json|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|] + { matchHeaders = [matchContentTypeJson] } it "matches with ilike using not operator" $ get "/simple_pk?k=not.ilike.xy*&order=extra.asc" `shouldRespondWith` "[]" @@ -178,21 +180,26 @@ spec = do it "matches filtering nested items" $ get "/clients?select=id,projects{id,tasks{id,name}}&projects.tasks.name=like.Design*" `shouldRespondWith` - [str|[{"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"}]}]}]|] + [json|[{"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"}]}]}]|] + { matchHeaders = [matchContentTypeJson] } it "matches with cs operator" $ do get "/complex_items?select=id&arr_data=cs.{2}" `shouldRespondWith` - [str|[{"id":2},{"id":3}]|] + [json|[{"id":2},{"id":3}]|] + { matchHeaders = [matchContentTypeJson] } -- TODO: remove in 0.5.0 as deprecated get "/complex_items?select=id&arr_data=@>.{2}" `shouldRespondWith` - [str|[{"id":2},{"id":3}]|] + [json|[{"id":2},{"id":3}]|] + { matchHeaders = [matchContentTypeJson] } it "matches with cd operator" $ do get "/complex_items?select=id&arr_data=cd.{1,2,4}" `shouldRespondWith` - [str|[{"id":1},{"id":2}]|] + [json|[{"id":1},{"id":2}]|] + { matchHeaders = [matchContentTypeJson] } -- TODO: remove in 0.5.0 as deprecated get "/complex_items?select=id&arr_data=<@.{1,2,4}" `shouldRespondWith` - [str|[{"id":1},{"id":2}]|] + [json|[{"id":1},{"id":2}]|] + { matchHeaders = [matchContentTypeJson] } describe "Shaping response with select parameter" $ do @@ -279,7 +286,8 @@ spec = do it "requesting parents and children" $ get "/projects?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"}]}]|] + [json|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] + { matchHeaders = [matchContentTypeJson] } it "requesting parent without specifying primary key" $ do get "/projects?select=name,client{name}" `shouldRespondWith` @@ -321,7 +329,8 @@ spec = do it "requesting parents and children while renaming them" $ get "/projects?id=eq.1&select=myId:id, name, project_client:client_id{*}, project_tasks:tasks{id, name}" `shouldRespondWith` - [str|[{"myId":1,"name":"Windows 7","project_client":{"id":1,"name":"Microsoft"},"project_tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] + [json|[{"myId":1,"name":"Windows 7","project_client":{"id":1,"name":"Microsoft"},"project_tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] + { matchHeaders = [matchContentTypeJson] } it "requesting parents two levels up while using FK to specify the link" $ get "/tasks?id=eq.1&select=id,name,project:project_id{id,name,client:client_id{id,name}}" `shouldRespondWith` @@ -338,7 +347,8 @@ spec = do it "rows with missing parents are included" $ get "/projects?id=in.1,5&select=id,clients{id}" `shouldRespondWith` - [str|[{"id":1,"clients":{"id":1}},{"id":5,"clients":null}]|] + [json|[{"id":1,"clients":{"id":1}},{"id":5,"clients":null}]|] + { matchHeaders = [matchContentTypeJson] } it "rows with no children return [] instead of null" $ get "/projects?id=in.5&select=id,tasks{id}" `shouldRespondWith` @@ -346,53 +356,70 @@ spec = do it "requesting children 2 levels" $ get "/clients?id=eq.1&select=id,projects{id,tasks{id}}" `shouldRespondWith` - [str|[{"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] } it "requesting children 2 levels (with relation path fixed)" $ get "/clients?id=eq.1&select=id,projects:projects.client_id{id,tasks{id}}" `shouldRespondWith` - [str|[{"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] } it "requesting many<->many relation" $ 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":[]}]|] + [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` - [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":[]}]|] + [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` - [str|[{"id":1,"theUsers":[{"id":1},{"id":3}]}]|] + [json|[{"id":1,"theUsers":[{"id":1},{"id":3}]}]|] + { matchHeaders = [matchContentTypeJson] } 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}]}]|] + [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 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"}]}]|] + [json|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] + { matchHeaders = [matchContentTypeJson] } it "requesting parents and children on views with renamed keys" $ get "/projects_view_alt?t_id=eq.1&select=t_id, name, clients{*}, tasks{id, name}" `shouldRespondWith` - [str|[{"t_id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] + [json|[{"t_id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] + { matchHeaders = [matchContentTypeJson] } it "requesting children with composite key" $ get "/users_tasks?user_id=eq.2&task_id=eq.6&select=*, comments{content}" `shouldRespondWith` - [str|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|] + [json|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|] + { matchHeaders = [matchContentTypeJson] } it "detect relations in views from exposed schema that are based on tables in private schema and have columns renames" $ get "/articles?id=eq.1&select=id,articleStars{users{*}}" `shouldRespondWith` - [str|[{"id":1,"articleStars":[{"users":{"id":1,"name":"Angela Martin"}},{"users":{"id":2,"name":"Michael Scott"}},{"users":{"id":3,"name":"Dwight Schrute"}}]}]|] + [json|[{"id":1,"articleStars":[{"users":{"id":1,"name":"Angela Martin"}},{"users":{"id":2,"name":"Michael Scott"}},{"users":{"id":3,"name":"Dwight Schrute"}}]}]|] + { matchHeaders = [matchContentTypeJson] } - it "can select by column name" $ - get "/projects?id=in.1,3&select=id,name,client_id,client_id{id,name}" `shouldRespondWith` - [str|[{"id":1,"name":"Windows 7","client_id":1,"client_id":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client_id":{"id":2,"name":"Apple"}}]|] + it "can embed by FK column name" $ + 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"}}]|] + { matchHeaders = [matchContentTypeJson] } it "can select by column name sans id" $ get "/projects?id=in.1,3&select=id,name,client_id,client{id,name}" `shouldRespondWith` - [str|[{"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"}}]|] + { matchHeaders = [matchContentTypeJson] } it "can detect fk relations through views to tables in the public schema" $ get "/consumers_view?select=*,orders_view{*}" `shouldRespondWith` 200 @@ -464,15 +491,18 @@ spec = do it "ordering embeded entities" $ get "/projects?id=eq.1&select=id, name, tasks{id, name}&tasks.order=name.asc" `shouldRespondWith` - [str|[{"id":1,"name":"Windows 7","tasks":[{"id":2,"name":"Code w7"},{"id":1,"name":"Design w7"}]}]|] + [json|[{"id":1,"name":"Windows 7","tasks":[{"id":2,"name":"Code w7"},{"id":1,"name":"Design w7"}]}]|] + { matchHeaders = [matchContentTypeJson] } it "ordering embeded entities with alias" $ get "/projects?id=eq.1&select=id, name, the_tasks:tasks{id, name}&tasks.order=name.asc" `shouldRespondWith` - [str|[{"id":1,"name":"Windows 7","the_tasks":[{"id":2,"name":"Code w7"},{"id":1,"name":"Design w7"}]}]|] + [json|[{"id":1,"name":"Windows 7","the_tasks":[{"id":2,"name":"Code w7"},{"id":1,"name":"Design w7"}]}]|] + { matchHeaders = [matchContentTypeJson] } it "ordering embeded entities, two levels" $ get "/projects?id=eq.1&select=id, name, tasks{id, name, users{id, name}}&tasks.order=name.asc&tasks.users.order=name.desc" `shouldRespondWith` - [str|[{"id":1,"name":"Windows 7","tasks":[{"id":2,"name":"Code w7","users":[{"id":1,"name":"Angela Martin"}]},{"id":1,"name":"Design w7","users":[{"id":3,"name":"Dwight Schrute"},{"id":1,"name":"Angela Martin"}]}]}]|] + [json|[{"id":1,"name":"Windows 7","tasks":[{"id":2,"name":"Code w7","users":[{"id":1,"name":"Angela Martin"}]},{"id":1,"name":"Design w7","users":[{"id":3,"name":"Dwight Schrute"},{"id":1,"name":"Angela Martin"}]}]}]|] + { matchHeaders = [matchContentTypeJson] } it "ordering embeded parents does not break things" $ get "/projects?id=eq.1&select=id, name, clients{id, name}&clients.order=name.asc" `shouldRespondWith` diff --git a/test/Feature/RangeSpec.hs b/test/Feature/RangeSpec.hs index 119bac2c58..74ae4173e4 100644 --- a/test/Feature/RangeSpec.hs +++ b/test/Feature/RangeSpec.hs @@ -9,7 +9,6 @@ import Network.Wai.Test (SResponse(simpleHeaders,simpleStatus)) import qualified Data.ByteString.Lazy as BL import SpecHelper -import Text.Heredoc import Network.Wai (Application) import Protolude hiding (get) @@ -131,20 +130,20 @@ spec = do it "no parameters return everything" $ get "/items?select=id&order=id.asc" `shouldRespondWith` - [str|[{"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}]|] + [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}]|] { matchStatus = 200 , matchHeaders = ["Content-Range" <:> "0-14/*"] } it "top level limit with parameter" $ get "/items?select=id&order=id.asc&limit=3" - `shouldRespondWith` [str|[{"id":1},{"id":2},{"id":3}]|] + `shouldRespondWith` [json|[{"id":1},{"id":2},{"id":3}]|] { matchStatus = 200 , matchHeaders = ["Content-Range" <:> "0-2/*"] } it "headers override get parameters" $ request methodGet "/items?select=id&order=id.asc&limit=3" (rangeHdrs $ ByteRangeFromTo 0 1) "" - `shouldRespondWith` [str|[{"id":1},{"id":2}]|] + `shouldRespondWith` [json|[{"id":1},{"id":2}]|] { matchStatus = 200 , matchHeaders = ["Content-Range" <:> "0-1/*"] } @@ -152,7 +151,7 @@ spec = do it "limit works on all levels" $ get "/clients?select=id,projects{id,tasks{id}}&order=id.asc&limit=1&projects.order=id.asc&projects.limit=2&projects.tasks.order=id.asc&projects.tasks.limit=1" `shouldRespondWith` - [str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1}]},{"id":2,"tasks":[{"id":3}]}]}]|] + [json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1}]},{"id":2,"tasks":[{"id":3}]}]}]|] { matchStatus = 200 , matchHeaders = ["Content-Range" <:> "0-0/*"] } @@ -160,7 +159,7 @@ spec = do it "limit and offset works on first level" $ get "/items?select=id&order=id.asc&limit=3&offset=2" - `shouldRespondWith` [str|[{"id":3},{"id":4},{"id":5}]|] + `shouldRespondWith` [json|[{"id":3},{"id":4},{"id":5}]|] { matchStatus = 200 , matchHeaders = ["Content-Range" <:> "2-4/*"] } diff --git a/test/Feature/RpcSpec.hs b/test/Feature/RpcSpec.hs index f7c0710ee6..f051fc07ab 100644 --- a/test/Feature/RpcSpec.hs +++ b/test/Feature/RpcSpec.hs @@ -112,9 +112,11 @@ spec = context "foreign entities embedding" $ do it "can embed if related tables are in the exposed schema" $ do post "/rpc/getproject?select=id,name,client{id},tasks{id}" [json| { "id": 1} |] `shouldRespondWith` - [str|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|] + [json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|] + { matchHeaders = [matchContentTypeJson] } get "/rpc/getproject?id=1&select=id,name,client{id},tasks{id}" `shouldRespondWith` - [str|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|] + [json|[{"id":1,"name":"Windows 7","client":{"id":1},"tasks":[{"id":1},{"id":2}]}]|] + { matchHeaders = [matchContentTypeJson] } it "cannot embed if the related table is not in the exposed schema" $ do post "/rpc/single_article?select=*,article_stars{*}" [json|{ "id": 1}|] diff --git a/test/Feature/SingularSpec.hs b/test/Feature/SingularSpec.hs index 53501f402e..0e73531050 100644 --- a/test/Feature/SingularSpec.hs +++ b/test/Feature/SingularSpec.hs @@ -39,7 +39,8 @@ spec = it "can shape plurality singular object routes" $ request methodGet "/projects_view?id=eq.1&select=id,name,clients{*},tasks{id,name}" [singular] "" `shouldRespondWith` - [str|{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}|] + [json|{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}|] + { matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] } context "when updating rows" $ do