fix: bad M2M embed on RPC

This commit is contained in:
steve-chavez
2023-02-02 03:30:15 -05:00
committed by Steve Chavez
parent aaa4fbc370
commit a525790c4c
7 changed files with 41 additions and 18 deletions
+4
View File
@@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased ## Unreleased
### Fixed
- #2565, Fix bad M2M embedding on RPC - @steve-chavez
## [10.1.1] - 2022-11-08 ## [10.1.1] - 2022-11-08
### Fixed ### Fixed
+1 -1
View File
@@ -193,7 +193,7 @@ compressedRel Relationship{..} =
: case relCardinality of : case relCardinality of
M2M Junction{..} -> [ M2M Junction{..} -> [
"cardinality" .= ("many-to-many" :: Text) "cardinality" .= ("many-to-many" :: Text)
, "relationship" .= (qiName junTable <> " using " <> junConstraint1 <> fmtEls (snd <$> junColumns1) <> " and " <> junConstraint2 <> fmtEls (snd <$> junColumns2)) , "relationship" .= (qiName junTable <> " using " <> junConstraint1 <> fmtEls (snd <$> junColsSource) <> " and " <> junConstraint2 <> fmtEls (snd <$> junColsTarget))
] ]
M2O cons relColumns -> [ M2O cons relColumns -> [
"cardinality" .= ("many-to-one" :: Text) "cardinality" .= ("many-to-one" :: Text)
+8 -7
View File
@@ -357,7 +357,7 @@ mutatePlan mutation qi ApiRequest{..} sCache readReq = mapLeft ApiRequestError $
returnings = returnings =
if iPreferRepresentation == None if iPreferRepresentation == None
then [] then []
else returningCols readReq pkCols else inferColsEmbedNeeds readReq pkCols
pkCols = maybe mempty tablePKCols $ HM.lookup qi $ dbTables sCache pkCols = maybe mempty tablePKCols $ HM.lookup qi $ dbTables sCache
logic = map snd qsLogic logic = map snd qsLogic
rootOrder = maybe [] snd $ find (\(x, _) -> null x) qsOrder rootOrder = maybe [] snd $ find (\(x, _) -> null x) qsOrder
@@ -371,7 +371,7 @@ callPlan proc apiReq readReq = FunctionCall {
, funCArgs = payRaw <$> iPayload apiReq , funCArgs = payRaw <$> iPayload apiReq
, funCScalar = procReturnsScalar proc , funCScalar = procReturnsScalar proc
, funCMultipleCall = iPreferParameters apiReq == Just MultipleObjects , funCMultipleCall = iPreferParameters apiReq == Just MultipleObjects
, funCReturning = returningCols readReq [] , funCReturning = inferColsEmbedNeeds readReq []
} }
where where
paramsAsSingleObject = iPreferParameters apiReq == Just SingleObject paramsAsSingleObject = iPreferParameters apiReq == Just SingleObject
@@ -382,14 +382,15 @@ callPlan proc apiReq readReq = FunctionCall {
prms -> KeyParams $ specifiedParams prms prms -> KeyParams $ specifiedParams prms
specifiedParams = filter (\x -> ppName x `S.member` iColumns apiReq) specifiedParams = filter (\x -> ppName x `S.member` iColumns apiReq)
returningCols :: ReadPlanTree -> [FieldName] -> [FieldName] -- | Infers the columns needed for an embed to be successful after a mutation or a function call.
returningCols rr@(Node _ forest) pkCols inferColsEmbedNeeds :: ReadPlanTree -> [FieldName] -> [FieldName]
inferColsEmbedNeeds (Node ReadPlan{select} forest) pkCols
-- if * is part of the select, we must not add pk or fk columns manually - -- if * is part of the select, we must not add pk or fk columns manually -
-- otherwise those would be selected and output twice -- otherwise those would be selected and output twice
| "*" `elem` fldNames = ["*"] | "*" `elem` fldNames = ["*"]
| otherwise = returnings | otherwise = returnings
where where
fldNames = fstFieldNames rr fldNames = (\((fld, _), _, _) -> fld) <$> select
-- Without fkCols, when a mutatePlan to -- Without fkCols, when a mutatePlan to
-- /projects?select=name,clients(name) occurs, the RETURNING SQL part would -- /projects?select=name,clients(name) occurs, the RETURNING SQL part would
-- be `RETURNING name`(see QueryBuilder). This would make the embedding -- be `RETURNING name`(see QueryBuilder). This would make the embedding
@@ -403,8 +404,8 @@ returningCols rr@(Node _ forest) pkCols
Just $ fst <$> cols Just $ fst <$> cols
Node ReadPlan{relToParent=Just Relationship{relCardinality=O2O _ cols}} _ -> Node ReadPlan{relToParent=Just Relationship{relCardinality=O2O _ cols}} _ ->
Just $ fst <$> cols Just $ fst <$> cols
Node ReadPlan{relToParent=Just Relationship{relCardinality=M2M Junction{junColumns1, junColumns2}}} _ -> Node ReadPlan{relToParent=Just Relationship{relCardinality=M2M Junction{junColsSource=cols}}} _ ->
Just $ (fst <$> junColumns1) ++ (fst <$> junColumns2) Just $ fst <$> cols
Node ReadPlan{relToParent=Just ComputedRelationship{}} _ -> Node ReadPlan{relToParent=Just ComputedRelationship{}} _ ->
Nothing Nothing
Node ReadPlan{relToParent=Nothing} _ -> Node ReadPlan{relToParent=Nothing} _ ->
-7
View File
@@ -1,8 +1,6 @@
{-# LANGUAGE NamedFieldPuns #-}
module PostgREST.Plan.ReadPlan module PostgREST.Plan.ReadPlan
( ReadPlanTree ( ReadPlanTree
, ReadPlan(..) , ReadPlan(..)
, fstFieldNames
, JoinCondition(..) , JoinCondition(..)
) where ) where
@@ -45,8 +43,3 @@ data ReadPlan = ReadPlan
-- ^ used for aliasing -- ^ used for aliasing
} }
deriving (Eq) deriving (Eq)
-- First level FieldNames(e.g get a,b from /table?select=a,b,other(c,d))
fstFieldNames :: ReadPlanTree -> [FieldName]
fstFieldNames (Node ReadPlan{select} _) =
fst . (\(f, _, _) -> f) <$> select
+2 -2
View File
@@ -55,8 +55,8 @@ data Junction = Junction
{ junTable :: QualifiedIdentifier { junTable :: QualifiedIdentifier
, junConstraint1 :: FKConstraint , junConstraint1 :: FKConstraint
, junConstraint2 :: FKConstraint , junConstraint2 :: FKConstraint
, junColumns1 :: [(FieldName, FieldName)] , junColsSource :: [(FieldName, FieldName)]
, junColumns2 :: [(FieldName, FieldName)] , junColsTarget :: [(FieldName, FieldName)]
} }
deriving (Eq, Ord, Generic, JSON.ToJSON) deriving (Eq, Ord, Generic, JSON.ToJSON)
+5 -1
View File
@@ -246,13 +246,17 @@ spec actualPgVersion =
`shouldRespondWith` `shouldRespondWith`
[json|{"id": 2, "articleStars": [{"userId": 3}]}|] [json|{"id": 2, "articleStars": [{"userId": 3}]}|]
it "can embed an M2M relationship table" $ it "can embed an M2M relationship table" $ do
get "/rpc/getallusers?select=name,tasks(name)&id=gt.1" get "/rpc/getallusers?select=name,tasks(name)&id=gt.1"
`shouldRespondWith` [json|[ `shouldRespondWith` [json|[
{"name":"Michael Scott", "tasks":[{"name":"Design IOS"}, {"name":"Code IOS"}, {"name":"Design OSX"}]}, {"name":"Michael Scott", "tasks":[{"name":"Design IOS"}, {"name":"Code IOS"}, {"name":"Design OSX"}]},
{"name":"Dwight Schrute","tasks":[{"name":"Design w7"}, {"name":"Design IOS"}]} {"name":"Dwight Schrute","tasks":[{"name":"Design w7"}, {"name":"Design IOS"}]}
]|] ]|]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
-- https://github.com/PostgREST/postgrest/issues/2565
get "/rpc/get_yards?select=groups(*)"
`shouldRespondWith` [json|[]|]
{ matchHeaders = [matchContentTypeJson] }
it "can embed an M2M relationship table that has a parent relationship table" $ it "can embed an M2M relationship table that has a parent relationship table" $
get "/rpc/getallusers?select=name,tasks(name,project:projects(name))&id=gt.1" get "/rpc/getallusers?select=name,tasks(name,project:projects(name))&id=gt.1"
+21
View File
@@ -2996,3 +2996,24 @@ CREATE TABLE public.tb (
CREATE VIEW test.va AS SELECT a1 FROM public.ta; CREATE VIEW test.va AS SELECT a1 FROM public.ta;
CREATE VIEW test.vb AS SELECT b1 FROM public.tb; CREATE VIEW test.vb AS SELECT b1 FROM public.tb;
CREATE TABLE test.groups (
name text PRIMARY KEY
);
CREATE TABLE test.yards (
id bigint PRIMARY KEY
);
CREATE TABLE test.group_yard (
id bigint NOT NULL,
group_id text NOT NULL REFERENCES test.groups(name),
yard_id bigint NOT NULL REFERENCES test.yards(id),
PRIMARY KEY (id, group_id, yard_id)
);
CREATE FUNCTION test.get_yards() RETURNS SETOF test.yards
LANGUAGE sql
AS $$
select * from test.yards;
$$;