fix: bad M2M embed on RPC
This commit is contained in:
committed by
Steve Chavez
parent
aaa4fbc370
commit
a525790c4c
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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} _ ->
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Vendored
+21
@@ -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;
|
||||||
|
$$;
|
||||||
|
|||||||
Reference in New Issue
Block a user