From 6398dd2b328e14e7a08119c0726a73c6baa721b6 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Mon, 3 Oct 2022 19:48:35 -0500 Subject: [PATCH] refactor: add plan module * rename/add plan dirs * add read plan func/data * add call plan func/data * add mutate plan func/data --- postgrest.cabal | 7 +- src/PostgREST/App.hs | 56 +++---- .../{Request/DbRequestBuilder.hs => Plan.hs} | 148 +++++++++--------- src/PostgREST/Plan/CallPlan.hs | 25 +++ .../MutateQuery.hs => Plan/MutatePlan.hs} | 9 +- .../ReadQuery.hs => Plan/ReadPlan.hs} | 27 ++-- src/PostgREST/Query.hs | 46 +++--- src/PostgREST/Query/QueryBuilder.hs | 67 ++++---- src/PostgREST/Request/Types.hs | 23 +-- test/spec/QueryCost.hs | 14 +- 10 files changed, 218 insertions(+), 204 deletions(-) rename src/PostgREST/{Request/DbRequestBuilder.hs => Plan.hs} (72%) create mode 100644 src/PostgREST/Plan/CallPlan.hs rename src/PostgREST/{Request/MutateQuery.hs => Plan/MutatePlan.hs} (90%) rename src/PostgREST/{Request/ReadQuery.hs => Plan/ReadPlan.hs} (66%) diff --git a/postgrest.cabal b/postgrest.cabal index 69e3746da9..996cf6614d 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -59,13 +59,14 @@ library PostgREST.Query.QueryBuilder PostgREST.Query.SqlFragment PostgREST.Query.Statements + PostgREST.Plan + PostgREST.Plan.CallPlan + PostgREST.Plan.MutatePlan + PostgREST.Plan.ReadPlan PostgREST.RangeQuery PostgREST.Request.ApiRequest - PostgREST.Request.DbRequestBuilder - PostgREST.Request.MutateQuery PostgREST.Request.Preferences PostgREST.Request.QueryParams - PostgREST.Request.ReadQuery PostgREST.Request.Types PostgREST.Response PostgREST.Response.OpenAPI diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 74a17855c9..d2f0290bdb 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -31,19 +31,18 @@ import qualified Hasql.Transaction.Sessions as SQL import qualified Network.Wai as Wai import qualified Network.Wai.Handler.Warp as Warp -import qualified PostgREST.Admin as Admin -import qualified PostgREST.AppState as AppState -import qualified PostgREST.Auth as Auth -import qualified PostgREST.Cors as Cors -import qualified PostgREST.Error as Error -import qualified PostgREST.Logger as Logger -import qualified PostgREST.Middleware as Middleware -import qualified PostgREST.Query as Query -import qualified PostgREST.Request.ApiRequest as ApiRequest -import qualified PostgREST.Request.DbRequestBuilder as ReqBuilder -import qualified PostgREST.Request.MutateQuery as MutateRequest -import qualified PostgREST.Request.Types as ApiRequestTypes -import qualified PostgREST.Response as Response +import qualified PostgREST.Admin as Admin +import qualified PostgREST.AppState as AppState +import qualified PostgREST.Auth as Auth +import qualified PostgREST.Cors as Cors +import qualified PostgREST.Error as Error +import qualified PostgREST.Logger as Logger +import qualified PostgREST.Middleware as Middleware +import qualified PostgREST.Plan as Plan +import qualified PostgREST.Query as Query +import qualified PostgREST.Request.ApiRequest as ApiRequest +import qualified PostgREST.Request.Types as ApiRequestTypes +import qualified PostgREST.Response as Response import PostgREST.AppState (AppState) import PostgREST.Auth (AuthResult (..)) @@ -57,13 +56,14 @@ import PostgREST.DbStructure.Identifiers (FieldName, import PostgREST.DbStructure.Proc (ProcDescription (..)) import PostgREST.DbStructure.Table (Table (..)) import PostgREST.Error (Error) +import PostgREST.Plan.MutatePlan (MutatePlan) +import PostgREST.Plan.ReadPlan (ReadPlanTree) import PostgREST.Query (DbHandler) import PostgREST.Request.ApiRequest (Action (..), ApiRequest (..), InvokeMethod (..), Mutation (..), Target (..)) import PostgREST.Request.Preferences (PreferRepresentation (..)) -import PostgREST.Request.ReadQuery (ReadRequest) import PostgREST.Version (prettyVersion) import PostgREST.Workers (connectionWorker, listener) @@ -223,7 +223,7 @@ handleRequest context@(RequestContext _ _ ApiRequest{..} _) = handleRead :: Bool -> QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response handleRead headersOnly identifier context@RequestContext{..} = do - req <- liftEither $ readRequest identifier context + req <- liftEither $ readPlan identifier context (resultSet, total) <- Query.readQuery req ctxConfig ctxApiRequest @@ -237,7 +237,7 @@ handleCreate identifier context@RequestContext{..} = do then maybe mempty tablePKCols $ HM.lookup identifier $ dbTables ctxDbStructure else mempty - (mutateReq, readReq) <- liftEither $ writeRequest MutationCreate identifier context pkCols + (mutateReq, readReq) <- liftEither $ mutatePlan MutationCreate identifier context pkCols resultSet <- Query.createQuery mutateReq readReq pkCols ctxApiRequest ctxConfig @@ -245,20 +245,20 @@ handleCreate identifier context@RequestContext{..} = do handleUpdate :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response handleUpdate identifier context@(RequestContext ctxConfig _ ctxApiRequest _) = do - (mutateReq, readReq) <- liftEither $ writeRequest MutationUpdate identifier context mempty + (mutateReq, readReq) <- liftEither $ mutatePlan MutationUpdate identifier context mempty resultSet <- Query.updateQuery mutateReq readReq ctxApiRequest ctxConfig pure $ Response.updateResponse ctxApiRequest resultSet handleSingleUpsert :: QualifiedIdentifier -> RequestContext-> DbHandler Wai.Response handleSingleUpsert identifier context@(RequestContext ctxConfig ctxDbStructure ctxApiRequest _) = do let pkCols = maybe mempty tablePKCols $ HM.lookup identifier $ dbTables ctxDbStructure - (mutateReq, readReq) <- liftEither $ writeRequest MutationSingleUpsert identifier context pkCols + (mutateReq, readReq) <- liftEither $ mutatePlan MutationSingleUpsert identifier context pkCols resultSet <- Query.singleUpsertQuery mutateReq readReq ctxApiRequest ctxConfig pure $ Response.singleUpsertResponse ctxApiRequest resultSet handleDelete :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response handleDelete identifier context@(RequestContext ctxConfig _ ctxApiRequest _) = do - (mutateReq, readReq) <- liftEither $ writeRequest MutationDelete identifier context mempty + (mutateReq, readReq) <- liftEither $ mutatePlan MutationDelete identifier context mempty resultSet <- Query.deleteQuery mutateReq readReq ctxApiRequest ctxConfig pure $ Response.deleteResponse ctxApiRequest resultSet @@ -270,8 +270,8 @@ handleInvoke invMethod proc context@RequestContext{..} = do (pdSchema proc) (fromMaybe (pdName proc) $ Proc.procTableName proc) - readReq <- liftEither $ readRequest identifier context - let callReq = ReqBuilder.callRequest proc ctxApiRequest readReq + readReq <- liftEither $ readPlan identifier context + let callReq = Plan.callPlan proc ctxApiRequest readReq resultSet <- Query.invokeQuery proc callReq readReq ctxApiRequest ctxConfig @@ -282,14 +282,14 @@ handleOpenApi headersOnly tSchema (RequestContext conf dbStructure apiRequest pg oaiResult <- Query.openApiQuery dbStructure pgVer conf tSchema pure $ Response.openApiResponse headersOnly oaiResult conf dbStructure (iSchema apiRequest) (iNegotiatedByProfile apiRequest) -writeRequest :: Mutation -> QualifiedIdentifier -> RequestContext -> [FieldName] -> Either Error (MutateRequest.MutateRequest, ReadRequest) -writeRequest mutation identifier@QualifiedIdentifier{..} context@RequestContext{..} pkCols = do - readReq <- readRequest identifier context - mutateReq <- ReqBuilder.mutateRequest mutation qiSchema qiName ctxApiRequest pkCols readReq +mutatePlan :: Mutation -> QualifiedIdentifier -> RequestContext -> [FieldName] -> Either Error (MutatePlan, ReadPlanTree) +mutatePlan mutation identifier@QualifiedIdentifier{..} context@RequestContext{..} pkCols = do + readReq <- readPlan identifier context + mutateReq <- Plan.mutatePlan mutation qiSchema qiName ctxApiRequest pkCols readReq pure (mutateReq, readReq) -readRequest :: QualifiedIdentifier -> RequestContext -> Either Error ReadRequest -readRequest QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure apiRequest _) = - ReqBuilder.readRequest qiSchema qiName configDbMaxRows +readPlan :: QualifiedIdentifier -> RequestContext -> Either Error ReadPlanTree +readPlan QualifiedIdentifier{..} (RequestContext AppConfig{..} dbStructure apiRequest _) = + Plan.readPlan qiSchema qiName configDbMaxRows (dbRelationships dbStructure) apiRequest diff --git a/src/PostgREST/Request/DbRequestBuilder.hs b/src/PostgREST/Plan.hs similarity index 72% rename from src/PostgREST/Request/DbRequestBuilder.hs rename to src/PostgREST/Plan.hs index f0856976f7..a9cd76fd14 100644 --- a/src/PostgREST/Request/DbRequestBuilder.hs +++ b/src/PostgREST/Plan.hs @@ -1,9 +1,9 @@ {-| -Module : PostgREST.Request.DbRequestBuilder -Description : PostgREST database request builder +Module : PostgREST.Plan +Description : PostgREST Request Planner This module is in charge of building an intermediate -representation(ReadRequest, MutateRequest) between the HTTP request and the +representation between the HTTP request and the final resulting SQL query. A query tree is built in case of resource embedding. By inferring the @@ -15,10 +15,10 @@ resource. {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} -module PostgREST.Request.DbRequestBuilder - ( readRequest - , mutateRequest - , callRequest +module PostgREST.Plan + ( readPlan + , mutatePlan + , callPlan ) where import qualified Data.HashMap.Strict as HM @@ -48,20 +48,21 @@ import PostgREST.Request.ApiRequest (Action (..), Mutation (..), Payload (..)) -import PostgREST.Request.MutateQuery +import PostgREST.Plan.CallPlan +import PostgREST.Plan.MutatePlan +import PostgREST.Plan.ReadPlan as ReadPlan import PostgREST.Request.Preferences -import PostgREST.Request.ReadQuery as ReadQuery import PostgREST.Request.Types import qualified PostgREST.Request.QueryParams as QueryParams import Protolude hiding (from) --- | Builds the ReadRequest tree on a number of stages. +-- | Builds the ReadPlan tree on a number of stages. -- | Adds filters, order, limits on its respective nodes. -- | Adds joins conditions obtained from resource embedding. -readRequest :: Schema -> TableName -> Maybe Integer -> RelationshipsMap -> ApiRequest -> Either Error ReadRequest -readRequest schema rootTableName maxRows allRels apiRequest = +readPlan :: Schema -> TableName -> Maybe Integer -> RelationshipsMap -> ApiRequest -> Either Error ReadPlanTree +readPlan schema rootTableName maxRows allRels apiRequest = mapLeft ApiRequestError $ treeRestrictRange maxRows (iAction apiRequest) =<< augmentRequestWithJoin schema allRels =<< @@ -80,67 +81,67 @@ readRequest schema rootTableName maxRows allRels apiRequest = -- can differentiate the parent and child tables by having an alias like -- "table_depth", this is related to -- http://github.com/PostgREST/postgrest/issues/987. -initReadRequest :: QualifiedIdentifier -> Maybe Alias -> [Tree SelectItem] -> ReadRequest +initReadRequest :: QualifiedIdentifier -> Maybe Alias -> [Tree SelectItem] -> ReadPlanTree initReadRequest rootQi rootAlias = foldr (treeEntry rootDepth) initial where rootDepth = 0 rootSchema = qiSchema rootQi rootName = qiName rootQi - initial = Node (Select [] rootQi rootAlias [] [] [] allRange, (rootName, Nothing, Nothing, Nothing, Nothing, rootDepth)) [] - treeEntry :: Depth -> Tree SelectItem -> ReadRequest -> ReadRequest - treeEntry depth (Node fld@((fn, _),_,alias, hint, joinType) fldForest) (Node (q, i) rForest) = + initial = Node (ReadPlan [] rootQi rootAlias [] [] [] allRange rootName Nothing Nothing Nothing Nothing rootDepth) [] + treeEntry :: Depth -> Tree SelectItem -> ReadPlanTree -> ReadPlanTree + treeEntry depth (Node fld@((fn, _),_,alias, hint, joinType) fldForest) (Node q rForest) = let nxtDepth = succ depth in case fldForest of - [] -> Node (q {select=fld:select q}, i) rForest - _ -> Node (q, i) $ + [] -> Node q{select=fld:select q} rForest + _ -> Node q $ foldr (treeEntry nxtDepth) - (Node (Select [] (QualifiedIdentifier rootSchema fn) Nothing [] [] [] allRange, - (fn, Nothing, alias, hint, joinType, nxtDepth)) []) + (Node (ReadPlan [] (QualifiedIdentifier rootSchema fn) Nothing [] [] [] allRange fn Nothing alias hint joinType nxtDepth) []) fldForest:rForest -- | Enforces the `max-rows` config on the result -treeRestrictRange :: Maybe Integer -> Action -> ReadRequest -> Either ApiRequestError ReadRequest +treeRestrictRange :: Maybe Integer -> Action -> ReadPlanTree -> Either ApiRequestError ReadPlanTree treeRestrictRange _ (ActionMutate _) request = Right request treeRestrictRange maxRows _ request = pure $ nodeRestrictRange maxRows <$> request where - nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode - nodeRestrictRange m (q@Select {range_=r}, i) = (q{range_=restrictRange m r }, i) + nodeRestrictRange :: Maybe Integer -> ReadPlan -> ReadPlan + nodeRestrictRange m q@ReadPlan{range_=r} = q{range_=restrictRange m r } -augmentRequestWithJoin :: Schema -> RelationshipsMap -> ReadRequest -> Either ApiRequestError ReadRequest +augmentRequestWithJoin :: Schema -> RelationshipsMap -> ReadPlanTree -> Either ApiRequestError ReadPlanTree augmentRequestWithJoin schema allRels request = addJoinConditions Nothing <$> addRels schema allRels Nothing request -addRels :: Schema -> RelationshipsMap -> Maybe ReadRequest -> ReadRequest -> Either ApiRequestError ReadRequest -addRels schema allRels parentNode (Node (query@Select{from=tbl}, (nodeName, _, alias, hint, joinType, depth)) forest) = +addRels :: Schema -> RelationshipsMap -> Maybe ReadPlanTree -> ReadPlanTree -> Either ApiRequestError ReadPlanTree +addRels schema allRels parentNode (Node query@ReadPlan{from=tbl,nodeName,nodeHint,nodeDepth} forest) = case parentNode of - Just (Node (Select{from=parentNodeQi, fromAlias=aliasQi}, _) _) -> + Just (Node ReadPlan{from=parentNodeQi, fromAlias=aliasQi} _) -> let newFrom r = if qiName tbl == nodeName then relForeignTable r else tbl - newReadNode = (\r -> + newReadPlan = (\r -> if not $ relIsSelf r -- add alias if self rel TODO consolidate aliasing in another function - then (query{from=newFrom r}, (nodeName, Just r, alias, hint, joinType, depth)) - else (query{from=newFrom r, fromAlias=Just (qiName (newFrom r) <> "_" <> show depth)}, (nodeName, Just r, alias, hint, joinType, depth)) + then query{from=newFrom r, nodeRel=Just r} + else query{from=newFrom r, nodeRel=Just r, fromAlias=Just (qiName (newFrom r) <> "_" <> show nodeDepth)} ) <$> rel - origin = if depth == 1 -- Only on depth 1 we check if the root(depth 0) has an alias so the sourceCTEName alias can be found as a relationship + origin = if nodeDepth == 1 -- Only on depth 1 we check if the root(depth 0) has an alias so the sourceCTEName alias can be found as a relationship then fromMaybe (qiName parentNodeQi) aliasQi else qiName parentNodeQi - rel = findRel schema allRels origin nodeName hint + rel = findRel schema allRels origin nodeName nodeHint in - Node <$> newReadNode <*> (updateForest . hush $ Node <$> newReadNode <*> pure forest) + Node <$> newReadPlan <*> (updateForest . hush $ Node <$> newReadPlan <*> pure forest) _ -> - let rn = (query, (nodeName, Nothing, alias, Nothing, joinType, depth)) in - Node rn <$> updateForest (Just $ Node rn forest) + Node query <$> updateForest (Just $ Node query forest) where - updateForest :: Maybe ReadRequest -> Either ApiRequestError [ReadRequest] + updateForest :: Maybe ReadPlanTree -> Either ApiRequestError [ReadPlanTree] updateForest rq = addRels schema allRels rq `traverse` forest -- applies aliasing to join conditions TODO refactor, this should go into the querybuilder module -addJoinConditions :: Maybe Alias -> ReadRequest -> ReadRequest -addJoinConditions _ (Node node@(Select{fromAlias=tblAlias}, (_, Nothing, _, _, _, _)) forest) = Node node (addJoinConditions tblAlias <$> forest) -addJoinConditions _ (Node node@(Select{fromAlias=tblAlias}, (_, Just ComputedRelationship{}, _, _, _, _)) forest) = Node node (addJoinConditions tblAlias <$> forest) -addJoinConditions previousAlias (Node (query@Select{fromAlias=tblAlias}, nodeProps@(_, Just (Relationship QualifiedIdentifier{qiSchema=tSchema, qiName=tN} QualifiedIdentifier{qiName=ftN} _ card _ _), _, _, _, _)) forest) = - Node (query{joinConditions=joinConds}, nodeProps) (addJoinConditions tblAlias <$> forest) +addJoinConditions :: Maybe Alias -> ReadPlanTree -> ReadPlanTree +addJoinConditions _ (Node node@ReadPlan{fromAlias=tblAlias, nodeRel=Nothing} forest) = Node node (addJoinConditions tblAlias <$> forest) +addJoinConditions _ (Node node@ReadPlan{fromAlias=tblAlias, nodeRel=Just ComputedRelationship{}} forest) = Node node (addJoinConditions tblAlias <$> forest) +addJoinConditions previousAlias (Node query@ReadPlan{fromAlias=tblAlias, nodeRel=Just Relationship{relTable=qi,relForeignTable=fQi,relCardinality=card}} forest) = + Node query{joinConditions=joinConds} (addJoinConditions tblAlias <$> forest) where + QualifiedIdentifier{qiSchema=tSchema, qiName=tN} = qi + QualifiedIdentifier{qiName=ftN} = fQi joinConds = case card of M2M (Junction QualifiedIdentifier{qiName=jtn} _ _ jcols1 jcols2) -> @@ -241,7 +242,7 @@ findRel schema allRels origin target hint = ) ) $ fromMaybe mempty $ HM.lookup (QualifiedIdentifier schema origin, schema) allRels -addFilters :: ApiRequest -> ReadRequest -> Either ApiRequestError ReadRequest +addFilters :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree addFilters ApiRequest{..} rReq = foldr addFilterToNode (Right rReq) flts where @@ -254,11 +255,11 @@ addFilters ApiRequest{..} rReq = ActionRead _ -> qsFilters _ -> qsFiltersNotRoot - addFilterToNode :: (EmbedPath, Filter) -> Either ApiRequestError ReadRequest -> Either ApiRequestError ReadRequest + addFilterToNode :: (EmbedPath, Filter) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree addFilterToNode = - updateNode (\flt (Node (q@Select {where_=lf}, i) f) -> Node (q{ReadQuery.where_=addFilterToLogicForest flt lf}, i) f) + updateNode (\flt (Node q@ReadPlan{where_=lf} f) -> Node q{ReadPlan.where_=addFilterToLogicForest flt lf} f) -addOrders :: ApiRequest -> ReadRequest -> Either ApiRequestError ReadRequest +addOrders :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree addOrders ApiRequest{..} rReq = case iAction of ActionMutate _ -> Right rReq @@ -266,10 +267,10 @@ addOrders ApiRequest{..} rReq = where QueryParams.QueryParams{..} = iQueryParams - addOrderToNode :: (EmbedPath, [OrderTerm]) -> Either ApiRequestError ReadRequest -> Either ApiRequestError ReadRequest - addOrderToNode = updateNode (\o (Node (q,i) f) -> Node (q{order=o}, i) f) + addOrderToNode :: (EmbedPath, [OrderTerm]) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree + addOrderToNode = updateNode (\o (Node q f) -> Node q{order=o} f) -addRanges :: ApiRequest -> ReadRequest -> Either ApiRequestError ReadRequest +addRanges :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree addRanges ApiRequest{..} rReq = case iAction of ActionMutate _ -> Right rReq @@ -278,20 +279,20 @@ addRanges ApiRequest{..} rReq = ranges :: Either ApiRequestError [(EmbedPath, NonnegRange)] ranges = first QueryParamError $ QueryParams.pRequestRange `traverse` HM.toList iRange - addRangeToNode :: (EmbedPath, NonnegRange) -> Either ApiRequestError ReadRequest -> Either ApiRequestError ReadRequest - addRangeToNode = updateNode (\r (Node (q,i) f) -> Node (q{range_=r}, i) f) + addRangeToNode :: (EmbedPath, NonnegRange) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree + addRangeToNode = updateNode (\r (Node q f) -> Node q{range_=r} f) -addLogicTrees :: ApiRequest -> ReadRequest -> Either ApiRequestError ReadRequest +addLogicTrees :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree addLogicTrees ApiRequest{..} rReq = foldr addLogicTreeToNode (Right rReq) qsLogic where QueryParams.QueryParams{..} = iQueryParams - addLogicTreeToNode :: (EmbedPath, LogicTree) -> Either ApiRequestError ReadRequest -> Either ApiRequestError ReadRequest - addLogicTreeToNode = updateNode (\t (Node (q@Select{where_=lf},i) f) -> Node (q{ReadQuery.where_=t:lf}, i) f) + addLogicTreeToNode :: (EmbedPath, LogicTree) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree + addLogicTreeToNode = updateNode (\t (Node q@ReadPlan{where_=lf} f) -> Node q{ReadPlan.where_=t:lf} f) -- Find a Node of the Tree and apply a function to it -updateNode :: (a -> ReadRequest -> ReadRequest) -> (EmbedPath, a) -> Either ApiRequestError ReadRequest -> Either ApiRequestError ReadRequest +updateNode :: (a -> ReadPlanTree -> ReadPlanTree) -> (EmbedPath, a) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree updateNode f ([], a) rr = f a <$> rr updateNode _ _ (Left e) = Left e updateNode f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) = @@ -301,11 +302,11 @@ updateNode f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) = (\node -> Node rootNode $ node : delete target forest) <$> updateNode f (remainingPath, a) (Right target) where - findNode :: Maybe ReadRequest - findNode = find (\(Node (_,(nodeName,_,alias,_,_, _)) _) -> nodeName == targetNodeName || alias == Just targetNodeName) forest + findNode :: Maybe ReadPlanTree + findNode = find (\(Node ReadPlan{nodeName, nodeAlias} _) -> nodeName == targetNodeName || nodeAlias == Just targetNodeName) forest -mutateRequest :: Mutation -> Schema -> TableName -> ApiRequest -> [FieldName] -> ReadRequest -> Either Error MutateRequest -mutateRequest mutation schema tName ApiRequest{..} pkCols readReq = mapLeft ApiRequestError $ +mutatePlan :: Mutation -> Schema -> TableName -> ApiRequest -> [FieldName] -> ReadPlanTree -> Either Error MutatePlan +mutatePlan mutation schema tName ApiRequest{..} pkCols readReq = mapLeft ApiRequestError $ case mutation of MutationCreate -> Right $ Insert qi iColumns body ((,) <$> iPreferResolution <*> Just confCols) [] returnings @@ -334,8 +335,8 @@ mutateRequest mutation schema tName ApiRequest{..} pkCols readReq = mapLeft ApiR combinedLogic = foldr addFilterToLogicForest logic qsFiltersRoot body = payRaw <$> iPayload -- the body is assumed to be json at this stage(ApiRequest validates) -callRequest :: ProcDescription -> ApiRequest -> ReadRequest -> CallRequest -callRequest proc apiReq readReq = FunctionCall { +callPlan :: ProcDescription -> ApiRequest -> ReadPlanTree -> CallPlan +callPlan proc apiReq readReq = FunctionCall { funCQi = QualifiedIdentifier (pdSchema proc) (pdName proc) , funCParams = callParams , funCArgs = payRaw <$> iPayload apiReq @@ -352,7 +353,7 @@ callRequest proc apiReq readReq = FunctionCall { prms -> KeyParams $ specifiedParams prms specifiedParams = filter (\x -> ppName x `S.member` iColumns apiReq) -returningCols :: ReadRequest -> [FieldName] -> [FieldName] +returningCols :: ReadPlanTree -> [FieldName] -> [FieldName] returningCols rr@(Node _ forest) pkCols -- if * is part of the select, we must not add pk or fk columns manually - -- otherwise those would be selected and output twice @@ -360,24 +361,31 @@ returningCols rr@(Node _ forest) pkCols | otherwise = returnings where fldNames = fstFieldNames rr - -- Without fkCols, when a mutateRequest to + -- Without fkCols, when a mutatePlan to -- /projects?select=name,clients(name) occurs, the RETURNING SQL part would -- be `RETURNING name`(see QueryBuilder). This would make the embedding -- fail because the following JOIN would need the "client_id" column from -- projects. So this adds the foreign key columns to ensure the embedding -- succeeds, result would be `RETURNING name, client_id`. fkCols = concat $ mapMaybe (\case - Node (_, (_, Just Relationship{relCardinality=O2M _ cols}, _, _, _, _)) _ -> Just $ fst <$> cols - Node (_, (_, Just Relationship{relCardinality=M2O _ cols}, _, _, _, _)) _ -> Just $ fst <$> cols - Node (_, (_, Just Relationship{relCardinality=O2O _ cols}, _, _, _, _)) _ -> Just $ fst <$> cols - Node (_, (_, Just Relationship{relCardinality=M2M Junction{junColumns1, junColumns2}}, _, _, _, _)) _ -> Just $ (fst <$> junColumns1) ++ (fst <$> junColumns2) - _ -> Nothing + Node ReadPlan{nodeRel=Just Relationship{relCardinality=O2M _ cols}} _ -> + Just $ fst <$> cols + Node ReadPlan{nodeRel=Just Relationship{relCardinality=M2O _ cols}} _ -> + Just $ fst <$> cols + Node ReadPlan{nodeRel=Just Relationship{relCardinality=O2O _ cols}} _ -> + Just $ fst <$> cols + Node ReadPlan{nodeRel=Just Relationship{relCardinality=M2M Junction{junColumns1, junColumns2}}} _ -> + Just $ (fst <$> junColumns1) ++ (fst <$> junColumns2) + Node ReadPlan{nodeRel=Just ComputedRelationship{}} _ -> + Nothing + Node ReadPlan{nodeRel=Nothing} _ -> + Nothing ) forest hasComputedRel = isJust $ find (\case - Node (_, (_, Just ComputedRelationship{}, _, _, _, _)) _ -> True - _ -> False + Node ReadPlan{nodeRel=Just ComputedRelationship{}} _ -> True + _ -> False ) forest - -- However if the "client_id" is present, e.g. mutateRequest to + -- However if the "client_id" is present, e.g. mutatePlan to -- /projects?select=client_id,name,clients(name) we would get `RETURNING -- client_id, name, client_id` and then we would produce the "column -- reference \"client_id\" is ambiguous" error from PostgreSQL. So we diff --git a/src/PostgREST/Plan/CallPlan.hs b/src/PostgREST/Plan/CallPlan.hs new file mode 100644 index 0000000000..10817ac504 --- /dev/null +++ b/src/PostgREST/Plan/CallPlan.hs @@ -0,0 +1,25 @@ +module PostgREST.Plan.CallPlan + ( CallPlan(..) + , CallParams(..) + ) +where + +import qualified Data.ByteString.Lazy as LBS +import PostgREST.DbStructure.Identifiers (FieldName, + QualifiedIdentifier) +import PostgREST.DbStructure.Proc (ProcParam (..)) + +import Protolude + +data CallPlan = FunctionCall + { funCQi :: QualifiedIdentifier + , funCParams :: CallParams + , funCArgs :: Maybe LBS.ByteString + , funCScalar :: Bool + , funCMultipleCall :: Bool + , funCReturning :: [FieldName] + } + +data CallParams + = KeyParams [ProcParam] -- ^ Call with key params: func(a := val1, b:= val2) + | OnePosParam ProcParam -- ^ Call with positional params(only one supported): func(val) diff --git a/src/PostgREST/Request/MutateQuery.hs b/src/PostgREST/Plan/MutatePlan.hs similarity index 90% rename from src/PostgREST/Request/MutateQuery.hs rename to src/PostgREST/Plan/MutatePlan.hs index 5d5d856161..395dd95f9b 100644 --- a/src/PostgREST/Request/MutateQuery.hs +++ b/src/PostgREST/Plan/MutatePlan.hs @@ -1,6 +1,5 @@ -module PostgREST.Request.MutateQuery - ( MutateQuery(..) - , MutateRequest +module PostgREST.Plan.MutatePlan + ( MutatePlan(..) ) where @@ -15,9 +14,7 @@ import PostgREST.Request.Types (LogicTree, OrderTerm) import Protolude -type MutateRequest = MutateQuery - -data MutateQuery +data MutatePlan = Insert { in_ :: QualifiedIdentifier , insCols :: S.Set FieldName diff --git a/src/PostgREST/Request/ReadQuery.hs b/src/PostgREST/Plan/ReadPlan.hs similarity index 66% rename from src/PostgREST/Request/ReadQuery.hs rename to src/PostgREST/Plan/ReadPlan.hs index ed7092351c..4d459892e5 100644 --- a/src/PostgREST/Request/ReadQuery.hs +++ b/src/PostgREST/Plan/ReadPlan.hs @@ -1,7 +1,7 @@ -module PostgREST.Request.ReadQuery - ( ReadNode - , ReadQuery(..) - , ReadRequest +{-# LANGUAGE NamedFieldPuns #-} +module PostgREST.Plan.ReadPlan + ( ReadPlanTree + , ReadPlan(..) , fstFieldNames ) where @@ -19,12 +19,9 @@ import PostgREST.Request.Types (Alias, Depth, Hint, import Protolude -type ReadRequest = Tree ReadNode +type ReadPlanTree = Tree ReadPlan -type ReadNode = - (ReadQuery, (NodeName, Maybe Relationship, Maybe Alias, Maybe Hint, Maybe JoinType, Depth)) - -data ReadQuery = Select +data ReadPlan = ReadPlan { select :: [SelectItem] , from :: QualifiedIdentifier , fromAlias :: Maybe Alias @@ -33,10 +30,16 @@ data ReadQuery = Select , joinConditions :: [JoinCondition] , order :: [OrderTerm] , range_ :: NonnegRange + , nodeName :: NodeName + , nodeRel :: Maybe Relationship + , nodeAlias :: Maybe Alias + , nodeHint :: Maybe Hint + , nodeJoinType :: Maybe JoinType + , nodeDepth :: Depth } deriving (Eq) -- First level FieldNames(e.g get a,b from /table?select=a,b,other(c,d)) -fstFieldNames :: ReadRequest -> [FieldName] -fstFieldNames (Node (sel, _) _) = - fst . (\(f, _, _, _, _) -> f) <$> select sel +fstFieldNames :: ReadPlanTree -> [FieldName] +fstFieldNames (Node ReadPlan{select} _) = + fst . (\(f, _, _, _, _) -> f) <$> select diff --git a/src/PostgREST/Query.hs b/src/PostgREST/Query.hs index d696dccb53..3d27e40da1 100644 --- a/src/PostgREST/Query.hs +++ b/src/PostgREST/Query.hs @@ -24,14 +24,12 @@ import qualified Hasql.DynamicStatements.Statement as SQL import qualified Hasql.Transaction as SQL import qualified Hasql.Transaction.Sessions as SQL -import qualified PostgREST.DbStructure as DbStructure -import qualified PostgREST.DbStructure.Proc as Proc -import qualified PostgREST.Error as Error -import qualified PostgREST.Query.QueryBuilder as QueryBuilder -import qualified PostgREST.Query.Statements as Statements -import qualified PostgREST.RangeQuery as RangeQuery -import qualified PostgREST.Request.MutateQuery as MutateRequest -import qualified PostgREST.Request.Types as ApiRequestTypes +import qualified PostgREST.DbStructure as DbStructure +import qualified PostgREST.DbStructure.Proc as Proc +import qualified PostgREST.Error as Error +import qualified PostgREST.Query.QueryBuilder as QueryBuilder +import qualified PostgREST.Query.Statements as Statements +import qualified PostgREST.RangeQuery as RangeQuery import Data.Scientific (FPFormat (..), formatScientific, isInteger) @@ -49,6 +47,9 @@ import PostgREST.DbStructure.Proc (ProcDescription (..), import PostgREST.DbStructure.Table (TablesMap) import PostgREST.Error (Error) import PostgREST.MediaType (MediaType (..)) +import PostgREST.Plan.CallPlan (CallPlan) +import PostgREST.Plan.MutatePlan (MutatePlan) +import PostgREST.Plan.ReadPlan (ReadPlanTree) import PostgREST.Query.SqlFragment (fromQi, intercalateSnippet, pgFmtIdentList, setConfigLocal, @@ -61,19 +62,18 @@ import PostgREST.Request.ApiRequest (Action (..), import PostgREST.Request.Preferences (PreferCount (..), PreferParameters (..), shouldCount) -import PostgREST.Request.ReadQuery (ReadRequest) import Protolude hiding (Handler) type DbHandler = ExceptT Error SQL.Transaction -readQuery :: ReadRequest -> AppConfig -> ApiRequest -> DbHandler (ResultSet, Maybe Int64) +readQuery :: ReadPlanTree -> AppConfig -> ApiRequest -> DbHandler (ResultSet, Maybe Int64) readQuery req conf@AppConfig{..} apiReq@ApiRequest{..} = do - let countQuery = QueryBuilder.readRequestToCountQuery req + let countQuery = QueryBuilder.readPlanToCountQuery req resultSet <- lift . SQL.statement mempty $ Statements.prepareRead - (QueryBuilder.readRequestToQuery req) + (QueryBuilder.readPlanToQuery req) (if iPreferCount == Just EstimatedCount then -- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed QueryBuilder.limitedQuery countQuery ((+ 1) <$> configDbMaxRows) @@ -106,20 +106,20 @@ readTotal AppConfig{..} ApiRequest{..} RSStandard{rsTableTotal=tableTotal} count lift . SQL.statement mempty . Statements.preparePlanRows countQuery $ configDbPreparedStatements -createQuery :: MutateRequest.MutateRequest -> ReadRequest -> [FieldName] -> ApiRequest -> AppConfig -> DbHandler ResultSet +createQuery :: MutatePlan -> ReadPlanTree -> [FieldName] -> ApiRequest -> AppConfig -> DbHandler ResultSet createQuery mutateReq readReq pkCols apiReq@ApiRequest{..} conf = do resultSet <- writeQuery mutateReq readReq True pkCols apiReq conf failNotSingular iAcceptMediaType resultSet pure resultSet -updateQuery :: MutateRequest.MutateRequest -> ReadRequest -> ApiRequest -> AppConfig -> DbHandler ResultSet +updateQuery :: MutatePlan -> ReadPlanTree -> ApiRequest -> AppConfig -> DbHandler ResultSet updateQuery mutateReq readReq apiReq@ApiRequest{..} conf = do resultSet <- writeQuery mutateReq readReq False mempty apiReq conf failNotSingular iAcceptMediaType resultSet failsChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resultSet pure resultSet -singleUpsertQuery :: MutateRequest.MutateRequest -> ReadRequest -> ApiRequest -> AppConfig -> DbHandler ResultSet +singleUpsertQuery :: MutatePlan -> ReadPlanTree -> ApiRequest -> AppConfig -> DbHandler ResultSet singleUpsertQuery mutateReq readReq apiReq conf = do resultSet <- writeQuery mutateReq readReq False mempty apiReq conf failPut resultSet @@ -137,23 +137,23 @@ failPut RSStandard{rsQueryTotal=queryTotal} = lift SQL.condemn throwError Error.PutMatchingPkError -deleteQuery :: MutateRequest.MutateRequest -> ReadRequest -> ApiRequest -> AppConfig -> DbHandler ResultSet +deleteQuery :: MutatePlan -> ReadPlanTree -> ApiRequest -> AppConfig -> DbHandler ResultSet deleteQuery mutateReq readReq apiReq@ApiRequest{..} conf = do resultSet <- writeQuery mutateReq readReq False mempty apiReq conf failNotSingular iAcceptMediaType resultSet failsChangesOffLimits (RangeQuery.rangeLimit iTopLevelRange) resultSet pure resultSet -invokeQuery :: ProcDescription -> ApiRequestTypes.CallRequest -> ReadRequest -> ApiRequest -> AppConfig -> DbHandler ResultSet +invokeQuery :: ProcDescription -> CallPlan -> ReadPlanTree -> ApiRequest -> AppConfig -> DbHandler ResultSet invokeQuery proc callReq readReq ApiRequest{..} AppConfig{..} = do resultSet <- lift . SQL.statement mempty $ Statements.prepareCall (Proc.procReturnsScalar proc) (Proc.procReturnsSingle proc) - (QueryBuilder.requestToCallProcQuery callReq) - (QueryBuilder.readRequestToQuery readReq) - (QueryBuilder.readRequestToCountQuery readReq) + (QueryBuilder.callPlanToQuery callReq) + (QueryBuilder.readPlanToQuery readReq) + (QueryBuilder.readPlanToCountQuery readReq) (shouldCount iPreferCount) iAcceptMediaType (iPreferParameters == Just MultipleObjects) @@ -197,12 +197,12 @@ txMode ApiRequest{..} = _ -> SQL.Write -writeQuery :: MutateRequest.MutateRequest -> ReadRequest -> Bool -> [Text] -> ApiRequest -> AppConfig -> DbHandler ResultSet +writeQuery :: MutatePlan -> ReadPlanTree -> Bool -> [Text] -> ApiRequest -> AppConfig -> DbHandler ResultSet writeQuery mutateReq readReq isInsert pkCols apiReq conf = do lift . SQL.statement mempty $ Statements.prepareWrite - (QueryBuilder.readRequestToQuery readReq) - (QueryBuilder.mutateRequestToQuery mutateReq) + (QueryBuilder.readPlanToQuery readReq) + (QueryBuilder.mutatePlanToQuery mutateReq) isInsert (iAcceptMediaType apiReq) (iPreferRepresentation apiReq) diff --git a/src/PostgREST/Query/QueryBuilder.hs b/src/PostgREST/Query/QueryBuilder.hs index f320218595..286993bbbe 100644 --- a/src/PostgREST/Query/QueryBuilder.hs +++ b/src/PostgREST/Query/QueryBuilder.hs @@ -5,14 +5,14 @@ Module : PostgREST.Query.QueryBuilder Description : PostgREST SQL queries generating functions. This module provides functions to consume data types that -represent database queries (e.g. ReadRequest, MutateRequest) and SqlFragment +represent database queries (e.g. ReadPlanTree, MutatePlan) and SqlFragment to produce SqlQuery type outputs. -} module PostgREST.Query.QueryBuilder - ( readRequestToQuery - , mutateRequestToQuery - , readRequestToCountQuery - , requestToCallProcQuery + ( readPlanToQuery + , mutatePlanToQuery + , readPlanToCountQuery + , callPlanToQuery , limitedQuery ) where @@ -29,35 +29,36 @@ import PostgREST.DbStructure.Relationship (Cardinality (..), Relationship (..)) import PostgREST.Request.Preferences (PreferResolution (..)) +import PostgREST.Plan.CallPlan +import PostgREST.Plan.MutatePlan +import PostgREST.Plan.ReadPlan import PostgREST.Query.SqlFragment -import PostgREST.RangeQuery (allRange) -import PostgREST.Request.MutateQuery -import PostgREST.Request.ReadQuery +import PostgREST.RangeQuery (allRange) import PostgREST.Request.Types import Protolude -readRequestToQuery :: ReadRequest -> SQL.Snippet -readRequestToQuery (Node (Select colSelects mainQi tblAlias logicForest joinConditions_ ordts range, (_, rel, _, _, _, _)) forest) = +readPlanToQuery :: ReadPlanTree -> SQL.Snippet +readPlanToQuery (Node ReadPlan{select,from=mainQi,fromAlias,where_=logicForest,joinConditions, order, range_=readRange, nodeRel} forest) = "SELECT " <> - intercalateSnippet ", " ((pgFmtSelectItem qi <$> colSelects) ++ selects) <> " " <> + intercalateSnippet ", " ((pgFmtSelectItem qi <$> select) ++ selects) <> " " <> fromFrag <> " " <> intercalateSnippet " " joins <> " " <> - (if null logicForest && null joinConditions_ + (if null logicForest && null joinConditions then mempty - else "WHERE " <> intercalateSnippet " AND " (map (pgFmtLogicTree qi) logicForest ++ map pgFmtJoinCondition joinConditions_)) <> " " <> - orderF qi ordts <> " " <> - limitOffsetF range + else "WHERE " <> intercalateSnippet " AND " (map (pgFmtLogicTree qi) logicForest ++ map pgFmtJoinCondition joinConditions)) <> " " <> + orderF qi order <> " " <> + limitOffsetF readRange where - fromFrag = fromF rel mainQi tblAlias - qi = getQualifiedIdentifier rel mainQi tblAlias + fromFrag = fromF nodeRel mainQi fromAlias + qi = getQualifiedIdentifier nodeRel mainQi fromAlias (selects, joins) = foldr getSelectsJoins ([],[]) forest -getSelectsJoins :: ReadRequest -> ([SQL.Snippet], [SQL.Snippet]) -> ([SQL.Snippet], [SQL.Snippet]) -getSelectsJoins (Node (_, (_, Nothing, _, _, _, _)) _) _ = ([], []) -getSelectsJoins rr@(Node (_, (name, Just rel, alias, _, joinType, _)) _) (selects,joins) = +getSelectsJoins :: ReadPlanTree -> ([SQL.Snippet], [SQL.Snippet]) -> ([SQL.Snippet], [SQL.Snippet]) +getSelectsJoins (Node ReadPlan{nodeRel=Nothing} _) _ = ([], []) +getSelectsJoins rr@(Node ReadPlan{nodeName=name, nodeRel=Just rel, nodeAlias=alias, nodeJoinType=joinType} _) (selects,joins) = let - subquery = readRequestToQuery rr + subquery = readPlanToQuery rr aliasOrName = fromMaybe name alias locTblName = qiName (relTable rel) <> "_" <> aliasOrName localTableName = pgFmtIdent locTblName @@ -82,8 +83,8 @@ getSelectsJoins rr@(Node (_, (name, Just rel, alias, _, joinType, _)) _) (select in (sel:selects, joi:joins) -mutateRequestToQuery :: MutateRequest -> SQL.Snippet -mutateRequestToQuery (Insert mainQi iCols body onConflct putConditions returnings) = +mutatePlanToQuery :: MutatePlan -> SQL.Snippet +mutatePlanToQuery (Insert mainQi iCols body onConflct putConditions returnings) = "WITH " <> normalizedBody body <> " " <> "INSERT INTO " <> SQL.sql (fromQi mainQi) <> SQL.sql (if S.null iCols then " " else "(" <> cols <> ") ") <> "SELECT " <> SQL.sql cols <> " " <> @@ -109,7 +110,7 @@ mutateRequestToQuery (Insert mainQi iCols body onConflct putConditions returning cols = BS.intercalate ", " $ pgFmtIdent <$> S.toList iCols -- An update without a limit is always filtered with a WHERE -mutateRequestToQuery (Update mainQi uCols body logicForest range ordts returnings) +mutatePlanToQuery (Update mainQi uCols body logicForest range ordts returnings) | S.null uCols = -- if there are no columns we cannot do UPDATE table SET {empty}, it'd be invalid syntax -- selecting an empty resultset from mainQi gives us the column names to prevent errors when using &select= @@ -145,7 +146,7 @@ mutateRequestToQuery (Update mainQi uCols body logicForest range ordts returning rangeCols = BS.intercalate ", " ((\col -> pgFmtIdent col <> " = (SELECT " <> pgFmtIdent col <> " FROM pgrst_update_body) ") <$> S.toList uCols) (whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts) -mutateRequestToQuery (Delete mainQi logicForest range ordts returnings) +mutatePlanToQuery (Delete mainQi logicForest range ordts returnings) | range == allRange = "DELETE FROM " <> SQL.sql (fromQi mainQi) <> " " <> whereLogic <> " " <> @@ -168,8 +169,8 @@ mutateRequestToQuery (Delete mainQi logicForest range ordts returnings) whereLogic = if null logicForest then mempty else " WHERE " <> intercalateSnippet " AND " (pgFmtLogicTree mainQi <$> logicForest) (whereRangeIdF, rangeIdF) = mutRangeF mainQi (fst . otTerm <$> ordts) -requestToCallProcQuery :: CallRequest -> SQL.Snippet -requestToCallProcQuery (FunctionCall qi params args returnsScalar multipleCall returnings) = +callPlanToQuery :: CallPlan -> SQL.Snippet +callPlanToQuery (FunctionCall qi params args returnsScalar multipleCall returnings) = prmsCTE <> argsBody where (prmsCTE, argFrag) = case params of @@ -223,8 +224,8 @@ requestToCallProcQuery (FunctionCall qi params args returnsScalar multipleCall r -- For this case, we use a WHERE EXISTS instead of an INNER JOIN on the count query. -- See https://github.com/PostgREST/postgrest/issues/2009#issuecomment-977473031 -- Only for the nodes that have an INNER JOIN linked to the root level. -readRequestToCountQuery :: ReadRequest -> SQL.Snippet -readRequestToCountQuery (Node (Select{from=mainQi, fromAlias=tblAlias, where_=logicForest, joinConditions=joinConditions_}, (_, rel, _, _, _, _)) forest) = +readPlanToCountQuery :: ReadPlanTree -> SQL.Snippet +readPlanToCountQuery (Node ReadPlan{from=mainQi, fromAlias=tblAlias, where_=logicForest, joinConditions=joinConditions_, nodeRel=rel} forest) = "SELECT 1 " <> fromFrag <> (if null logicForest && null joinConditions_ && null subQueries then mempty @@ -238,16 +239,16 @@ readRequestToCountQuery (Node (Select{from=mainQi, fromAlias=tblAlias, where_=lo qi = getQualifiedIdentifier rel mainQi tblAlias fromFrag = fromF rel mainQi tblAlias subQueries = foldr existsSubquery [] forest - existsSubquery :: ReadRequest -> [SQL.Snippet] -> [SQL.Snippet] - existsSubquery readReq@(Node (_, (_, _, _, _, joinType, _)) _) rest = + existsSubquery :: ReadPlanTree -> [SQL.Snippet] -> [SQL.Snippet] + existsSubquery readReq@(Node ReadPlan{nodeJoinType=joinType} _) rest = if joinType == Just JTInner - then ("EXISTS (" <> readRequestToCountQuery readReq <> " )"):rest + then ("EXISTS (" <> readPlanToCountQuery readReq <> " )"):rest else rest limitedQuery :: SQL.Snippet -> Maybe Integer -> SQL.Snippet limitedQuery query maxRows = query <> SQL.sql (maybe mempty (\x -> " LIMIT " <> BS.pack (show x)) maxRows) --- TODO refactor so this function is uneeded and ComputedRelationship QualifiedIdentifier comes from the ReadQuery type +-- TODO refactor so this function is uneeded and ComputedRelationship QualifiedIdentifier comes from the ReadPlan type getQualifiedIdentifier :: Maybe Relationship -> QualifiedIdentifier -> Maybe Alias -> QualifiedIdentifier getQualifiedIdentifier rel mainQi tblAlias = case rel of Just ComputedRelationship{relFunction} -> QualifiedIdentifier mempty $ fromMaybe (qiName relFunction) tblAlias diff --git a/src/PostgREST/Request/Types.hs b/src/PostgREST/Request/Types.hs index a08cfaedaa..f0f179e607 100644 --- a/src/PostgREST/Request/Types.hs +++ b/src/PostgREST/Request/Types.hs @@ -9,9 +9,6 @@ module PostgREST.Request.Types , Field , Filter(..) , Hint - , CallQuery(..) - , CallParams(..) - , CallRequest , JoinCondition(..) , JoinType(..) , JsonOperand(..) @@ -35,12 +32,9 @@ module PostgREST.Request.Types , SelectItem ) where -import qualified Data.ByteString.Lazy as LBS - import PostgREST.DbStructure.Identifiers (FieldName, QualifiedIdentifier) -import PostgREST.DbStructure.Proc (ProcDescription (..), - ProcParam (..)) +import PostgREST.DbStructure.Proc (ProcDescription (..)) import PostgREST.DbStructure.Relationship (Relationship) import PostgREST.MediaType (MediaType (..)) @@ -75,8 +69,6 @@ data RangeError | LowerGTUpper | OutOfBounds Text Text -type CallRequest = CallQuery - type NodeName = Text type Depth = Integer @@ -103,19 +95,6 @@ data OrderNulls | OrderNullsLast deriving (Eq) -data CallQuery = FunctionCall - { funCQi :: QualifiedIdentifier - , funCParams :: CallParams - , funCArgs :: Maybe LBS.ByteString - , funCScalar :: Bool - , funCMultipleCall :: Bool - , funCReturning :: [FieldName] - } - -data CallParams - = KeyParams [ProcParam] -- ^ Call with key params: func(a := val1, b:= val2) - | OnePosParam ProcParam -- ^ Call with positional params(only one supported): func(val) - type Field = (FieldName, JsonPath) type Cast = Text type Alias = Text diff --git a/test/spec/QueryCost.hs b/test/spec/QueryCost.hs index 9c3dcd942a..0a5faf8c5b 100644 --- a/test/spec/QueryCost.hs +++ b/test/spec/QueryCost.hs @@ -14,8 +14,8 @@ import Text.Heredoc import Protolude hiding (get, toS) -import PostgREST.Query.QueryBuilder (requestToCallProcQuery) -import PostgREST.Request.Types +import PostgREST.Plan.CallPlan +import PostgREST.Query.QueryBuilder (callPlanToQuery) import PostgREST.DbStructure.Identifiers import PostgREST.DbStructure.Proc @@ -30,7 +30,7 @@ main = do context "call proc query" $ do it "should not exceed cost when calling setof composite proc" $ do cost <- exec pool $ - requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below") + callPlanToQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below") (KeyParams [ProcParam "id" "int" True False]) (Just [str| {"id": 3} |]) False False []) liftIO $ @@ -38,13 +38,13 @@ main = do it "should not exceed cost when calling setof composite proc with empty params" $ do cost <- exec pool $ - requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "getallprojects") (KeyParams []) Nothing False False []) + callPlanToQuery (FunctionCall (QualifiedIdentifier "test" "getallprojects") (KeyParams []) Nothing False False []) liftIO $ cost `shouldSatisfy` (< Just 30) it "should not exceed cost when calling scalar proc" $ do cost <- exec pool $ - requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "add_them") + callPlanToQuery (FunctionCall (QualifiedIdentifier "test" "add_them") (KeyParams [ProcParam "a" "int" True False, ProcParam "b" "int" True False]) (Just [str| {"a": 3, "b": 4} |]) True False []) liftIO $ @@ -53,7 +53,7 @@ main = do context "params=multiple-objects" $ do it "should not exceed cost when calling setof composite proc" $ do cost <- exec pool $ - requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below") + callPlanToQuery (FunctionCall (QualifiedIdentifier "test" "get_projects_below") (KeyParams [ProcParam "id" "int" True False]) (Just [str| [{"id": 1}, {"id": 4}] |]) False True []) liftIO $ do @@ -63,7 +63,7 @@ main = do it "should not exceed cost when calling scalar proc" $ do cost <- exec pool $ - requestToCallProcQuery (FunctionCall (QualifiedIdentifier "test" "add_them") + callPlanToQuery (FunctionCall (QualifiedIdentifier "test" "add_them") (KeyParams [ProcParam "a" "int" True False, ProcParam "b" "int" True False]) (Just [str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |]) True False []) liftIO $