From f9f572a5d2e16d7baa77bca508b8b4ad0f71d417 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Thu, 27 Oct 2022 16:31:49 +0200 Subject: [PATCH] fix: Treat non-setof computed relationships as M2O/O2M Fixes #2481 Signed-off-by: Wolfgang Walther --- CHANGELOG.md | 1 + src/PostgREST/SchemaCache.hs | 12 ++++---- test/spec/Feature/Query/ComputedRelsSpec.hs | 32 +++++++++++++++++---- test/spec/fixtures/schema.sql | 4 +++ 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08fa9b0c1..9b8264169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2356, Fix a regression in openapi output with mode follow-privileges - @wolfgangwalther - #2283, Fix infinite recursion when loading schema cache with self-referencing view - @wolfgangwalther - #2343, Return status code 200 for PATCH requests which don't affect any rows - @wolfgangwalther + - #2481, Treat computed relationships not marked SETOF as M2O/O2O relationship - @wolfgangwalther ### Changed diff --git a/src/PostgREST/SchemaCache.hs b/src/PostgREST/SchemaCache.hs index 84ef9c9c0..534659b54 100644 --- a/src/PostgREST/SchemaCache.hs +++ b/src/PostgREST/SchemaCache.hs @@ -727,12 +727,12 @@ allComputedRels = computed_rels as ( select (parse_ident(p.pronamespace::regnamespace::text))[1] as schema, - p.proname::text as name, - arg_schema.nspname::text as rel_table_schema, - arg_name.typname::text as rel_table_name, - ret_schema.nspname::text as rel_ftable_schema, - ret_name.typname::text as rel_ftable_name, - p.prorows = 1 as single_row + p.proname::text as name, + arg_schema.nspname::text as rel_table_schema, + arg_name.typname::text as rel_table_name, + ret_schema.nspname::text as rel_ftable_schema, + ret_name.typname::text as rel_ftable_name, + not p.proretset or p.prorows = 1 as single_row from pg_proc p join pg_type arg_name on arg_name.oid = p.proargtypes[0] join pg_namespace arg_schema on arg_schema.oid = arg_name.typnamespace diff --git a/test/spec/Feature/Query/ComputedRelsSpec.hs b/test/spec/Feature/Query/ComputedRelsSpec.hs index d3430efc9..612453d09 100644 --- a/test/spec/Feature/Query/ComputedRelsSpec.hs +++ b/test/spec/Feature/Query/ComputedRelsSpec.hs @@ -12,14 +12,24 @@ import SpecHelper spec :: SpecWith ((), Application) spec = describe "computed relationships" $ do - it "can define a many-to-one relationship and embed" $ - get "/videogames?select=name,designers:computed_designers(name)" + it "can define a many-to-one relationship with SETOF and ROWS 1 and embed" $ + get "/videogames?select=name,designer:computed_designers(name)" `shouldRespondWith` [json|[ - {"name":"Civilization I","designers":{"name":"Sid Meier"}}, - {"name":"Civilization II","designers":{"name":"Sid Meier"}}, - {"name":"Final Fantasy I","designers":{"name":"Hironobu Sakaguchi"}}, - {"name":"Final Fantasy II","designers":{"name":"Hironobu Sakaguchi"}} + {"name":"Civilization I","designer":{"name":"Sid Meier"}}, + {"name":"Civilization II","designer":{"name":"Sid Meier"}}, + {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}}, + {"name":"Final Fantasy II","designer":{"name":"Hironobu Sakaguchi"}} + ]|] { matchHeaders = [matchContentTypeJson] } + + it "can define a many-to-one relationship without SETOF and embed" $ + get "/videogames?select=name,designer:computed_designers_noset(name)" + `shouldRespondWith` + [json|[ + {"name":"Civilization I","designer":{"name":"Sid Meier"}}, + {"name":"Civilization II","designer":{"name":"Sid Meier"}}, + {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}}, + {"name":"Final Fantasy II","designer":{"name":"Hironobu Sakaguchi"}} ]|] { matchHeaders = [matchContentTypeJson] } it "can define a one-to-many relationship and embed" $ @@ -48,6 +58,16 @@ spec = describe "computed relationships" $ do { matchStatus = 200 , matchHeaders = ["Content-Range" <:> "0-1/2"] } + request methodGet "/videogames?select=name,designer:computed_designers_noset!inner(name)&designer.name=like.*Hironobu*" + [("Prefer", "count=exact")] "" + `shouldRespondWith` + [json|[ + {"name":"Final Fantasy I","designer":{"name":"Hironobu Sakaguchi"}}, + {"name":"Final Fantasy II","designer":{"name":"Hironobu Sakaguchi"}} + ]|] + { matchStatus = 200 + , matchHeaders = ["Content-Range" <:> "0-1/2"] + } it "works with rpc" $ do get "/rpc/getallvideogames?select=name,designer:computed_designers(name)" diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index b830d8168..5cb2e22f9 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -2737,6 +2737,10 @@ CREATE FUNCTION test.computed_designers(test.videogames) RETURNS SETOF test.desi SELECT * FROM test.designers WHERE id = $1.designer_id; $$ LANGUAGE sql STABLE ROWS 1; +CREATE FUNCTION test.computed_designers_noset(test.videogames) RETURNS test.designers AS $$ + SELECT * FROM test.designers WHERE id = $1.designer_id; +$$ LANGUAGE sql STABLE; + CREATE FUNCTION test.computed_videogames(test.designers) RETURNS SETOF test.videogames AS $$ SELECT * FROM test.videogames WHERE designer_id = $1.id; $$ LANGUAGE sql STABLE;