From 968bf9ce590501d5b3952360065033bf41dd90a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steve=20Ch=C3=A1vez?= Date: Wed, 19 Jul 2017 18:01:37 -0500 Subject: [PATCH] Put traditional Filters inside [LogicTree] (#911) --- src/PostgREST/DbRequestBuilder.hs | 46 +++++++++++++++++-------------- src/PostgREST/Parsers.hs | 4 +-- src/PostgREST/QueryBuilder.hs | 43 +++++++++++++---------------- src/PostgREST/Types.hs | 6 ++-- 4 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/PostgREST/DbRequestBuilder.hs b/src/PostgREST/DbRequestBuilder.hs index 8c484cc54..d5f15e672 100644 --- a/src/PostgREST/DbRequestBuilder.hs +++ b/src/PostgREST/DbRequestBuilder.hs @@ -30,7 +30,7 @@ import PostgREST.ApiRequest ( ApiRequest(..) import PostgREST.Error (apiRequestError) import PostgREST.Parsers import PostgREST.RangeQuery (NonnegRange, restrictRange) -import PostgREST.QueryBuilder (getJoinConditions, sourceCTEName) +import PostgREST.QueryBuilder (getJoinFilters, sourceCTEName) import PostgREST.Types import Protolude hiding (from, dropWhile, drop) @@ -88,7 +88,7 @@ treeRestrictRange maxRows_ request = pure $ nodeRestrictRange maxRows_ `fmap` re augumentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest augumentRequestWithJoin schema allRels request = addRelations schema allRels Nothing request - >>= addJoinConditions schema + >>= addJoinFilters schema addRelations :: Schema -> [Relation] -> Maybe ReadRequest -> ReadRequest -> Either ApiRequestError ReadRequest addRelations schema allRelations parentNode (Node readNode@(query, (name, _, alias)) forest) = @@ -155,21 +155,20 @@ addRelations schema allRelations parentNode (Node readNode@(query, (name, _, ali updateForest :: Maybe ReadRequest -> Either ApiRequestError [ReadRequest] updateForest n = mapM (addRelations schema allRelations n) forest -addJoinConditions :: Schema -> ReadRequest -> Either ApiRequestError ReadRequest -addJoinConditions schema (Node nn@(query, (n, r, a)) forest) = - case r of - Just Relation{relType=Root} -> Node nn <$> updatedForest -- this is the root node - Just rel@Relation{relType=Child} -> Node (addCond query (getJoinConditions rel),(n,r,a)) <$> updatedForest - Just Relation{relType=Parent} -> Node nn <$> updatedForest +addJoinFilters :: Schema -> ReadRequest -> Either ApiRequestError ReadRequest +addJoinFilters schema (Node node@(query, nodeProps@(_, relation, _)) forest) = + case relation of + Just Relation{relType=Root} -> Node node <$> updatedForest -- this is the root node + Just Relation{relType=Parent} -> Node node <$> updatedForest + Just rel@Relation{relType=Child} -> Node (augmentQuery rel, nodeProps) <$> updatedForest Just rel@Relation{relType=Many, relLTable=(Just linkTable)} -> - Node (qq, (n, r, a)) <$> updatedForest - where - query' = addCond query (getJoinConditions rel) - qq = query'{from=tableName linkTable : from query'} + let rq = augmentQuery rel in + Node (rq{from=tableName linkTable:from rq}, nodeProps) <$> updatedForest _ -> Left UnknownRelation where - updatedForest = mapM (addJoinConditions schema) forest - addCond query' con = query'{flt_=con ++ flt_ query'} + updatedForest = mapM (addJoinFilters schema) forest + augmentQuery rel = foldr addFilterToReadQuery query (getJoinFilters rel) + addFilterToReadQuery flt rq@Select{where_=lf} = rq{where_=addFilterToLogicForest flt lf}::ReadQuery addFiltersOrdersRanges :: ApiRequest -> Either ApiRequestError (ReadRequest -> ReadRequest) addFiltersOrdersRanges apiRequest = foldr1 (liftA2 (.)) [ @@ -198,7 +197,7 @@ addFiltersOrdersRanges apiRequest = foldr1 (liftA2 (.)) [ ranges = mapM pRequestRange $ M.toList $ iRange apiRequest addFilterToNode :: Filter -> ReadRequest -> ReadRequest -addFilterToNode flt (Node (q@Select {flt_=flts}, i) f) = Node (q {flt_=flt:flts}, i) f +addFilterToNode flt (Node (q@Select {where_=lf}, i) f) = Node (q{where_=addFilterToLogicForest flt lf}::ReadQuery, i) f addFilter :: (EmbedPath, Filter) -> ReadRequest -> ReadRequest addFilter = addProperty addFilterToNode @@ -216,7 +215,7 @@ addRange :: (EmbedPath, NonnegRange) -> ReadRequest -> ReadRequest addRange = addProperty addRangeToNode addLogicTreeToNode :: LogicTree -> ReadRequest -> ReadRequest -addLogicTreeToNode t (Node (q@Select{logic=l},i) f) = Node (q{logic=t:l}::ReadQuery, i) f +addLogicTreeToNode t (Node (q@Select{where_=lf},i) f) = Node (q{where_=t:lf}::ReadQuery, i) f addLogicTree :: (EmbedPath, LogicTree) -> ReadRequest -> ReadRequest addLogicTree = addProperty addLogicTreeToNode @@ -258,8 +257,8 @@ mutateRequest :: ApiRequest -> [FieldName] -> Either Response MutateRequest mutateRequest apiRequest fldNames = mapLeft apiRequestError $ case action of ActionCreate -> Right $ Insert rootTableName payload returnings - ActionUpdate -> Update rootTableName <$> pure payload <*> filters <*> logic_ <*> pure returnings - ActionDelete -> Delete rootTableName <$> filters <*> logic_ <*> pure returnings + ActionUpdate -> Update rootTableName <$> pure payload <*> combinedLogic <*> pure returnings + ActionDelete -> Delete rootTableName <$> combinedLogic <*> pure returnings _ -> Left UnsupportedVerb where action = iAction apiRequest @@ -271,10 +270,10 @@ mutateRequest apiRequest fldNames = mapLeft apiRequestError $ _ -> undefined returnings = if iPreferRepresentation apiRequest == None then [] else fldNames filters = map snd <$> mapM pRequestFilter mutateFilters - logic_ = map snd <$> mapM pRequestLogicTree logicFilters + logic = map snd <$> mapM pRequestLogicTree logicFilters + combinedLogic = foldr addFilterToLogicForest <$> logic <*> filters -- update/delete filters can be only on the root table - mutateFilters = onlyRoot $ iFilters apiRequest - logicFilters = onlyRoot $ iLogic apiRequest + (mutateFilters, logicFilters) = join (***) onlyRoot (iFilters apiRequest, iLogic apiRequest) onlyRoot = filter (not . ( "." `isInfixOf` ) . fst) fieldNames :: ReadRequest -> [FieldName] @@ -284,3 +283,8 @@ fieldNames (Node (sel, _) forest) = fks = concatMap (fromMaybe [] . f) forest f (Node (_, (_, Just Relation{relFColumns=cols, relType=Parent}, _)) _) = Just cols f _ = Nothing + +-- Traditional filters(e.g. id=eq.1) are added as root nodes of the LogicTree +-- they are later concatenated with AND in the QueryBuilder +addFilterToLogicForest :: Filter -> [LogicTree] -> [LogicTree] +addFilterToLogicForest flt lf = Stmnt flt : lf diff --git a/src/PostgREST/Parsers.hs b/src/PostgREST/Parsers.hs index 9f59a3f70..bf505baee 100644 --- a/src/PostgREST/Parsers.hs +++ b/src/PostgREST/Parsers.hs @@ -58,7 +58,7 @@ pReadRequest rootNodeName = do fieldTree <- pFieldForest return $ foldr treeEntry (Node (readQuery, (rootNodeName, Nothing, Nothing)) []) fieldTree where - readQuery = Select [] [rootNodeName] [] [] Nothing allRange + readQuery = Select [] [rootNodeName] [] Nothing allRange treeEntry :: Tree SelectItem -> ReadRequest -> ReadRequest treeEntry (Node fld@((fn, _),_,alias) fldForest) (Node (q, i) rForest) = case fldForest of @@ -66,7 +66,7 @@ pReadRequest rootNodeName = do _ -> Node (q, i) newForest where newForest = - foldr treeEntry (Node (Select [] [fn] [] [] Nothing allRange, (fn, Nothing, alias)) []) fldForest:rForest + foldr treeEntry (Node (Select [] [fn] [] Nothing allRange, (fn, Nothing, alias)) []) fldForest:rForest pTreePath :: Parser (EmbedPath, Field) pTreePath = do diff --git a/src/PostgREST/QueryBuilder.hs b/src/PostgREST/QueryBuilder.hs index e35775990..628c94b97 100644 --- a/src/PostgREST/QueryBuilder.hs +++ b/src/PostgREST/QueryBuilder.hs @@ -15,7 +15,7 @@ module PostgREST.QueryBuilder ( callProc , createReadStatement , createWriteStatement - , getJoinConditions + , getJoinFilters , pgFmtIdent , pgFmtLit , requestToQuery @@ -199,25 +199,23 @@ pgFmtLit x = requestToCountQuery :: Schema -> DbRequest -> SqlQuery requestToCountQuery _ (DbMutate _) = undefined -requestToCountQuery schema (DbRead (Node (Select _ _ conditions logic_ _ _, (mainTbl, _, _)) _)) = +requestToCountQuery schema (DbRead (Node (Select _ _ logicForest _ _, (mainTbl, _, _)) _)) = unwords [ "SELECT pg_catalog.count(*)", "FROM ", fromQi qi, - -- logic_ doesn't not need localFilter filtering because it doesn't have VForeignKey vals - ("WHERE " <> intercalate " AND " (map (pgFmtFilter qi) localConditions ++ map (pgFmtLogicTree qi) logic_)) - `emptyOnFalse` (null conditions && null logic_) + ("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) filteredLogic)) `emptyOnFalse` null filteredLogic ] where qi = removeSourceCTESchema schema mainTbl - localFilter :: Filter -> Bool - localFilter Filter{operation=Operation{expr=(_, val)}} = case val of - VText _ -> True - VTextL _ -> True - VForeignKey _ _ -> False - localConditions = filter localFilter conditions + -- all foreing key filters are root nodes(see addFilterToLogicForest), only those are filtered + nonFKRoot :: LogicTree -> Bool + nonFKRoot (Stmnt (Filter _ Operation{expr=(_, VForeignKey _ _)})) = False + nonFKRoot (Stmnt _) = True + nonFKRoot Expr{} = True + filteredLogic = filter nonFKRoot logicForest requestToQuery :: Schema -> Bool -> DbRequest -> SqlQuery -requestToQuery schema isParent (DbRead (Node (Select colSelects tbls conditions logic_ ord range, (nodeName, maybeRelation, _)) forest)) = +requestToQuery schema isParent (DbRead (Node (Select colSelects tbls logicForest ord range, (nodeName, maybeRelation, _)) forest)) = query where mainTbl = fromMaybe nodeName (tableName . relTable <$> maybeRelation) @@ -227,8 +225,7 @@ requestToQuery schema isParent (DbRead (Node (Select colSelects tbls conditions "SELECT ", intercalate ", " (map (pgFmtSelectItem qi) colSelects ++ selects), "FROM ", intercalate ", " (map (fromQi . toQi) tbls), unwords joins, - ("WHERE " <> intercalate " AND " (map (pgFmtFilter qi) conditions ++ map (pgFmtLogicTree qi) logic_)) - `emptyOnFalse` (null conditions && null logic_), + ("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) logicForest)) `emptyOnFalse` null logicForest, orderF (fromMaybe [] ord), if isParent then "" else limitF range ] @@ -261,7 +258,7 @@ requestToQuery schema isParent (DbRead (Node (Select colSelects tbls conditions replaceTableName _ x = x sel = "row_to_json(" <> pgFmtIdent local_table_name <> ".*) AS " <> pgFmtIdent node_name joi = " LEFT OUTER JOIN ( " <> subquery <> " ) AS " <> pgFmtIdent local_table_name <> - " ON " <> intercalate " AND " ( map (pgFmtFilter qi . replaceTableName local_table_name) (getJoinConditions r) ) + " ON " <> intercalate " AND " ( map (pgFmtFilter qi . replaceTableName local_table_name) (getJoinFilters r) ) where subquery = requestToQuery schema True (DbRead (Node n forst)) getQueryParts (Node n@(_, (name, Just Relation{relType=Many,relTable=Table{tableName=table}}, alias)) forst) (j,s) = (j,sel:s) where @@ -289,7 +286,7 @@ requestToQuery schema _ (DbMutate (Insert mainTbl (PayloadJSON rows) returnings) ret = if null returnings then "" else unwords [" RETURNING ", intercalate ", " (map (pgFmtColumn qi) returnings)] -requestToQuery schema _ (DbMutate (Update mainTbl (PayloadJSON rows) conditions logic_ returnings)) = +requestToQuery schema _ (DbMutate (Update mainTbl (PayloadJSON rows) logicForest returnings)) = case rows V.!? 0 of Just obj -> let assignments = map @@ -297,21 +294,19 @@ requestToQuery schema _ (DbMutate (Update mainTbl (PayloadJSON rows) conditions unwords [ "UPDATE ", fromQi qi, " SET " <> intercalate "," assignments <> " ", - ("WHERE " <> intercalate " AND " (map (pgFmtFilter qi) conditions ++ map (pgFmtLogicTree qi) logic_)) - `emptyOnFalse` (null conditions && null logic_), + ("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) logicForest)) `emptyOnFalse` null logicForest, ("RETURNING " <> intercalate ", " (map (pgFmtColumn qi) returnings)) `emptyOnFalse` null returnings ] Nothing -> undefined where qi = QualifiedIdentifier schema mainTbl -requestToQuery schema _ (DbMutate (Delete mainTbl conditions logic_ returnings)) = +requestToQuery schema _ (DbMutate (Delete mainTbl logicForest returnings)) = query where qi = QualifiedIdentifier schema mainTbl query = unwords [ "DELETE FROM ", fromQi qi, - ("WHERE " <> intercalate " AND " (map (pgFmtFilter qi) conditions ++ map (pgFmtLogicTree qi) logic_)) - `emptyOnFalse` (null conditions && null logic_), + ("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) logicForest)) `emptyOnFalse` null logicForest, ("RETURNING " <> intercalate ", " (map (pgFmtColumn qi) returnings)) `emptyOnFalse` null returnings ] @@ -378,13 +373,13 @@ fromQi t = (if s == "" then "" else pgFmtIdent s <> ".") <> pgFmtIdent n n = qiName t s = qiSchema t -getJoinConditions :: Relation -> [Filter] -getJoinConditions (Relation t cols ft fcs typ lt lc1 lc2) = +getJoinFilters :: Relation -> [Filter] +getJoinFilters (Relation t cols ft fcs typ lt lc1 lc2) = case typ of Child -> zipWith (toFilter tN ftN) cols fcs Parent -> zipWith (toFilter tN ftN) cols fcs Many -> zipWith (toFilter tN ltN) cols (fromMaybe [] lc1) ++ zipWith (toFilter ftN ltN) fcs (fromMaybe [] lc2) - Root -> undefined --error "undefined getJoinConditions" + Root -> undefined --error "undefined getJoinFilters" where s = if typ == Parent then "" else tableSchema t tN = tableName t diff --git a/src/PostgREST/Types.hs b/src/PostgREST/Types.hs index 3c20cb549..245461d1d 100644 --- a/src/PostgREST/Types.hs +++ b/src/PostgREST/Types.hs @@ -184,10 +184,10 @@ type SelectItem = (Field, Maybe Cast, Maybe Alias) type EmbedPath = [Text] data Filter = Filter { field::Field, operation::Operation } deriving (Show, Eq) -data ReadQuery = Select { select::[SelectItem], from::[TableName], flt_::[Filter], logic::[LogicTree], order::Maybe [OrderTerm], range_::NonnegRange } deriving (Show, Eq) +data ReadQuery = Select { select::[SelectItem], from::[TableName], where_::[LogicTree], order::Maybe [OrderTerm], range_::NonnegRange } deriving (Show, Eq) data MutateQuery = Insert { in_::TableName, qPayload::PayloadJSON, returning::[FieldName] } - | Delete { in_::TableName, where_::[Filter], logic::[LogicTree], returning::[FieldName] } - | Update { in_::TableName, qPayload::PayloadJSON, where_::[Filter], logic::[LogicTree], returning::[FieldName] } deriving (Show, Eq) + | Delete { in_::TableName, where_::[LogicTree], returning::[FieldName] } + | Update { in_::TableName, qPayload::PayloadJSON, where_::[LogicTree], returning::[FieldName] } deriving (Show, Eq) type ReadNode = (ReadQuery, (NodeName, Maybe Relation, Maybe Alias)) type ReadRequest = Tree ReadNode type MutateRequest = MutateQuery