Refactor QueryBuilder and DbRequestBuilder
- Refactor ORDER BY and LIMIT query fragments - Move ReadRequest building to DbRequestBuilder - Clarify Relation link table attributes - Change Join Operation to JoinCond
This commit is contained in:
committed by
Steve Chávez
parent
108f3cd651
commit
8e2a0e05ea
@@ -31,7 +31,7 @@ import PostgREST.ApiRequest ( ApiRequest(..)
|
|||||||
)
|
)
|
||||||
import PostgREST.Error (apiRequestError)
|
import PostgREST.Error (apiRequestError)
|
||||||
import PostgREST.Parsers
|
import PostgREST.Parsers
|
||||||
import PostgREST.RangeQuery (NonnegRange, restrictRange)
|
import PostgREST.RangeQuery (NonnegRange, restrictRange, allRange)
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
|
|
||||||
import Protolude hiding (from, dropWhile, drop)
|
import Protolude hiding (from, dropWhile, drop)
|
||||||
@@ -43,8 +43,10 @@ readRequest maxRows allRels proc apiRequest =
|
|||||||
mapLeft apiRequestError $
|
mapLeft apiRequestError $
|
||||||
treeRestrictRange maxRows =<<
|
treeRestrictRange maxRows =<<
|
||||||
augumentRequestWithJoin schema relations =<<
|
augumentRequestWithJoin schema relations =<<
|
||||||
parseReadRequest
|
addFiltersOrdersRanges apiRequest <*>
|
||||||
|
(buildReadRequest <$> pRequestSelect (iSelect apiRequest))
|
||||||
where
|
where
|
||||||
|
action = iAction apiRequest
|
||||||
(schema, rootTableName) = fromJust $ -- Make it safe
|
(schema, rootTableName) = fromJust $ -- Make it safe
|
||||||
let target = iTarget apiRequest in
|
let target = iTarget apiRequest in
|
||||||
case target of
|
case target of
|
||||||
@@ -58,17 +60,17 @@ readRequest maxRows allRels proc apiRequest =
|
|||||||
|
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
action :: Action
|
buildReadRequest :: [Tree SelectItem] -> ReadRequest
|
||||||
action = iAction apiRequest
|
buildReadRequest fieldTree =
|
||||||
|
let rootNodeName = if action == ActionRead then rootTableName else sourceCTEName in
|
||||||
parseReadRequest :: Either ApiRequestError ReadRequest
|
foldr treeEntry (Node (Select [] [rootNodeName] [] [] [] allRange, (rootNodeName, Nothing, Nothing, Nothing)) []) fieldTree
|
||||||
parseReadRequest = addFiltersOrdersRanges apiRequest <*>
|
|
||||||
pRequestSelect rootName selStr
|
|
||||||
where
|
where
|
||||||
selStr = iSelect apiRequest
|
treeEntry :: Tree SelectItem -> ReadRequest -> ReadRequest
|
||||||
rootName = if action == ActionRead
|
treeEntry (Node fld@((fn, _),_,alias,relationDetail) fldForest) (Node (q, i) rForest) =
|
||||||
then rootTableName
|
case fldForest of
|
||||||
else sourceCTEName
|
[] -> Node (q {select=fld:select q}, i) rForest
|
||||||
|
_ -> Node (q, i) $
|
||||||
|
foldr treeEntry (Node (Select [] [fn] [] [] [] allRange, (fn, Nothing, alias, relationDetail)) []) fldForest:rForest
|
||||||
|
|
||||||
relations :: [Relation]
|
relations :: [Relation]
|
||||||
relations = case action of
|
relations = case action of
|
||||||
@@ -77,7 +79,19 @@ readRequest maxRows allRels proc apiRequest =
|
|||||||
ActionDelete -> fakeSourceRelations ++ allRels
|
ActionDelete -> fakeSourceRelations ++ allRels
|
||||||
ActionInvoke _ -> fakeSourceRelations ++ allRels
|
ActionInvoke _ -> fakeSourceRelations ++ allRels
|
||||||
_ -> allRels
|
_ -> allRels
|
||||||
where fakeSourceRelations = mapMaybe (toSourceRelation rootTableName) allRels -- see comment in toSourceRelation
|
where fakeSourceRelations = mapMaybe (toSourceRelation rootTableName) allRels
|
||||||
|
|
||||||
|
-- in a relation where one of the tables matches "TableName"
|
||||||
|
-- replace the name to that table with pg_source
|
||||||
|
-- this "fake" relations is needed so that in a mutate query
|
||||||
|
-- we can look a the "returning *" part which is wrapped with a "with"
|
||||||
|
-- as just another table that has relations with other tables
|
||||||
|
toSourceRelation :: TableName -> Relation -> Maybe Relation
|
||||||
|
toSourceRelation mt r@(Relation t _ ft _ _ rt _ _)
|
||||||
|
| mt == tableName t = Just $ r {relTable=t {tableName=sourceCTEName}}
|
||||||
|
| mt == tableName ft = Just $ r {relFTable=t {tableName=sourceCTEName}}
|
||||||
|
| Just mt == (tableName <$> rt) = Just $ r {relLinkTable=(\tbl -> tbl {tableName=sourceCTEName}) <$> rt}
|
||||||
|
| otherwise = Nothing
|
||||||
|
|
||||||
treeRestrictRange :: Maybe Integer -> ReadRequest -> Either ApiRequestError ReadRequest
|
treeRestrictRange :: Maybe Integer -> ReadRequest -> Either ApiRequestError ReadRequest
|
||||||
treeRestrictRange maxRows_ request = pure $ nodeRestrictRange maxRows_ `fmap` request
|
treeRestrictRange maxRows_ request = pure $ nodeRestrictRange maxRows_ `fmap` request
|
||||||
@@ -88,7 +102,7 @@ treeRestrictRange maxRows_ request = pure $ nodeRestrictRange maxRows_ `fmap` re
|
|||||||
augumentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest
|
augumentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest
|
||||||
augumentRequestWithJoin schema allRels request =
|
augumentRequestWithJoin schema allRels request =
|
||||||
addRelations schema allRels Nothing request
|
addRelations schema allRels Nothing request
|
||||||
>>= addJoinFilters schema
|
>>= addJoinConditions schema
|
||||||
|
|
||||||
addRelations :: Schema -> [Relation] -> Maybe ReadRequest -> ReadRequest -> Either ApiRequestError ReadRequest
|
addRelations :: Schema -> [Relation] -> Maybe ReadRequest -> ReadRequest -> Either ApiRequestError ReadRequest
|
||||||
addRelations schema allRelations parentNode (Node readNode@(query, (name, _, alias, relationDetail)) forest) =
|
addRelations schema allRelations parentNode (Node readNode@(query, (name, _, alias, relationDetail)) forest) =
|
||||||
@@ -171,7 +185,7 @@ addRelations schema allRelations parentNode (Node readNode@(query, (name, _, ali
|
|||||||
relType r == Many &&
|
relType r == Many &&
|
||||||
nodeTableName == tableName (relTable r) && -- match relation table name
|
nodeTableName == tableName (relTable r) && -- match relation table name
|
||||||
parentNodeTableName == tableName (relFTable r) && -- match relation foreign table name
|
parentNodeTableName == tableName (relFTable r) && -- match relation foreign table name
|
||||||
rd == tableName (fromJust (relLTable r))
|
rd == tableName (fromJust (relLinkTable r))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
) allRelations
|
) allRelations
|
||||||
@@ -189,35 +203,35 @@ addRelations schema allRelations parentNode (Node readNode@(query, (name, _, ali
|
|||||||
updateForest :: Maybe ReadRequest -> Either ApiRequestError [ReadRequest]
|
updateForest :: Maybe ReadRequest -> Either ApiRequestError [ReadRequest]
|
||||||
updateForest n = mapM (addRelations schema allRelations n) forest
|
updateForest n = mapM (addRelations schema allRelations n) forest
|
||||||
|
|
||||||
addJoinFilters :: Schema -> ReadRequest -> Either ApiRequestError ReadRequest
|
addJoinConditions :: Schema -> ReadRequest -> Either ApiRequestError ReadRequest
|
||||||
addJoinFilters schema (Node node@(query, nodeProps@(_, relation, _, _)) forest) =
|
addJoinConditions schema (Node node@(query, nodeProps@(_, relation, _, _)) forest) =
|
||||||
case relation of
|
case relation of
|
||||||
Just Relation{relType=Root} -> Node node <$> updatedForest -- this is the root node
|
Just Relation{relType=Root} -> Node node <$> updatedForest -- this is the root node
|
||||||
Just rel@Relation{relType=Parent} -> Node (augmentQuery rel, nodeProps) <$> updatedForest
|
Just rel@Relation{relType=Parent} -> Node (augmentQuery rel, nodeProps) <$> updatedForest
|
||||||
Just rel@Relation{relType=Child} -> Node (augmentQuery rel, nodeProps) <$> updatedForest
|
Just rel@Relation{relType=Child} -> Node (augmentQuery rel, nodeProps) <$> updatedForest
|
||||||
Just rel@Relation{relType=Many, relLTable=(Just linkTable)} ->
|
Just rel@Relation{relType=Many, relLinkTable=(Just linkTable)} ->
|
||||||
let rq = augmentQuery rel in
|
let rq = augmentQuery rel in
|
||||||
Node (rq{from=tableName linkTable:from rq}, nodeProps) <$> updatedForest
|
Node (rq{from=tableName linkTable:from rq}, nodeProps) <$> updatedForest
|
||||||
_ -> Left UnknownRelation
|
_ -> Left UnknownRelation
|
||||||
where
|
where
|
||||||
updatedForest = mapM (addJoinFilters schema) forest
|
updatedForest = mapM (addJoinConditions schema) forest
|
||||||
augmentQuery rel = foldr addFilterToReadQuery query (getJoinFilters rel)
|
augmentQuery rel = foldr addJoinCondToReadQuery query (getJoinConds rel)
|
||||||
addFilterToReadQuery flt rq@Select{where_=lf} = rq{where_=addFilterToLogicForest flt lf}::ReadQuery
|
addJoinCondToReadQuery jc rq@Select{joinConds=jcs} = rq{joinConds=jc:jcs}::ReadQuery
|
||||||
|
|
||||||
getJoinFilters :: Relation -> [Filter]
|
getJoinConds :: Relation -> [JoinCond]
|
||||||
getJoinFilters (Relation t cols ft fcs typ lt lc1 lc2) =
|
getJoinConds (Relation t cols ft fcs typ lt lc1 lc2) =
|
||||||
case typ of
|
case typ of
|
||||||
Child -> zipWith (toFilter tN ftN) cols fcs
|
Child -> zipWith (toJoinCond tN ftN) cols fcs
|
||||||
Parent -> zipWith (toFilter tN ftN) cols fcs
|
Parent -> zipWith (toJoinCond tN ftN) cols fcs
|
||||||
Many -> zipWith (toFilter tN ltN) cols (fromMaybe [] lc1) ++ zipWith (toFilter ftN ltN) fcs (fromMaybe [] lc2)
|
Many -> zipWith (toJoinCond tN ltN) cols (fromMaybe [] lc1) ++ zipWith (toJoinCond ftN ltN) fcs (fromMaybe [] lc2)
|
||||||
Root -> undefined --error "undefined getJoinFilters"
|
Root -> undefined
|
||||||
where
|
where
|
||||||
s = if typ == Parent then "" else tableSchema t
|
s = if typ == Parent then "" else tableSchema t
|
||||||
tN = tableName t
|
tN = tableName t
|
||||||
ftN = tableName ft
|
ftN = tableName ft
|
||||||
ltN = fromMaybe "" (tableName <$> lt)
|
ltN = fromMaybe "" (tableName <$> lt)
|
||||||
toFilter :: Text -> Text -> Column -> Column -> Filter
|
toJoinCond :: Text -> Text -> Column -> Column -> JoinCond
|
||||||
toFilter tb ftb c fc = Filter (colName c, Nothing) (OpExpr False (Join (QualifiedIdentifier s tb) (ForeignKey fc{colTable=(colTable fc){tableName=ftb}})))
|
toJoinCond tb ftb c fc = JoinCond (QualifiedIdentifier s tb, colName c) (QualifiedIdentifier s ftb, colName fc)
|
||||||
|
|
||||||
addFiltersOrdersRanges :: ApiRequest -> Either ApiRequestError (ReadRequest -> ReadRequest)
|
addFiltersOrdersRanges :: ApiRequest -> Either ApiRequestError (ReadRequest -> ReadRequest)
|
||||||
addFiltersOrdersRanges apiRequest = foldr1 (liftA2 (.)) [
|
addFiltersOrdersRanges apiRequest = foldr1 (liftA2 (.)) [
|
||||||
@@ -254,7 +268,7 @@ addFilter :: (EmbedPath, Filter) -> ReadRequest -> ReadRequest
|
|||||||
addFilter = addProperty addFilterToNode
|
addFilter = addProperty addFilterToNode
|
||||||
|
|
||||||
addOrderToNode :: [OrderTerm] -> ReadRequest -> ReadRequest
|
addOrderToNode :: [OrderTerm] -> ReadRequest -> ReadRequest
|
||||||
addOrderToNode o (Node (q,i) f) = Node (q{order=Just o}, i) f
|
addOrderToNode o (Node (q,i) f) = Node (q{order=o}, i) f
|
||||||
|
|
||||||
addOrder :: (EmbedPath, [OrderTerm]) -> ReadRequest -> ReadRequest
|
addOrder :: (EmbedPath, [OrderTerm]) -> ReadRequest -> ReadRequest
|
||||||
addOrder = addProperty addOrderToNode
|
addOrder = addProperty addOrderToNode
|
||||||
@@ -292,18 +306,6 @@ addProperty f (path, a) (Node rn forest) =
|
|||||||
fnd :: ReadRequest -> Bool
|
fnd :: ReadRequest -> Bool
|
||||||
fnd (Node (_,(n,_,_,_)) _) = n == name
|
fnd (Node (_,(n,_,_,_)) _) = n == name
|
||||||
|
|
||||||
-- in a relation where one of the tables mathces "TableName"
|
|
||||||
-- replace the name to that table with pg_source
|
|
||||||
-- this "fake" relations is needed so that in a mutate query
|
|
||||||
-- we can look a the "returning *" part which is wrapped with a "with"
|
|
||||||
-- as just another table that has relations with other tables
|
|
||||||
toSourceRelation :: TableName -> Relation -> Maybe Relation
|
|
||||||
toSourceRelation mt r@(Relation t _ ft _ _ rt _ _)
|
|
||||||
| mt == tableName t = Just $ r {relTable=t {tableName=sourceCTEName}}
|
|
||||||
| mt == tableName ft = Just $ r {relFTable=t {tableName=sourceCTEName}}
|
|
||||||
| Just mt == (tableName <$> rt) = Just $ r {relLTable=(\tbl -> tbl {tableName=sourceCTEName}) <$> rt}
|
|
||||||
| otherwise = Nothing
|
|
||||||
|
|
||||||
mutateRequest :: ApiRequest -> TableName -> [Text] -> [FieldName] -> Either Response MutateRequest
|
mutateRequest :: ApiRequest -> TableName -> [Text] -> [FieldName] -> Either Response MutateRequest
|
||||||
mutateRequest apiRequest tName pkCols fldNames = mapLeft apiRequestError $
|
mutateRequest apiRequest tName pkCols fldNames = mapLeft apiRequestError $
|
||||||
case action of
|
case action of
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import Data.Text (intercalate, replace, strip)
|
|||||||
import Data.List (init, last)
|
import Data.List (init, last)
|
||||||
import Data.Tree
|
import Data.Tree
|
||||||
import Data.Either.Combinators (mapLeft)
|
import Data.Either.Combinators (mapLeft)
|
||||||
import PostgREST.RangeQuery (NonnegRange,allRange)
|
import PostgREST.RangeQuery (NonnegRange)
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import Text.ParserCombinators.Parsec hiding (many, (<|>))
|
import Text.ParserCombinators.Parsec hiding (many, (<|>))
|
||||||
import Text.Parsec.Error
|
import Text.Parsec.Error
|
||||||
|
|
||||||
pRequestSelect :: Text -> Text -> Either ApiRequestError ReadRequest
|
pRequestSelect :: Text -> Either ApiRequestError [Tree SelectItem]
|
||||||
pRequestSelect rootName selStr =
|
pRequestSelect selStr =
|
||||||
mapError $ parse (pReadRequest rootName) ("failed to parse select parameter (" <> toS selStr <> ")") (toS selStr)
|
mapError $ parse pFieldForest ("failed to parse select parameter (" <> toS selStr <> ")") (toS selStr)
|
||||||
|
|
||||||
pRequestFilter :: (Text, Text) -> Either ApiRequestError (EmbedPath, Filter)
|
pRequestFilter :: (Text, Text) -> Either ApiRequestError (EmbedPath, Filter)
|
||||||
pRequestFilter (k, v) = mapError $ (,) <$> path <*> (Filter <$> fld <*> oper)
|
pRequestFilter (k, v) = mapError $ (,) <$> path <*> (Filter <$> fld <*> oper)
|
||||||
@@ -53,21 +53,6 @@ ws = toS <$> many (oneOf " \t")
|
|||||||
lexeme :: Parser a -> Parser a
|
lexeme :: Parser a -> Parser a
|
||||||
lexeme p = ws *> p <* ws
|
lexeme p = ws *> p <* ws
|
||||||
|
|
||||||
pReadRequest :: Text -> Parser ReadRequest
|
|
||||||
pReadRequest rootNodeName = do
|
|
||||||
fieldTree <- pFieldForest
|
|
||||||
return $ foldr treeEntry (Node (readQuery, (rootNodeName, Nothing, Nothing, Nothing)) []) fieldTree
|
|
||||||
where
|
|
||||||
readQuery = Select [] [rootNodeName] [] Nothing allRange
|
|
||||||
treeEntry :: Tree SelectItem -> ReadRequest -> ReadRequest
|
|
||||||
treeEntry (Node fld@((fn, _),_,alias,relationDetail) fldForest) (Node (q, i) rForest) =
|
|
||||||
case fldForest of
|
|
||||||
[] -> Node (q {select=fld:select q}, i) rForest
|
|
||||||
_ -> Node (q, i) newForest
|
|
||||||
where
|
|
||||||
newForest =
|
|
||||||
foldr treeEntry (Node (Select [] [fn] [] Nothing allRange, (fn, Nothing, alias, relationDetail)) []) fldForest:rForest
|
|
||||||
|
|
||||||
pTreePath :: Parser (EmbedPath, Field)
|
pTreePath :: Parser (EmbedPath, Field)
|
||||||
pTreePath = do
|
pTreePath = do
|
||||||
p <- pFieldName `sepBy1` pDelimiter
|
p <- pFieldName `sepBy1` pDelimiter
|
||||||
@@ -76,16 +61,15 @@ pTreePath = do
|
|||||||
|
|
||||||
pFieldForest :: Parser [Tree SelectItem]
|
pFieldForest :: Parser [Tree SelectItem]
|
||||||
pFieldForest = pFieldTree `sepBy1` lexeme (char ',')
|
pFieldForest = pFieldTree `sepBy1` lexeme (char ',')
|
||||||
|
where
|
||||||
pFieldTree :: Parser (Tree SelectItem)
|
pFieldTree :: Parser (Tree SelectItem)
|
||||||
pFieldTree = try (Node <$> pRelationSelect <*> between (char '{') (char '}') pFieldForest) -- TODO: "{}" deprecated
|
pFieldTree = try (Node <$> pRelationSelect <*> between (char '{') (char '}') pFieldForest) -- TODO: "{}" deprecated
|
||||||
<|> try (Node <$> pRelationSelect <*> between (char '(') (char ')') pFieldForest)
|
<|> try (Node <$> pRelationSelect <*> between (char '(') (char ')') pFieldForest)
|
||||||
<|> Node <$> pFieldSelect <*> pure []
|
<|> Node <$> pFieldSelect <*> pure []
|
||||||
|
|
||||||
pStar :: Parser Text
|
pStar :: Parser Text
|
||||||
pStar = toS <$> (string "*" *> pure ("*"::ByteString))
|
pStar = toS <$> (string "*" *> pure ("*"::ByteString))
|
||||||
|
|
||||||
|
|
||||||
pFieldName :: Parser Text
|
pFieldName :: Parser Text
|
||||||
pFieldName = do
|
pFieldName = do
|
||||||
matches <- (many1 (letter <|> digit <|> oneOf "_") `sepBy1` dash) <?> "field name (* or [a..z0..9_])"
|
matches <- (many1 (letter <|> digit <|> oneOf "_") `sepBy1` dash) <?> "field name (* or [a..z0..9_])"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import qualified Hasql.Decoders as HD
|
|||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
|
|
||||||
import PostgREST.Config (pgVersion96)
|
import PostgREST.Config (pgVersion96)
|
||||||
import PostgREST.RangeQuery (NonnegRange, rangeLimit, rangeOffset, allRange)
|
import PostgREST.RangeQuery (rangeLimit, rangeOffset, allRange)
|
||||||
import qualified Data.HashMap.Strict as HM
|
import qualified Data.HashMap.Strict as HM
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
@@ -206,47 +206,31 @@ pgFmtLit x =
|
|||||||
|
|
||||||
requestToCountQuery :: Schema -> DbRequest -> SqlQuery
|
requestToCountQuery :: Schema -> DbRequest -> SqlQuery
|
||||||
requestToCountQuery _ (DbMutate _) = undefined
|
requestToCountQuery _ (DbMutate _) = undefined
|
||||||
requestToCountQuery schema (DbRead (Node (Select _ _ logicForest _ _, (mainTbl, _, _, _)) _)) =
|
requestToCountQuery schema (DbRead (Node (Select _ _ logicForest _ _ _, (mainTbl, _, _, _)) _)) =
|
||||||
unwords [
|
unwords [
|
||||||
"SELECT pg_catalog.count(*)",
|
"SELECT pg_catalog.count(*)",
|
||||||
"FROM ", fromQi qi,
|
"FROM ", fromQi qi,
|
||||||
("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) filteredLogic)) `emptyOnFalse` null filteredLogic
|
("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) logicForest)) `emptyOnFalse` null logicForest
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
qi = removeSourceCTESchema schema mainTbl
|
qi = removeSourceCTESchema schema mainTbl
|
||||||
-- all foreing key filters are root nodes(see addFilterToLogicForest), only those are filtered
|
|
||||||
nonFKRoot :: LogicTree -> Bool
|
|
||||||
nonFKRoot (Stmnt (Filter _ (OpExpr _ (Join _ _)))) = False
|
|
||||||
nonFKRoot (Stmnt _) = True
|
|
||||||
nonFKRoot Expr{} = True
|
|
||||||
filteredLogic = filter nonFKRoot logicForest
|
|
||||||
|
|
||||||
requestToQuery :: Schema -> Bool -> DbRequest -> SqlQuery
|
requestToQuery :: Schema -> Bool -> DbRequest -> SqlQuery
|
||||||
requestToQuery schema isParent (DbRead (Node (Select colSelects tbls logicForest ord range, (nodeName, maybeRelation, _, _)) forest)) =
|
requestToQuery schema isParent (DbRead (Node (Select colSelects tbls logicForest joinConds_ ordts range, (nodeName, maybeRelation, _, _)) forest)) =
|
||||||
query
|
query
|
||||||
where
|
where
|
||||||
mainTbl = fromMaybe nodeName (tableName . relTable <$> maybeRelation)
|
mainTbl = fromMaybe nodeName (tableName . relTable <$> maybeRelation)
|
||||||
qi = removeSourceCTESchema schema mainTbl
|
qi = removeSourceCTESchema schema mainTbl
|
||||||
toQi = removeSourceCTESchema schema
|
toQi = removeSourceCTESchema schema
|
||||||
query = unwords [
|
query = unwords [
|
||||||
"SELECT ", intercalate ", " (map (pgFmtSelectItem qi) colSelects ++ selects),
|
"SELECT " <> intercalate ", " (map (pgFmtSelectItem qi) colSelects ++ selects),
|
||||||
"FROM ", intercalate ", " (map (fromQi . toQi) tbls),
|
"FROM " <> intercalate ", " (map (fromQi . toQi) tbls),
|
||||||
unwords joins,
|
unwords joins,
|
||||||
("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) logicForest)) `emptyOnFalse` null logicForest,
|
("WHERE " <> intercalate " AND " (map (pgFmtLogicTree qi) logicForest ++ map pgFmtJoinCond joinConds_))
|
||||||
orderF (fromMaybe [] ord),
|
`emptyOnFalse` (null logicForest && null joinConds_),
|
||||||
if isParent then "" else limitF range
|
("ORDER BY " <> intercalate ", " (map (pgFmtOrderTerm qi) ordts)) `emptyOnFalse` null ordts,
|
||||||
]
|
("LIMIT " <> maybe "ALL" show (rangeLimit range) <> " OFFSET " <> show (rangeOffset range)) `emptyOnFalse` (isParent || range == allRange) ]
|
||||||
orderF ts =
|
|
||||||
if null ts
|
|
||||||
then ""
|
|
||||||
else "ORDER BY " <> clause
|
|
||||||
where
|
|
||||||
clause = intercalate "," (map queryTerm ts)
|
|
||||||
queryTerm :: OrderTerm -> Text
|
|
||||||
queryTerm t = " "
|
|
||||||
<> toS (pgFmtField qi $ otTerm t) <> " "
|
|
||||||
<> maybe "" show (otDirection t) <> " "
|
|
||||||
<> maybe "" show (otNullOrder t) <> " "
|
|
||||||
(joins, selects) = foldr getQueryParts ([],[]) forest
|
(joins, selects) = foldr getQueryParts ([],[]) forest
|
||||||
|
|
||||||
getQueryParts :: Tree ReadNode -> ([SqlFragment], [SqlFragment]) -> ([SqlFragment], [SqlFragment])
|
getQueryParts :: Tree ReadNode -> ([SqlFragment], [SqlFragment]) -> ([SqlFragment], [SqlFragment])
|
||||||
@@ -376,14 +360,6 @@ locationF pKeys = [qc|(
|
|||||||
{("WHERE json_data.key IN ('" <> intercalate "','" pKeys <> "')") `emptyOnFalse` null pKeys}
|
{("WHERE json_data.key IN ('" <> intercalate "','" pKeys <> "')") `emptyOnFalse` null pKeys}
|
||||||
)|]
|
)|]
|
||||||
|
|
||||||
limitF :: NonnegRange -> SqlFragment
|
|
||||||
limitF r = if r == allRange
|
|
||||||
then ""
|
|
||||||
else "LIMIT " <> limit <> " OFFSET " <> offset
|
|
||||||
where
|
|
||||||
limit = maybe "ALL" show $ rangeLimit r
|
|
||||||
offset = show $ rangeOffset r
|
|
||||||
|
|
||||||
fromQi :: QualifiedIdentifier -> SqlFragment
|
fromQi :: QualifiedIdentifier -> SqlFragment
|
||||||
fromQi t = (if s == "" then "" else pgFmtIdent s <> ".") <> pgFmtIdent n
|
fromQi t = (if s == "" then "" else pgFmtIdent s <> ".") <> pgFmtIdent n
|
||||||
where
|
where
|
||||||
@@ -407,6 +383,12 @@ pgFmtSelectItem :: QualifiedIdentifier -> SelectItem -> SqlFragment
|
|||||||
pgFmtSelectItem table (f@(_, jp), Nothing, alias, _) = pgFmtField table f <> pgFmtAs jp alias
|
pgFmtSelectItem table (f@(_, jp), Nothing, alias, _) = pgFmtField table f <> pgFmtAs jp alias
|
||||||
pgFmtSelectItem table (f@(_, jp), Just cast, alias, _) = "CAST (" <> pgFmtField table f <> " AS " <> cast <> " )" <> pgFmtAs jp alias
|
pgFmtSelectItem table (f@(_, jp), Just cast, alias, _) = "CAST (" <> pgFmtField table f <> " AS " <> cast <> " )" <> pgFmtAs jp alias
|
||||||
|
|
||||||
|
pgFmtOrderTerm :: QualifiedIdentifier -> OrderTerm -> SqlFragment
|
||||||
|
pgFmtOrderTerm qi ot = unwords [
|
||||||
|
toS . pgFmtField qi $ otTerm ot,
|
||||||
|
maybe "" show $ otDirection ot,
|
||||||
|
maybe "" show $ otNullOrder ot]
|
||||||
|
|
||||||
pgFmtFilter :: QualifiedIdentifier -> Filter -> SqlFragment
|
pgFmtFilter :: QualifiedIdentifier -> Filter -> SqlFragment
|
||||||
pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper of
|
pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper of
|
||||||
Op op val -> pgFmtFieldOp op <> " " <> case op of
|
Op op val -> pgFmtFieldOp op <> " " <> case op of
|
||||||
@@ -428,9 +410,6 @@ pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper
|
|||||||
<> maybe "" ((<> ", ") . pgFmtLit) lang
|
<> maybe "" ((<> ", ") . pgFmtLit) lang
|
||||||
<> unknownLiteral val
|
<> unknownLiteral val
|
||||||
<> ") "
|
<> ") "
|
||||||
|
|
||||||
Join fQi (ForeignKey Column{colTable=Table{tableName=fTableName}, colName=fColName}) ->
|
|
||||||
pgFmtField fQi fld <> " = " <> pgFmtColumn (removeSourceCTESchema (qiSchema fQi) fTableName) fColName
|
|
||||||
where
|
where
|
||||||
pgFmtFieldOp op = pgFmtField table fld <> " " <> sqlOperator op
|
pgFmtFieldOp op = pgFmtField table fld <> " " <> sqlOperator op
|
||||||
sqlOperator o = HM.lookupDefault "=" o operators
|
sqlOperator o = HM.lookupDefault "=" o operators
|
||||||
@@ -442,6 +421,10 @@ pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper
|
|||||||
(toS (pgFmtLit v) <> "::unknown ")
|
(toS (pgFmtLit v) <> "::unknown ")
|
||||||
(find ((==) . toLower $ v) ["null","true","false"])
|
(find ((==) . toLower $ v) ["null","true","false"])
|
||||||
|
|
||||||
|
pgFmtJoinCond :: JoinCond -> SqlFragment
|
||||||
|
pgFmtJoinCond (JoinCond (qi, cName) (fQi, fcName)) =
|
||||||
|
pgFmtColumn (removeSourceCTESchema (qiSchema qi) (qiName qi)) cName <> " = " <> pgFmtColumn (removeSourceCTESchema (qiSchema fQi) (qiName fQi)) fcName
|
||||||
|
|
||||||
pgFmtLogicTree :: QualifiedIdentifier -> LogicTree -> SqlFragment
|
pgFmtLogicTree :: QualifiedIdentifier -> LogicTree -> SqlFragment
|
||||||
pgFmtLogicTree qi (Expr hasNot op forest) = notOp <> " (" <> intercalate (" " <> show op <> " ") (pgFmtLogicTree qi <$> forest) <> ")"
|
pgFmtLogicTree qi (Expr hasNot op forest) = notOp <> " (" <> intercalate (" " <> show op <> " ") (pgFmtLogicTree qi <$> forest) <> ")"
|
||||||
where notOp = if hasNot then "NOT" else ""
|
where notOp = if hasNot then "NOT" else ""
|
||||||
|
|||||||
@@ -149,9 +149,10 @@ data Relation = Relation {
|
|||||||
, relFTable :: Table
|
, relFTable :: Table
|
||||||
, relFColumns :: [Column]
|
, relFColumns :: [Column]
|
||||||
, relType :: RelationType
|
, relType :: RelationType
|
||||||
, relLTable :: Maybe Table
|
-- The Link attrs are used when RelationType == Many
|
||||||
, relLCols1 :: Maybe [Column]
|
, relLinkTable :: Maybe Table
|
||||||
, relLCols2 :: Maybe [Column]
|
, relLinkCols1 :: Maybe [Column]
|
||||||
|
, relLinkCols2 :: Maybe [Column]
|
||||||
} deriving (Show, Eq)
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
-- | Cached attributes of a JSON payload
|
-- | Cached attributes of a JSON payload
|
||||||
@@ -215,8 +216,7 @@ ftsOperators = M.fromList [
|
|||||||
data OpExpr = OpExpr Bool Operation deriving (Eq, Show)
|
data OpExpr = OpExpr Bool Operation deriving (Eq, Show)
|
||||||
data Operation = Op Operator SingleVal |
|
data Operation = Op Operator SingleVal |
|
||||||
In ListVal |
|
In ListVal |
|
||||||
Fts Operator (Maybe Language) SingleVal |
|
Fts Operator (Maybe Language) SingleVal deriving (Eq, Show)
|
||||||
Join QualifiedIdentifier ForeignKey deriving (Eq, Show)
|
|
||||||
type Language = Text
|
type Language = Text
|
||||||
|
|
||||||
-- | Represents a single value in a filter, e.g. id=eq.singleval
|
-- | Represents a single value in a filter, e.g. id=eq.singleval
|
||||||
@@ -274,8 +274,9 @@ type SelectItem = (Field, Maybe Cast, Maybe Alias, Maybe RelationDetail)
|
|||||||
-- | Path of the embedded levels, e.g "clients.projects.name=eq.." gives Path ["clients", "projects"]
|
-- | Path of the embedded levels, e.g "clients.projects.name=eq.." gives Path ["clients", "projects"]
|
||||||
type EmbedPath = [Text]
|
type EmbedPath = [Text]
|
||||||
data Filter = Filter { field::Field, opExpr::OpExpr } deriving (Show, Eq)
|
data Filter = Filter { field::Field, opExpr::OpExpr } deriving (Show, Eq)
|
||||||
|
data JoinCond = JoinCond (QualifiedIdentifier, FieldName) (QualifiedIdentifier, FieldName) deriving (Show, Eq)
|
||||||
|
|
||||||
data ReadQuery = Select { select::[SelectItem], from::[TableName], where_::[LogicTree], order::Maybe [OrderTerm], range_::NonnegRange } deriving (Show, Eq)
|
data ReadQuery = Select { select::[SelectItem], from::[TableName], where_::[LogicTree], joinConds::[JoinCond], order::[OrderTerm], range_::NonnegRange } deriving (Show, Eq)
|
||||||
data MutateQuery = Insert { in_::TableName, insPkCols::[Text], qPayload::PayloadJSON, onConflict:: Maybe PreferResolution, where_::[LogicTree], returning::[FieldName] }
|
data MutateQuery = Insert { in_::TableName, insPkCols::[Text], qPayload::PayloadJSON, onConflict:: Maybe PreferResolution, where_::[LogicTree], returning::[FieldName] }
|
||||||
| Delete { in_::TableName, where_::[LogicTree], returning::[FieldName] }
|
| Delete { in_::TableName, where_::[LogicTree], returning::[FieldName] }
|
||||||
| Update { in_::TableName, qPayload::PayloadJSON, where_::[LogicTree], returning::[FieldName] } deriving (Show, Eq)
|
| Update { in_::TableName, qPayload::PayloadJSON, where_::[LogicTree], returning::[FieldName] } deriving (Show, Eq)
|
||||||
|
|||||||
Reference in New Issue
Block a user