handle many to many relations
This commit is contained in:
+34
-20
@@ -16,11 +16,10 @@ import PostgREST.PgQuery (PStmt, QualifiedIdentifier (..), fromQi,
|
|||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
--import qualified Hasql as H
|
--import qualified Hasql as H
|
||||||
--import qualified Hasql.Postgres as P
|
--import qualified Hasql.Postgres as P
|
||||||
|
import Control.Applicative ((<|>))
|
||||||
import qualified Data.Vector as V (empty)
|
import qualified Data.Vector as V (empty)
|
||||||
import qualified Hasql.Backend as B
|
import qualified Hasql.Backend as B
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
findColumn :: [Column] -> Text -> Text -> Text -> Either Text Column
|
findColumn :: [Column] -> Text -> Text -> Text -> Either Text Column
|
||||||
findColumn allColumns s t c = note ("no such column: "<>t<>"."<>c) $
|
findColumn allColumns s t c = note ("no such column: "<>t<>"."<>c) $
|
||||||
find (\ col -> colSchema col == s && colTable col == t && colName col == c ) allColumns
|
find (\ col -> colSchema col == s && colTable col == t && colName col == c ) allColumns
|
||||||
@@ -40,8 +39,9 @@ addRelations schema allRelations parentNode node@(Node query@(Select {mainTable=
|
|||||||
Nothing -> Node query{relation=Nothing} <$> updatedForest
|
Nothing -> Node query{relation=Nothing} <$> updatedForest
|
||||||
(Just (Node (Select{mainTable=parentTable}) _)) -> Node <$> (addRel query <$> rel) <*> updatedForest
|
(Just (Node (Select{mainTable=parentTable}) _)) -> Node <$> (addRel query <$> rel) <*> updatedForest
|
||||||
where
|
where
|
||||||
rel = note ("no relation between " <> table <> " and " <> parentTable) $
|
rel = note ("no relation between " <> table <> " and " <> parentTable)
|
||||||
findRelation allRelations schema table parentTable
|
$ findRelation allRelations schema table parentTable
|
||||||
|
<|> findRelation allRelations schema parentTable table
|
||||||
addRel :: Query -> Relation -> Query
|
addRel :: Query -> Relation -> Query
|
||||||
addRel q r = q{relation = Just r}
|
addRel q r = q{relation = Just r}
|
||||||
where
|
where
|
||||||
@@ -52,26 +52,34 @@ addJoinConditions :: Text -> [Column] -> ApiRequest -> Either Text ApiRequest
|
|||||||
addJoinConditions schema allColumns (Node query@(Select{relation=r}) forest) =
|
addJoinConditions schema allColumns (Node query@(Select{relation=r}) forest) =
|
||||||
case r of
|
case r of
|
||||||
Nothing -> Node updatedQuery <$> updatedForest -- this is the root node
|
Nothing -> Node updatedQuery <$> updatedForest -- this is the root node
|
||||||
Just rel@(Relation{relType="child"}) -> Node (addCond updatedQuery (getJoinCondition rel)) <$> updatedForest
|
Just rel@(Relation{relType="child"}) -> Node (addCond updatedQuery (getJoinConditions rel)) <$> updatedForest
|
||||||
Just (Relation{relType="parent"}) -> Node updatedQuery <$> updatedForest
|
Just (Relation{relType="parent"}) -> Node updatedQuery <$> updatedForest
|
||||||
-- Just (Many relationColumn1 relationColumn2) -> Node <$> pure updatedQuery{qJoinTables=linkTable:qJoinTables updatedQuery, qWhere=cond1:cond2:qWhere updatedQuery} <*> updatedForest
|
Just rel@(Relation{relType="many", relLTable=(Just linkTable)}) ->
|
||||||
-- where
|
Node <$> pure qq <*> updatedForest
|
||||||
-- cond1 = getJoinCondition relationColumn1
|
where
|
||||||
-- cond2 = getJoinCondition relationColumn2
|
q = addCond updatedQuery (getJoinConditions rel)
|
||||||
-- linkTable = Table "public" (colTable relationColumn1) True
|
qq = q{joinTables=linkTable:joinTables q}
|
||||||
_ -> Left "unknow relation"
|
_ -> Left "unknow relation"
|
||||||
where
|
where
|
||||||
-- add parentTable and parentJoinConditions to the query
|
-- add parentTable and parentJoinConditions to the query
|
||||||
updatedQuery = foldr (flip addCond) (query{joinTables = parentTables ++ joinTables query}) parentJoinConditions
|
updatedQuery = foldr (flip addCond) (query{joinTables = parentTables ++ joinTables query}) parentJoinConditions
|
||||||
where
|
where
|
||||||
parentJoinConditions = map (getJoinCondition.snd) parents
|
parentJoinConditions = map (getJoinConditions.snd) parents
|
||||||
parentTables = map fst parents
|
parentTables = map fst parents
|
||||||
parents = mapMaybe (getParents.rootLabel) forest
|
parents = mapMaybe (getParents.rootLabel) forest
|
||||||
getParents qq@(Select{relation=(Just rel@(Relation{relType="parent"}))}) = Just (mainTable qq, rel)
|
getParents qq@(Select{relation=(Just rel@(Relation{relType="parent"}))}) = Just (mainTable qq, rel)
|
||||||
getParents _ = Nothing
|
getParents _ = Nothing
|
||||||
updatedForest = mapM (addJoinConditions schema allColumns) forest
|
updatedForest = mapM (addJoinConditions schema allColumns) forest
|
||||||
getJoinCondition rel@(Relation _ _ c _ _ _) = Filter (c, Nothing) "=" (VForeignKey rel)
|
getJoinConditions :: Relation -> [Filter]
|
||||||
addCond q con = q{filters=con:filters q}
|
getJoinConditions rel@(Relation _ _ c _ _ "child" _ _ _) = [Filter (c, Nothing) "=" (VForeignKey rel)]
|
||||||
|
getJoinConditions rel@(Relation _ _ c _ _ "parent" _ _ _) = [Filter (c, Nothing) "=" (VForeignKey rel)]
|
||||||
|
getJoinConditions (Relation s t c ft fc "many" (Just lt) (Just lc1) (Just lc2)) =
|
||||||
|
[
|
||||||
|
Filter (c, Nothing) "=" (VForeignKey (Relation s t c lt lc1 "child" Nothing Nothing Nothing)),
|
||||||
|
Filter (fc, Nothing) "=" (VForeignKey (Relation s ft fc lt lc2 "child" Nothing Nothing Nothing))
|
||||||
|
]
|
||||||
|
getJoinConditions _ = []
|
||||||
|
addCond q con = q{filters=con ++ filters q}
|
||||||
|
|
||||||
|
|
||||||
requestToCountQuery :: Text -> ApiRequest -> PStmt
|
requestToCountQuery :: Text -> ApiRequest -> PStmt
|
||||||
@@ -120,25 +128,31 @@ requestToQuery schema (Node (Select mainTbl colSelects tbls conditions ord _) fo
|
|||||||
sel = "row_to_json(" <> table <> ".*) AS "<>table --TODO must be singular
|
sel = "row_to_json(" <> table <> ".*) AS "<>table --TODO must be singular
|
||||||
wit = table <> " AS ( " <> subquery <> " )"
|
wit = table <> " AS ( " <> subquery <> " )"
|
||||||
where (B.Stmt subquery _ _) = requestToQuery schema (Node q forst)
|
where (B.Stmt subquery _ _) = requestToQuery schema (Node q forst)
|
||||||
-- getQueryParts (Node q@(Select{qMainTable=table, qRelation=(Just (Many _ _))}) forst) (w,s) = (w,sel:s)
|
|
||||||
-- where name = tableName table
|
getQueryParts (Node q@(Select{mainTable=table, relation=(Just (Relation {relType="many"}))}) forst) (w,s) = (w,sel:s)
|
||||||
-- sel = "("
|
where
|
||||||
-- <> "SELECT array_to_json(array_agg(row_to_json("<>name<>"))) "
|
sel = "("
|
||||||
-- <> "FROM (" <> requestToQuery (Node q forst) <> ") " <> name
|
<> "SELECT array_to_json(array_agg(row_to_json("<>table<>"))) "
|
||||||
-- <> ") AS " <> name
|
<> "FROM (" <> subquery <> ") " <> table
|
||||||
|
<> ") AS " <> table
|
||||||
|
where (B.Stmt subquery _ _) = requestToQuery schema (Node q forst)
|
||||||
|
|
||||||
-- the following is just to remove the warning, maybe relType should not be String?
|
-- the following is just to remove the warning, maybe relType should not be String?
|
||||||
getQueryParts (Node (Select{relation=Nothing}) _) _ = undefined
|
getQueryParts (Node (Select{relation=Nothing}) _) _ = undefined
|
||||||
getQueryParts (Node (Select{relation=(Just (Relation {relType=_}))}) _) _ = undefined
|
getQueryParts (Node (Select{relation=(Just (Relation {relType=_}))}) _) _ = undefined
|
||||||
|
|
||||||
pgFmtCondition :: QualifiedIdentifier -> Filter -> Text
|
pgFmtCondition :: QualifiedIdentifier -> Filter -> Text
|
||||||
pgFmtCondition table (Filter (col,jp) ops val) =
|
pgFmtCondition table (Filter (col,jp) ops val) =
|
||||||
notOp <> " " <> pgFmtColumn table col <> pgFmtJsonPath jp <> " " <> pgFmtOperator opCode <> " " <>
|
notOp <> " " <> sqlCol <> " " <> pgFmtOperator opCode <> " " <>
|
||||||
if opCode `elem` ["is","isnot"] then whiteList (getInner val) else sqlValue
|
if opCode `elem` ["is","isnot"] then whiteList (getInner val) else sqlValue
|
||||||
where
|
where
|
||||||
headPredicate:rest = split (=='.') ops
|
headPredicate:rest = split (=='.') ops
|
||||||
hasNot caseTrue caseFalse = if headPredicate == "not" then caseTrue else caseFalse
|
hasNot caseTrue caseFalse = if headPredicate == "not" then caseTrue else caseFalse
|
||||||
opCode = hasNot (head rest) headPredicate
|
opCode = hasNot (head rest) headPredicate
|
||||||
notOp = hasNot headPredicate ""
|
notOp = hasNot headPredicate ""
|
||||||
|
sqlCol = case val of
|
||||||
|
VText _ -> pgFmtColumn table col <> pgFmtJsonPath jp
|
||||||
|
VForeignKey (Relation s t c _ _ _ _ _ _) -> pgFmtColumn (QualifiedIdentifier s t) c
|
||||||
sqlValue = valToStr val
|
sqlValue = valToStr val
|
||||||
getInner v = case v of
|
getInner v = case v of
|
||||||
VText s -> s
|
VText s -> s
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import PostgREST.Types
|
|||||||
import Data.Functor.Identity
|
import Data.Functor.Identity
|
||||||
--import Data.String.Conversions (cs)
|
--import Data.String.Conversions (cs)
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
import Data.Maybe (fromMaybe, isJust)
|
import Data.Maybe (fromMaybe, isJust, mapMaybe)
|
||||||
|
import Data.Monoid
|
||||||
|
|
||||||
--import qualified Data.Map as Map
|
--import qualified Data.Map as Map
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ import qualified Hasql as H
|
|||||||
import qualified Hasql.Postgres as P
|
import qualified Hasql.Postgres as P
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
import GHC.Exts (groupWith)
|
||||||
|
|
||||||
|
|
||||||
doesProcExist :: Text -> Text -> H.Tx P.Postgres s Bool
|
doesProcExist :: Text -> Text -> H.Tx P.Postgres s Bool
|
||||||
@@ -60,14 +62,14 @@ columnFromRow (s, t, n, pos, nul, typ, u, l, p, d, e) =
|
|||||||
|
|
||||||
|
|
||||||
relationFromRow :: (Text, Text, Text, Text, Text) -> Relation
|
relationFromRow :: (Text, Text, Text, Text, Text) -> Relation
|
||||||
relationFromRow (s, t, c, ft, fc) = Relation s t c ft fc "child"
|
relationFromRow (s, t, c, ft, fc) = Relation s t c ft fc "child" Nothing Nothing Nothing
|
||||||
|
|
||||||
pkFromRow :: (Text, Text, Text) -> PrimaryKey
|
pkFromRow :: (Text, Text, Text) -> PrimaryKey
|
||||||
pkFromRow (s, t, n) = PrimaryKey s t n
|
pkFromRow (s, t, n) = PrimaryKey s t n
|
||||||
|
|
||||||
|
|
||||||
addFlippedRelation :: Relation -> [Relation] -> [Relation]
|
addParentRelation :: Relation -> [Relation] -> [Relation]
|
||||||
addFlippedRelation rel@(Relation s t c ft fc _) rels = Relation s ft fc t c "parent":rel:rels
|
addParentRelation rel@(Relation s t c ft fc _ _ _ _) rels = Relation s ft fc t c "parent" Nothing Nothing Nothing:rel:rels
|
||||||
|
|
||||||
alltables :: H.Tx P.Postgres s [Table]
|
alltables :: H.Tx P.Postgres s [Table]
|
||||||
alltables = do
|
alltables = do
|
||||||
@@ -130,7 +132,17 @@ allrelations = do
|
|||||||
)
|
)
|
||||||
|
|
||||||
|]
|
|]
|
||||||
return $ foldr (addFlippedRelation.relationFromRow) [] rels
|
let simpleRelations = foldr (addParentRelation.relationFromRow) [] rels
|
||||||
|
let links = filter ((==2).length) $ groupWith groupFn $ filter ( (=="child"). relType) simpleRelations
|
||||||
|
return $ simpleRelations ++ mapMaybe link2Relation links
|
||||||
|
where
|
||||||
|
groupFn :: Relation -> Text
|
||||||
|
groupFn (Relation{relSchema=s, relTable=t}) = s<>"_"<>t
|
||||||
|
link2Relation [
|
||||||
|
Relation{relSchema=sc, relTable=lt, relColumn=lc1, relFTable=t, relFColumn=c},
|
||||||
|
Relation{ relColumn=lc2, relFTable=ft, relFColumn=fc}
|
||||||
|
] = Just $ Relation sc t c ft fc "many" (Just lt) (Just lc1) (Just lc2)
|
||||||
|
link2Relation _ = Nothing
|
||||||
|
|
||||||
allcolumns :: [Relation] -> H.Tx P.Postgres s [Column]
|
allcolumns :: [Relation] -> H.Tx P.Postgres s [Column]
|
||||||
allcolumns rels = do
|
allcolumns rels = do
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ data Relation = Relation {
|
|||||||
, relFTable :: Text
|
, relFTable :: Text
|
||||||
, relFColumn :: Text
|
, relFColumn :: Text
|
||||||
, relType :: Text
|
, relType :: Text
|
||||||
|
, relLTable :: Maybe Text
|
||||||
|
, relLCol1 :: Maybe Text
|
||||||
|
, relLCol2 :: Maybe Text
|
||||||
} deriving (Show, Eq)
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user