Skip to content

Commit

Permalink
fix: data reps for computed relations.
Browse files Browse the repository at this point in the history
Test included. Also added a test for mixed mode field selection.
  • Loading branch information
aljungberg committed Nov 29, 2022
1 parent 434a7b9 commit 0693f11
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 4 deletions.
6 changes: 3 additions & 3 deletions src/PostgREST/Plan.hs
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,12 @@ readPlan qi@QualifiedIdentifier{..} AppConfig{configDbMaxRows} SchemaCache{dbTab
treeRestrictRange configDbMaxRows (iAction apiRequest) =<<
validateSpreadEmbeds =<<
addRelatedOrders =<<
addDataRepresentationAliases =<<
expandStarsForDataRepresentations ctx =<<
addRels qiSchema (iAction apiRequest) dbRelationships Nothing =<<
addLogicTrees ctx apiRequest =<<
addRanges apiRequest =<<
addOrders apiRequest =<<
addDataRepresentationAliases =<<
expandStarsForDataRepresentations ctx =<<
addFilters ctx apiRequest (initReadRequest ctx $ QueryParams.qsSelect $ iQueryParams apiRequest)

-- Build the initial read plan tree
Expand Down Expand Up @@ -223,7 +223,7 @@ expandStarsForDataRepresentations ctx@ResolverContext{qi} rPlanTree = Right $ fm
where
expandStars :: ReadPlan -> ReadPlan
-- When the schema is "" and the table is the source CTE, we assume the true source table is given in the from
-- alias and belongs to the request schema.
-- alias and belongs to the request schema. See the bit in `addRels` with `newFrom = ...`.
expandStars rPlan@ReadPlan{from=(QualifiedIdentifier "" "pgrst_source"), fromAlias=(Just tblAlias)} =
expandStarsForTable ctx{qi=qi{qiName=tblAlias}} rPlan
expandStars rPlan@ReadPlan{from=fromTable} =
Expand Down
36 changes: 36 additions & 0 deletions test/spec/Feature/Query/ComputedRelsSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,42 @@ spec = describe "computed relationships" $ do
[json|[ {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}} ]|]
{ matchStatus = 200 }

it "applies data representations to response" $ do
-- A smoke test for data reps in the presence of computed relations.

-- The data rep here title cases the designer name before presentation. So here the lowercase version will be saved,
-- but the title case version returned. Pulling in a computed relation should not confuse this.
request methodPatch "/designers?select=name,videogames:computed_videogames(name)&id=eq.1"
[("Prefer", "return=representation"), ("Prefer", "tx=commit")]
[json| {"name": "sidney k. meier"} |]
`shouldRespondWith`
[json|[{"name":"Sidney K. Meier","videogames":[{"name":"Civilization I"}, {"name":"Civilization II"}]}]|]
{ matchStatus = 200 }

-- Verify it was saved the way we requested (there's no text data rep for this column, so if we select with the wrong casing, it should fail.)
get "/designers?select=id&name=eq.Sidney%20K.%20Meier"
`shouldRespondWith`
[json|[]|]
{ matchStatus = 200, matchHeaders = [matchContentTypeJson] }
-- But with the right casing it works.
get "/designers?select=id,name&name=eq.sidney%20k.%20meier"
`shouldRespondWith`
[json|[{"id": 1, "name":"Sidney K. Meier"}]|]
{ matchStatus = 200, matchHeaders = [matchContentTypeJson] }

-- Most importantly, if you read it back even via a computed relation, the data rep should be applied.
get "/videogames?select=name,designer:computed_designers(*)&id=eq.1"
`shouldRespondWith`
[json|[
{"name":"Civilization I","designer":{"id": 1, "name":"Sidney K. Meier"}}
]|] { matchHeaders = [matchContentTypeJson] }

-- reset the test fixture
request methodPatch "/designers?id=eq.1"
[("Prefer", "tx=commit")]
[json| {"name": "Sid Meier"} |]
`shouldRespondWith` 204

it "works with self joins" $
get "/web_content?select=name,child_web_content(name),parent_web_content(name)&id=in.(0,1)"
`shouldRespondWith`
Expand Down
11 changes: 11 additions & 0 deletions test/spec/Feature/Query/UpdateSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,17 @@ spec actualPgVersion = do
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-0/*"]
}

it "parses values in payload and formats star mixed selected values in return=representation" $
request methodPatch "/datarep_todos?id=eq.2&select=due_at,*" [("Prefer", "return=representation")]
[json| {"label_color": "#221100", "due_at": "2019-01-03T11:00:00+00"} |]
`shouldRespondWith`
-- end up with due_at twice here but that's unrelated to data reps
[json| [{"due_at":"2019-01-03T11:00:00+00", "id":2, "name":"Essay", "label_color":"#221100", "due_at":"2019-01-03T11:00:00+00"}] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-0/*"]
}
context "for multiple rows" $ do
it "parses values in payload and formats individually selected values in return=representation" $
request methodPatch "/datarep_todos?id=lt.4&select=id,name,label_color" [("Prefer", "return=representation")]
Expand Down
13 changes: 12 additions & 1 deletion test/spec/fixtures/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2721,9 +2721,20 @@ BEGIN
LOAD 'safeupdate';
END; $$ LANGUAGE plpgsql SECURITY DEFINER;

-- This tests data representations over computed joins: even a lower case title should come back title cased.
DROP DOMAIN IF EXISTS public.titlecasetext CASCADE;
CREATE DOMAIN public.titlecasetext AS text;

CREATE OR REPLACE FUNCTION json(public.titlecasetext) RETURNS json AS $$
SELECT to_json(INITCAP($1::text));
$$ LANGUAGE SQL IMMUTABLE;

CREATE CAST (public.titlecasetext AS json) WITH FUNCTION json(public.titlecasetext) AS IMPLICIT;
-- End of data representations specific stuff except for where the domain is used in the table.

CREATE TABLE designers (
id int primary key
, name text
, name public.titlecasetext
);

CREATE TABLE videogames (
Expand Down

0 comments on commit 0693f11

Please sign in to comment.