diff --git a/src/PostgREST/Query/QueryBuilder.hs b/src/PostgREST/Query/QueryBuilder.hs index 056dbdce9..9ae3d08f2 100644 --- a/src/PostgREST/Query/QueryBuilder.hs +++ b/src/PostgREST/Query/QueryBuilder.hs @@ -38,7 +38,7 @@ import Protolude readRequestToQuery :: ReadRequest -> SQL.Snippet readRequestToQuery (Node (Select colSelects mainQi tblAlias implJoins logicForest joinConditions_ ordts range, _) forest) = "SELECT " <> - intercalateSnippet ", " ((pgFmtSelectItem qi <$> colSelects) ++ selects) <> + intercalateSnippet ", " ((pgFmtSelectItem qi <$> colSelects) ++ selects) <> " " <> "FROM " <> SQL.sql (BS.intercalate ", " (tabl : implJs)) <> " " <> intercalateSnippet " " joins <> " " <> (if null logicForest && null joinConditions_ @@ -50,38 +50,31 @@ readRequestToQuery (Node (Select colSelects mainQi tblAlias implJoins logicFores implJs = fromQi <$> implJoins tabl = fromQi mainQi <> maybe mempty (\a -> " AS " <> pgFmtIdent a) tblAlias qi = maybe mainQi (QualifiedIdentifier mempty) tblAlias - (joins, selects) = foldr getJoinsSelects ([],[]) forest + (selects, joins) = foldr getSelectsJoins ([],[]) forest -getJoinsSelects :: ReadRequest -> ([SQL.Snippet], [SQL.Snippet]) -> ([SQL.Snippet], [SQL.Snippet]) -getJoinsSelects rr@(Node (_, (name, Just Relationship{relCardinality=card,relTable=QualifiedIdentifier{qiName=table}}, alias, _, joinType, _)) _) (joins,selects) = - let subquery = readRequestToQuery rr in - case card of - M2O _ _ -> - let aliasOrName = fromMaybe name alias - localTableName = pgFmtIdent $ table <> "_" <> aliasOrName - sel = SQL.sql ("row_to_json(" <> localTableName <> ".*) AS " <> pgFmtIdent aliasOrName) - joi = (if joinType == Just JTInner then " INNER" else " LEFT") - <> " JOIN LATERAL( " <> subquery <> " ) AS " <> SQL.sql localTableName <> " ON TRUE " in - (joi:joins,sel:selects) - _ -> case joinType of - Just JTInner -> - let aliasOrName = fromMaybe name alias - locTblName = table <> "_" <> aliasOrName - localTableName = pgFmtIdent locTblName - internalTableName = pgFmtIdent $ "_" <> locTblName - sel = SQL.sql $ localTableName <> "." <> internalTableName <> " AS " <> pgFmtIdent aliasOrName - joi = "INNER JOIN LATERAL(" <> - "SELECT json_agg(" <> SQL.sql internalTableName <> ") AS " <> SQL.sql internalTableName <> - "FROM (" <> subquery <> " ) AS " <> SQL.sql internalTableName <> - ") AS " <> SQL.sql localTableName <> " ON " <> SQL.sql localTableName <> "IS NOT NULL" in - (joi:joins,sel:selects) +getSelectsJoins :: ReadRequest -> ([SQL.Snippet], [SQL.Snippet]) -> ([SQL.Snippet], [SQL.Snippet]) +getSelectsJoins rr@(Node (_, (name, Just Relationship{relCardinality=card,relTable=QualifiedIdentifier{qiName=table}}, alias, _, joinType, _)) _) (selects,joins) = + let + subquery = readRequestToQuery rr + aliasOrName = fromMaybe name alias + locTblName = table <> "_" <> aliasOrName + localTableName = pgFmtIdent locTblName + internalTableName = pgFmtIdent $ "_" <> locTblName + correlatedSubquery sub al cond = + (if joinType == Just JTInner then "INNER" else "LEFT") <> " JOIN LATERAL ( " <> sub <> " ) AS " <> SQL.sql al <> " ON " <> cond + (sel, joi) = case card of + M2O _ _ -> + ( SQL.sql ("row_to_json(" <> localTableName <> ".*) AS " <> pgFmtIdent aliasOrName) + , correlatedSubquery subquery localTableName "TRUE") _ -> - let sel = "COALESCE ((" - <> "SELECT json_agg(" <> SQL.sql (pgFmtIdent table) <> ".*) " - <> "FROM (" <> subquery <> ") " <> SQL.sql (pgFmtIdent table) <> " " - <> "), '[]') AS " <> SQL.sql (pgFmtIdent (fromMaybe name alias)) in - (joins,sel:selects) -getJoinsSelects (Node (_, (_, Nothing, _, _, _, _)) _) _ = ([], []) + ( SQL.sql $ "COALESCE( " <> localTableName <> "." <> internalTableName <> ", '[]') AS " <> pgFmtIdent aliasOrName + , correlatedSubquery ( + "SELECT json_agg(" <> SQL.sql internalTableName <> ") AS " <> SQL.sql internalTableName <> + "FROM (" <> subquery <> " ) AS " <> SQL.sql internalTableName + ) localTableName $ if joinType == Just JTInner then SQL.sql localTableName <> " IS NOT NULL" else "TRUE") + in + (sel:selects, joi:joins) +getSelectsJoins (Node (_, (_, Nothing, _, _, _, _)) _) _ = ([], []) mutateRequestToQuery :: MutateRequest -> SQL.Snippet mutateRequestToQuery (Insert mainQi iCols body onConflct putConditions returnings) = diff --git a/test/spec/Feature/Query/PlanSpec.hs b/test/spec/Feature/Query/PlanSpec.hs index 56eabf69a..386b2618a 100644 --- a/test/spec/Feature/Query/PlanSpec.hs +++ b/test/spec/Feature/Query/PlanSpec.hs @@ -14,8 +14,8 @@ import Test.Hspec hiding (pendingWith) import Test.Hspec.Wai import Test.Hspec.Wai.JSON -import PostgREST.Config.PgVersion (PgVersion, pgVersion120, - pgVersion130, pgVersion100) +import PostgREST.Config.PgVersion (PgVersion, pgVersion100, + pgVersion120, pgVersion130) import Protolude hiding (get) import SpecHelper @@ -274,8 +274,8 @@ spec actualPgVersion = do let totalCost = simpleBody r ^? nth 0 . key "Plan" . key "Total Cost" liftIO $ totalCost `shouldBe` if actualPgVersion > pgVersion120 - then Just [aesonQQ|58.26|] - else Just [aesonQQ|33.25|] + then Just [aesonQQ|33.25|] + else Just [aesonQQ|33.27|] it "a many to one doesn't surpass a threshold" $ do r <- request methodGet "/projects?select=*,clients(*)&id=eq.1" @@ -293,9 +293,9 @@ spec actualPgVersion = do let totalCost = simpleBody r ^? nth 0 . key "Plan" . key "Total Cost" liftIO $ totalCost `shouldBe` - if | actualPgVersion > pgVersion120 -> Just [aesonQQ|130.44|] - | actualPgVersion > pgVersion100 -> Just [aesonQQ|69.34|] - | otherwise -> Just [aesonQQ|70.79|] + if | actualPgVersion > pgVersion120 -> Just [aesonQQ|69.34|] + | actualPgVersion > pgVersion100 -> Just [aesonQQ|69.36|] + | otherwise -> Just [aesonQQ|70.81|] disabledSpec :: SpecWith ((), Application) disabledSpec =