refactor: improve disambiguation error message

* reverse backwards relationships
* remove redundancy from getJoinSelects
* properly name Cardinality constructors
This commit is contained in:
steve-chavez
2019-11-17 13:28:21 -05:00
committed by Steve Chavez
parent 4ef6926791
commit f9c64d9f65
6 changed files with 186 additions and 186 deletions
+51 -49
View File
@@ -62,9 +62,9 @@ rootWithRelations schema rootTableName allRels action = case action of
-- To enable embedding in the sourceCTEName cases we need to replace the foreign key tableName in the Relation -- To enable embedding in the sourceCTEName cases we need to replace the foreign key tableName in the Relation
-- with {sourceCTEName}. This way findRelation can find Relations with sourceCTEName. -- with {sourceCTEName}. This way findRelation can find Relations with sourceCTEName.
toSourceRelation :: Relation -> Maybe Relation toSourceRelation :: Relation -> Maybe Relation
toSourceRelation r@Relation{relFTable=ft} toSourceRelation r@Relation{relTable=t}
| rootTableName == tableName ft = Just $ r {relFTable=ft {tableName=sourceCTEName}} | rootTableName == tableName t = Just $ r {relTable=t {tableName=sourceCTEName}}
| otherwise = Nothing | otherwise = Nothing
-- Build the initial tree with a Depth attribute so when a self join occurs we can differentiate the parent and child tables by having -- Build the initial tree with a Depth attribute so when a self join occurs we 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. -- an alias like "table_depth", this is related to http://github.com/PostgREST/postgrest/issues/987.
@@ -102,21 +102,22 @@ addRelations :: Schema -> [Relation] -> Maybe ReadRequest -> ReadRequest -> Eith
addRelations schema allRelations parentNode (Node (query@Select{from=tbl}, (nodeName, _, alias, relationDetail, depth)) forest) = addRelations schema allRelations parentNode (Node (query@Select{from=tbl}, (nodeName, _, alias, relationDetail, depth)) forest) =
case parentNode of case parentNode of
Just (Node (Select{from=parentNodeQi}, _) _) -> Just (Node (Select{from=parentNodeQi}, _) _) ->
let newFrom r = if qiName tbl == nodeName then tableQi (relTable r) else tbl let newFrom r = if qiName tbl == nodeName then tableQi (relFTable r) else tbl
newReadNode = (\r -> (query{from=newFrom r}, (nodeName, Just r, alias, Nothing, depth))) <$> rel newReadNode = (\r -> (query{from=newFrom r}, (nodeName, Just r, alias, Nothing, depth))) <$> rel
parentNodeTable = qiName parentNodeQi parentNodeTable = qiName parentNodeQi
results = findRelation schema allRelations nodeName parentNodeTable relationDetail results = findRelation schema allRelations parentNodeTable nodeName relationDetail
rel :: Either ApiRequestError Relation rel :: Either ApiRequestError Relation
rel = case results of rel = case results of
[] -> Left $ NoRelBetween parentNodeTable nodeName [] -> Left $ NoRelBetween parentNodeTable nodeName
[r] -> Right r [r] -> Right r
rs -> rs ->
-- Temporary hack for handling a self reference relationship. In this case we get a parent and child rel with the same relTable/relFtable. -- Hack for handling a self reference relationship.
-- We output the child rel, the parent can be obtained by using the fk column as an embed hint. -- In this case we get an O2M and M2O rels with the same relTable and relFtable.
-- We output the O2M rel, the M2O rel can be obtained by using the fk column as an embed hint in findRelation.
let rel0 = head rs let rel0 = head rs
rel1 = rs !! 1 in rel1 = rs !! 1 in
if length rs == 2 && relTable rel0 == relTable rel1 && relFTable rel0 == relFTable rel1 if length rs == 2 && relTable rel0 == relTable rel1 && relFTable rel0 == relFTable rel1
then note (NoRelBetween parentNodeTable nodeName) (find (\r -> relType r == Child) rs) then note (NoRelBetween parentNodeTable nodeName) (find (\r -> relType r == O2M) rs)
else Left $ AmbiguousRelBetween parentNodeTable nodeName rs else Left $ AmbiguousRelBetween parentNodeTable nodeName rs
in in
Node <$> newReadNode <*> (updateForest . hush $ Node <$> newReadNode <*> pure forest) Node <$> newReadNode <*> (updateForest . hush $ Node <$> newReadNode <*> pure forest)
@@ -127,8 +128,8 @@ addRelations schema allRelations parentNode (Node (query@Select{from=tbl}, (node
updateForest :: Maybe ReadRequest -> Either ApiRequestError [ReadRequest] updateForest :: Maybe ReadRequest -> Either ApiRequestError [ReadRequest]
updateForest rq = mapM (addRelations schema allRelations rq) forest updateForest rq = mapM (addRelations schema allRelations rq) forest
findRelation :: Schema -> [Relation] -> NodeName -> TableName -> Maybe RelationDetail -> [Relation] findRelation :: Schema -> [Relation] -> TableName -> NodeName -> Maybe RelationDetail -> [Relation]
findRelation schema allRelations nodeTableName parentNodeTableName relationDetail = findRelation schema allRelations parentTableName nodeName relationDetail =
filter (\Relation{relTable, relColumns, relFTable, relFColumns, relType, relLinkTable} -> filter (\Relation{relTable, relColumns, relFTable, relFColumns, relType, relLinkTable} ->
-- Both relation ends need to be on the exposed schema -- Both relation ends need to be on the exposed schema
schema == tableSchema relTable && schema == tableSchema relFTable && schema == tableSchema relTable && schema == tableSchema relFTable &&
@@ -137,29 +138,30 @@ findRelation schema allRelations nodeTableName parentNodeTableName relationDetai
-- (request) => projects { ..., clients{...} } -- (request) => projects { ..., clients{...} }
-- will match -- will match
-- (relation type) => parent -- (relation type) => M2O
-- (entity) => clients {id} -- (entity) => clients {id}
-- (foriegn entity) => projects {client_id} -- (foriegn entity) => projects {client_id}
( (
nodeTableName == tableName relTable && -- match relation table name parentTableName == tableName relTable && -- projects
parentNodeTableName == tableName relFTable -- match relation foreign table name nodeName == tableName relFTable -- clients
) || ) ||
-- (request) => projects { ..., client_id{...} } -- (request) => projects { ..., client_id{...} }
-- will match -- will match
-- (relation type) => parent -- (relation type) => M2O
-- (entity) => clients {id} -- (entity) => clients {id}
-- (foriegn entity) => projects {client_id} -- (foriegn entity) => projects {client_id}
( (
parentNodeTableName == tableName relFTable && parentTableName == tableName relTable && -- projects
length relFColumns == 1 && length relColumns == 1 &&
-- match common foreign key names(table_name_id, table_name_fk) to table_name -- match common foreign key names(table_name_id, table_name_fk) to table_name
(toS ("^" <> colName (unsafeHead relFColumns) <> "_?(?:|[iI][dD]|[fF][kK])$") :: BS.ByteString) =~ (toS nodeTableName :: BS.ByteString) (toS ("^" <> colName (unsafeHead relColumns) <> "_?(?:|[iI][dD]|[fF][kK])$") :: BS.ByteString) =~
(toS nodeName :: BS.ByteString) -- client_id
) )
-- (request) => project_id { ..., client_id{...} } -- (request) => project_id { ..., client_id{...} }
-- will match -- will match
-- (relation type) => parent -- (relation type) => M2O
-- (entity) => clients {id} -- (entity) => clients {id}
-- (foriegn entity) => projects {client_id} -- (foriegn entity) => projects {client_id}
-- this case works becasue before reaching this place -- this case works becasue before reaching this place
@@ -167,42 +169,42 @@ findRelation schema allRelations nodeTableName parentNodeTableName relationDetai
Just rd -> Just rd ->
-- (request) => clients { ..., projects.client_id{...} } -- (request) => clients { ..., projects!client_id{...} }
-- will match -- will match
-- (relation type) => child -- (relation type) => O2M
-- (entity) => clients {id} -- (entity) => clients {id}
-- (foriegn entity) => projects {client_id} -- (foriegn entity) => projects {client_id}
( (
relType == Child && relType == O2M &&
nodeTableName == tableName relTable && -- match relation table name parentTableName == tableName relTable && -- clients
parentNodeTableName == tableName relFTable && -- match relation foreign table name nodeName == tableName relFTable && -- projects
length relColumns == 1 && length relFColumns == 1 &&
rd == colName (unsafeHead relColumns) rd == colName (unsafeHead relFColumns) -- rd is client_id
) || ) ||
-- (request) => message { ..., person_detail.sender{...} } -- (request) => message { ..., person_detail!sender{...} }
-- will match -- will match
-- (relation type) => parent -- (relation type) => M2O
-- (entity) => message {sender} -- (entity) => message {sender}
-- (foriegn entity) => person_detail {id} -- (foriegn entity) => person_detail {id}
( (
relType == Parent && relType == M2O &&
nodeTableName == tableName relTable && -- match relation table name parentTableName == tableName relTable && -- message
parentNodeTableName == tableName relFTable && -- match relation foreign table name nodeName == tableName relFTable && -- person_detail
length relFColumns == 1 && length relColumns == 1 &&
rd == colName (unsafeHead relFColumns) rd == colName (unsafeHead relColumns) -- rd is sender
) || ) ||
-- (request) => tasks { ..., users.tasks_users{...} } -- (request) => tasks { ..., users.tasks_users{...} }
-- will match -- will match
-- (relation type) => many -- (relation type) => M2M
-- (entity) => users -- (entity) => users
-- (foriegn entity) => tasks -- (foriegn entity) => tasks
( (
relType == Many && relType == M2M &&
nodeTableName == tableName relTable && -- match relation table name parentTableName == tableName relTable && -- tasks
parentNodeTableName == tableName relFTable && -- match relation foreign table name nodeName == tableName relFTable && -- users
rd == tableName (fromJust relLinkTable) rd == tableName (fromJust relLinkTable) -- rd is tasks_users
) )
) allRelations ) allRelations
@@ -210,9 +212,9 @@ findRelation schema allRelations nodeTableName parentNodeTableName relationDetai
addJoinConditions :: Maybe Alias -> ReadRequest -> Either ApiRequestError ReadRequest addJoinConditions :: Maybe Alias -> ReadRequest -> Either ApiRequestError ReadRequest
addJoinConditions previousAlias (Node node@(query@Select{from=tbl}, nodeProps@(_, relation, _, _, depth)) forest) = addJoinConditions previousAlias (Node node@(query@Select{from=tbl}, nodeProps@(_, relation, _, _, depth)) forest) =
case relation of case relation of
Just rel@Relation{relType=Parent} -> Node (augmentQuery rel, nodeProps) <$> updatedForest Just rel@Relation{relType=O2M} -> Node (augmentQuery rel, nodeProps) <$> updatedForest
Just rel@Relation{relType=Child} -> Node (augmentQuery rel, nodeProps) <$> updatedForest Just rel@Relation{relType=M2O} -> Node (augmentQuery rel, nodeProps) <$> updatedForest
Just rel@Relation{relType=Many, relLinkTable=lTable} -> Just rel@Relation{relType=M2M, relLinkTable=lTable} ->
case lTable of case lTable of
Just linkTable -> Just linkTable ->
let rq = augmentQuery rel in let rq = augmentQuery rel in
@@ -237,11 +239,11 @@ addJoinConditions previousAlias (Node node@(query@Select{from=tbl}, nodeProps@(_
getJoinConditions :: Maybe Alias -> Maybe Alias -> Relation -> [JoinCondition] getJoinConditions :: Maybe Alias -> Maybe Alias -> Relation -> [JoinCondition]
getJoinConditions previousAlias newAlias (Relation Table{tableSchema=tSchema, tableName=tN} cols Table{tableName=ftN} fCols typ lt lc1 lc2) = getJoinConditions previousAlias newAlias (Relation Table{tableSchema=tSchema, tableName=tN} cols Table{tableName=ftN} fCols typ lt lc1 lc2) =
case typ of case typ of
Child -> O2M ->
zipWith (toJoinCondition tN ftN) cols fCols zipWith (toJoinCondition tN ftN) cols fCols
Parent -> M2O ->
zipWith (toJoinCondition tN ftN) cols fCols zipWith (toJoinCondition tN ftN) cols fCols
Many -> M2M ->
let ltN = maybe "" tableName lt in let ltN = maybe "" tableName lt in
zipWith (toJoinCondition tN ltN) cols (fromMaybe [] lc1) ++ zipWith (toJoinCondition ftN ltN) fCols (fromMaybe [] lc2) zipWith (toJoinCondition tN ltN) cols (fromMaybe [] lc1) ++ zipWith (toJoinCondition ftN ltN) fCols (fromMaybe [] lc2)
where where
@@ -249,8 +251,8 @@ getJoinConditions previousAlias newAlias (Relation Table{tableSchema=tSchema, ta
toJoinCondition tb ftb c fc = toJoinCondition tb ftb c fc =
let qi1 = removeSourceCTESchema tSchema tb let qi1 = removeSourceCTESchema tSchema tb
qi2 = removeSourceCTESchema tSchema ftb in qi2 = removeSourceCTESchema tSchema ftb in
JoinCondition (maybe qi1 (QualifiedIdentifier mempty) newAlias, colName c) JoinCondition (maybe qi1 (QualifiedIdentifier mempty) previousAlias, colName c)
(maybe qi2 (QualifiedIdentifier mempty) previousAlias, colName fc) (maybe qi2 (QualifiedIdentifier mempty) newAlias, colName fc)
-- On mutation and calling proc cases we wrap the target table in a WITH {sourceCTEName} -- On mutation and calling proc cases we wrap the target table in a WITH {sourceCTEName}
-- if this happens remove the schema `FROM "schema"."{sourceCTEName}"` and use only the -- if this happens remove the schema `FROM "schema"."{sourceCTEName}"` and use only the
@@ -361,10 +363,10 @@ returningCols rr@(Node _ forest) = returnings
-- So this adds the foreign key columns to ensure the embedding succeeds, result would be `RETURNING name, client_id`. -- So this adds the foreign key columns to ensure the embedding succeeds, result would be `RETURNING name, client_id`.
-- This also works for the other relType's. -- This also works for the other relType's.
fkCols = concat $ mapMaybe (\case fkCols = concat $ mapMaybe (\case
Node (_, (_, Just Relation{relFColumns=cols, relType=relTyp}, _, _, _)) _ -> case relTyp of Node (_, (_, Just Relation{relColumns=cols, relType=relTyp}, _, _, _)) _ -> case relTyp of
Parent -> Just cols O2M -> Just cols
Child -> Just cols M2O -> Just cols
Many -> Just cols M2M -> Just cols
_ -> Nothing _ -> Nothing
) forest ) forest
-- However if the "client_id" is present, e.g. mutateRequest to /projects?select=client_id,name,clients(name) -- However if the "client_id" is present, e.g. mutateRequest to /projects?select=client_id,name,clients(name)
+39 -39
View File
@@ -47,14 +47,14 @@ import Protolude
getDbStructure :: Schema -> PgVersion -> HT.Transaction DbStructure getDbStructure :: Schema -> PgVersion -> HT.Transaction DbStructure
getDbStructure schema pgVer = do getDbStructure schema pgVer = do
HT.sql "set local schema ''" -- for getting the fully qualified name(schema.name) of every db object HT.sql "set local schema ''" -- for getting the fully qualified name(schema.name) of every db object
tabs <- HT.statement () allTables tabs <- HT.statement () allTables
cols <- HT.statement schema $ allColumns tabs cols <- HT.statement schema $ allColumns tabs
srcCols <- HT.statement schema $ allSourceColumns cols pgVer srcCols <- HT.statement schema $ allSourceColumns cols pgVer
childRels <- HT.statement () $ allChildRelations tabs cols m2oRels <- HT.statement () $ allM2ORels tabs cols
keys <- HT.statement () $ allPrimaryKeys tabs keys <- HT.statement () $ allPrimaryKeys tabs
procs <- HT.statement schema allProcs procs <- HT.statement schema allProcs
let rels = addManyToManyRelations . addParentRelations $ addViewChildRelations srcCols childRels let rels = addM2MRels . addO2MRels $ addViewM2ORels srcCols m2oRels
cols' = addForeignKeys rels cols cols' = addForeignKeys rels cols
keys' = addViewPrimaryKeys srcCols keys keys' = addViewPrimaryKeys srcCols keys
@@ -91,9 +91,9 @@ decodeColumns tables =
<*> nullableColumn HD.text <*> nullableColumn HD.text
<*> nullableColumn HD.text <*> nullableColumn HD.text
decodeRelations :: [Table] -> [Column] -> HD.Result [Relation] decodeRels :: [Table] -> [Column] -> HD.Result [Relation]
decodeRelations tables cols = decodeRels tables cols =
mapMaybe (relationFromRow tables cols) <$> HD.rowList relRow mapMaybe (relFromRow tables cols) <$> HD.rowList relRow
where where
relRow = (,,,,,) relRow = (,,,,,)
<$> column HD.text <$> column HD.text
@@ -250,20 +250,20 @@ addForeignKeys rels = map addFk
addFk col = col { colFK = fk col } addFk col = col { colFK = fk col }
fk col = find (lookupFn col) rels >>= relToFk col fk col = find (lookupFn col) rels >>= relToFk col
lookupFn :: Column -> Relation -> Bool lookupFn :: Column -> Relation -> Bool
lookupFn c Relation{relColumns=cs, relType=rty} = c `elem` cs && rty==Child lookupFn c Relation{relColumns=cs, relType=rty} = c `elem` cs && rty==M2O
relToFk col Relation{relColumns=cols, relFColumns=colsF} = do relToFk col Relation{relColumns=cols, relFColumns=colsF} = do
pos <- L.elemIndex col cols pos <- L.elemIndex col cols
colF <- atMay colsF pos colF <- atMay colsF pos
return $ ForeignKey colF return $ ForeignKey colF
{- {-
Adds Views Child Relations based on SourceColumns found, the logic is as follows: Adds Views M2O Relations based on SourceColumns found, the logic is as follows:
Having a Relation{relTable=t1, relColumns=[c1], relFTable=t2, relFColumns=[c2], relType=Child} represented by: Having a Relation{relTable=t1, relColumns=[c1], relFTable=t2, relFColumns=[c2], relType=M2O} represented by:
t1.c1------t2.c2 t1.c1------t2.c2
When only having a t1_view.c1 source column, we need to add a View-Table Child Relation When only having a t1_view.c1 source column, we need to add a View-Table M2O Relation
t1.c1----t2.c2 t1.c1----------t2.c2 t1.c1----t2.c2 t1.c1----------t2.c2
-> ________/ -> ________/
@@ -271,14 +271,14 @@ When only having a t1_view.c1 source column, we need to add a View-Table Child R
t1_view.c1 t1_view.c1 t1_view.c1 t1_view.c1
When only having a t2_view.c2 source column, we need to add a Table-View Child Relation When only having a t2_view.c2 source column, we need to add a Table-View M2O Relation
t1.c1----t2.c2 t1.c1----------t2.c2 t1.c1----t2.c2 t1.c1----------t2.c2
-> \________ -> \________
\ \
t2_view.c2 t2_view.c1 t2_view.c2 t2_view.c1
When having t1_view.c1 and a t2_view.c2 source columns, we need to add a View-View Child Relation in addition to the prior When having t1_view.c1 and a t2_view.c2 source columns, we need to add a View-View M2O Relation in addition to the prior
t1.c1----t2.c2 t1.c1----------t2.c2 t1.c1----t2.c2 t1.c1----------t2.c2
-> \________/ -> \________/
@@ -287,10 +287,10 @@ When having t1_view.c1 and a t2_view.c2 source columns, we need to add a View-Vi
The logic for composite pks is similar just need to make sure all the Relation columns have source columns. The logic for composite pks is similar just need to make sure all the Relation columns have source columns.
-} -}
addViewChildRelations :: [SourceColumn] -> [Relation] -> [Relation] addViewM2ORels :: [SourceColumn] -> [Relation] -> [Relation]
addViewChildRelations allSrcCols = concatMap (\rel -> addViewM2ORels allSrcCols = concatMap (\rel ->
rel : case rel of rel : case rel of
Relation{relType=Child, relTable, relColumns, relFTable, relFColumns} -> Relation{relType=M2O, relTable, relColumns, relFTable, relFColumns} ->
let srcColsGroupedByView :: [Column] -> [[SourceColumn]] let srcColsGroupedByView :: [Column] -> [[SourceColumn]]
srcColsGroupedByView relCols = L.groupBy (\(_, viewCol1) (_, viewCol2) -> colTable viewCol1 == colTable viewCol2) $ srcColsGroupedByView relCols = L.groupBy (\(_, viewCol1) (_, viewCol2) -> colTable viewCol1 == colTable viewCol2) $
@@ -305,36 +305,36 @@ addViewChildRelations allSrcCols = concatMap (\rel ->
-- TODO: This could be avoided if the Relation type is improved with a structure that maintains the association of relColumns and relFColumns -- TODO: This could be avoided if the Relation type is improved with a structure that maintains the association of relColumns and relFColumns
srcCols `sortAccordingTo` cols = sortOn (\(k, _) -> L.lookup k $ zip cols [0::Int ..]) srcCols srcCols `sortAccordingTo` cols = sortOn (\(k, _) -> L.lookup k $ zip cols [0::Int ..]) srcCols
viewTableChild = viewTableM2O =
[ Relation (getView srcCols) (snd <$> srcCols `sortAccordingTo` relColumns) [ Relation (getView srcCols) (snd <$> srcCols `sortAccordingTo` relColumns)
relFTable relFColumns relFTable relFColumns
Child Nothing Nothing Nothing M2O Nothing Nothing Nothing
| srcCols <- relSrcCols, srcCols `allSrcColsOf` relColumns ] | srcCols <- relSrcCols, srcCols `allSrcColsOf` relColumns ]
tableViewChild = tableViewM2O =
[ Relation relTable relColumns [ Relation relTable relColumns
(getView fSrcCols) (snd <$> fSrcCols `sortAccordingTo` relFColumns) (getView fSrcCols) (snd <$> fSrcCols `sortAccordingTo` relFColumns)
Child Nothing Nothing Nothing M2O Nothing Nothing Nothing
| fSrcCols <- relFSrcCols, fSrcCols `allSrcColsOf` relFColumns ] | fSrcCols <- relFSrcCols, fSrcCols `allSrcColsOf` relFColumns ]
viewViewChild = viewViewM2O =
[ Relation (getView srcCols) (snd <$> srcCols `sortAccordingTo` relColumns) [ Relation (getView srcCols) (snd <$> srcCols `sortAccordingTo` relColumns)
(getView fSrcCols) (snd <$> fSrcCols `sortAccordingTo` relFColumns) (getView fSrcCols) (snd <$> fSrcCols `sortAccordingTo` relFColumns)
Child Nothing Nothing Nothing M2O Nothing Nothing Nothing
| srcCols <- relSrcCols, srcCols `allSrcColsOf` relColumns | srcCols <- relSrcCols, srcCols `allSrcColsOf` relColumns
, fSrcCols <- relFSrcCols, fSrcCols `allSrcColsOf` relFColumns ] , fSrcCols <- relFSrcCols, fSrcCols `allSrcColsOf` relFColumns ]
in viewTableChild ++ tableViewChild ++ viewViewChild in viewTableM2O ++ tableViewM2O ++ viewViewM2O
_ -> []) _ -> [])
addParentRelations :: [Relation] -> [Relation] addO2MRels :: [Relation] -> [Relation]
addParentRelations = concatMap (\rel@(Relation t c ft fc _ _ _ _) -> [rel, Relation ft fc t c Parent Nothing Nothing Nothing]) addO2MRels = concatMap (\rel@(Relation t c ft fc _ _ _ _) -> [rel, Relation ft fc t c O2M Nothing Nothing Nothing])
addManyToManyRelations :: [Relation] -> [Relation] addM2MRels :: [Relation] -> [Relation]
addManyToManyRelations rels = rels ++ addMirrorRelation (mapMaybe link2Relation links) addM2MRels rels = rels ++ addMirrorRelation (mapMaybe link2Relation links)
where where
links = join $ map (combinations 2) $ filter (not . null) $ groupWith groupFn $ filter ( (==Child). relType) rels links = join $ map (combinations 2) $ filter (not . null) $ groupWith groupFn $ filter ( (==M2O). relType) rels
groupFn :: Relation -> Text groupFn :: Relation -> Text
groupFn Relation{relTable=Table{tableSchema=s, tableName=t}} = s <> "_" <> t groupFn Relation{relTable=Table{tableSchema=s, tableName=t}} = s <> "_" <> t
-- Reference : https://wiki.haskell.org/99_questions/Solutions/26 -- Reference : https://wiki.haskell.org/99_questions/Solutions/26
@@ -342,12 +342,12 @@ addManyToManyRelations rels = rels ++ addMirrorRelation (mapMaybe link2Relation
combinations 0 _ = [ [] ] combinations 0 _ = [ [] ]
combinations n xs = [ y:ys | y:xs' <- tails xs combinations n xs = [ y:ys | y:xs' <- tails xs
, ys <- combinations (n-1) xs'] , ys <- combinations (n-1) xs']
addMirrorRelation = concatMap (\rel@(Relation t c ft fc _ lt lc1 lc2) -> [rel, Relation ft fc t c Many lt lc2 lc1]) addMirrorRelation = concatMap (\rel@(Relation t c ft fc _ lt lc1 lc2) -> [rel, Relation ft fc t c M2M lt lc2 lc1])
link2Relation [ link2Relation [
Relation{relTable=lt, relColumns=lc1, relFTable=t, relFColumns=c}, Relation{relTable=lt, relColumns=lc1, relFTable=t, relFColumns=c},
Relation{ relColumns=lc2, relFTable=ft, relFColumns=fc} Relation{ relColumns=lc2, relFTable=ft, relFColumns=fc}
] ]
| lc1 /= lc2 && length lc1 == 1 && length lc2 == 1 = Just $ Relation t c ft fc Many (Just lt) (Just lc1) (Just lc2) | lc1 /= lc2 && length lc1 == 1 && length lc2 == 1 = Just $ Relation t c ft fc M2M (Just lt) (Just lc1) (Just lc2)
| otherwise = Nothing | otherwise = Nothing
link2Relation _ = Nothing link2Relation _ = Nothing
@@ -567,9 +567,9 @@ columnFromRow tabs (s, t, n, desc, pos, nul, typ, u, l, p, d, e) = buildColumn <
parseEnum :: Maybe Text -> [Text] parseEnum :: Maybe Text -> [Text]
parseEnum = maybe [] (split (==',')) parseEnum = maybe [] (split (==','))
allChildRelations :: [Table] -> [Column] -> H.Statement () [Relation] allM2ORels :: [Table] -> [Column] -> H.Statement () [Relation]
allChildRelations tabs cols = allM2ORels tabs cols =
H.Statement sql HE.noParams (decodeRelations tabs cols) True H.Statement sql HE.noParams (decodeRels tabs cols) True
where where
sql = [q| sql = [q|
SELECT ns1.nspname AS table_schema, SELECT ns1.nspname AS table_schema,
@@ -597,9 +597,9 @@ allChildRelations tabs cols =
WHERE confrelid != 0 WHERE confrelid != 0
ORDER BY (conrelid, column_info.nums) |] ORDER BY (conrelid, column_info.nums) |]
relationFromRow :: [Table] -> [Column] -> (Text, Text, [Text], Text, Text, [Text]) -> Maybe Relation relFromRow :: [Table] -> [Column] -> (Text, Text, [Text], Text, Text, [Text]) -> Maybe Relation
relationFromRow allTabs allCols (rs, rt, rcs, frs, frt, frcs) = relFromRow allTabs allCols (rs, rt, rcs, frs, frt, frcs) =
Relation <$> table <*> cols <*> tableF <*> colsF <*> pure Child <*> pure Nothing <*> pure Nothing <*> pure Nothing Relation <$> table <*> cols <*> tableF <*> colsF <*> pure M2O <*> pure Nothing <*> pure Nothing <*> pure Nothing
where where
findTable s t = find (\tbl -> tableSchema tbl == s && tableName tbl == t) allTabs findTable s t = find (\tbl -> tableSchema tbl == s && tableName tbl == t) allTabs
findCol s t c = find (\col -> tableSchema (colTable col) == s && tableName (colTable col) == t && colName col == c) allCols findCol s t c = find (\col -> tableSchema (colTable col) == s && tableName (colTable col) == t && colName col == c) allCols
+1 -1
View File
@@ -107,7 +107,7 @@ compressedRel rel =
, "target" .= fmt (tableSchema fTab) (tableName fTab) (colName <$> relFColumns rel) , "target" .= fmt (tableSchema fTab) (tableName fTab) (colName <$> relFColumns rel)
, "cardinality" .= (show $ relType rel :: Text) , "cardinality" .= (show $ relType rel :: Text)
] ++ ] ++
if relType rel == Many if relType rel == M2M
then [ then [
"junction" .= case (relLinkTable rel, relLinkCols1 rel, relLinkCols2 rel) of "junction" .= case (relLinkTable rel, relLinkCols1 rel, relLinkCols2 rel) of
(Just lt, Just lc1, Just lc2) -> fmtMany (tableSchema lt) (tableName lt) (colName <$> lc1) (colName <$> lc2) (Just lt, Just lc1, Just lc2) -> fmtMany (tableSchema lt) (tableName lt) (colName <$> lc1) (colName <$> lc2)
+2 -8
View File
@@ -55,19 +55,13 @@ getJoinsSelects :: ReadRequest -> ([SqlFragment], [SqlFragment]) -> ([SqlFragmen
getJoinsSelects rr@(Node (_, (name, Just Relation{relType=relTyp,relTable=Table{tableName=table}}, alias, _, _)) _) (j,s) = getJoinsSelects rr@(Node (_, (name, Just Relation{relType=relTyp,relTable=Table{tableName=table}}, alias, _, _)) _) (j,s) =
let subquery = readRequestToQuery rr in let subquery = readRequestToQuery rr in
case relTyp of case relTyp of
Child -> M2O ->
let sel = "COALESCE(("
<> "SELECT json_agg(" <> pgFmtIdent table <> ".*) "
<> "FROM (" <> subquery <> ") " <> pgFmtIdent table
<> "), '[]') AS " <> pgFmtIdent (fromMaybe name alias) in
(j, sel:s)
Parent ->
let aliasOrName = fromMaybe name alias let aliasOrName = fromMaybe name alias
localTableName = pgFmtIdent $ table <> "_" <> aliasOrName localTableName = pgFmtIdent $ table <> "_" <> aliasOrName
sel = "row_to_json(" <> localTableName <> ".*) AS " <> pgFmtIdent aliasOrName sel = "row_to_json(" <> localTableName <> ".*) AS " <> pgFmtIdent aliasOrName
joi = " LEFT JOIN LATERAL( " <> subquery <> " ) AS " <> localTableName <> " ON TRUE " in joi = " LEFT JOIN LATERAL( " <> subquery <> " ) AS " <> localTableName <> " ON TRUE " in
(joi:j,sel:s) (joi:j,sel:s)
Many -> _ ->
let sel = "COALESCE ((" let sel = "COALESCE (("
<> "SELECT json_agg(" <> pgFmtIdent table <> ".*) " <> "SELECT json_agg(" <> pgFmtIdent table <> ".*) "
<> "FROM (" <> subquery <> ") " <> pgFmtIdent table <> "FROM (" <> subquery <> ") " <> pgFmtIdent table
+8 -4
View File
@@ -259,10 +259,14 @@ data QualifiedIdentifier = QualifiedIdentifier {
-- | The relationship [cardinality](https://en.wikipedia.org/wiki/Cardinality_(data_modeling)). -- | The relationship [cardinality](https://en.wikipedia.org/wiki/Cardinality_(data_modeling)).
-- | TODO: missing one-to-one -- | TODO: missing one-to-one
data Cardinality = Child -- ^ a.k.a. many-to-one data Cardinality = O2M -- ^ one-to-many, previously known as Parent
| Parent -- ^ a.k.a. one-to-many | M2O -- ^ many-to-one, previously known as Child
| Many -- ^ a.k.a. many-to-many | M2M -- ^ many-to-many, previously known as Many
deriving (Show, Eq) deriving Eq
instance Show Cardinality where
show O2M = "one-to-many"
show M2O = "many-to-one"
show M2M = "many-to-many"
{-| {-|
The name 'Relation' here is used with the meaning The name 'Relation' here is used with the meaning
+85 -85
View File
@@ -20,14 +20,14 @@ spec =
{ {
"details": [ "details": [
{ {
"cardinality": "Parent", "cardinality": "many-to-one",
"source": "test.person[id]", "source": "test.message[sender]",
"target": "test.message[sender]" "target": "test.person[id]"
}, },
{ {
"cardinality": "Parent", "cardinality": "many-to-one",
"source": "test.person_detail[id]", "source": "test.message[sender]",
"target": "test.message[sender]" "target": "test.person_detail[id]"
} }
], ],
"hint": "Disambiguate by choosing a relationship from the `details` key", "hint": "Disambiguate by choosing a relationship from the `details` key",
@@ -42,85 +42,85 @@ spec =
[json| [json|
{ {
"details": [ "details": [
{ {
"cardinality": "Child", "cardinality": "one-to-many",
"source": "test.articleStars[userId]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.articleStars[userId]"
}, },
{ {
"cardinality": "Child", "cardinality": "one-to-many",
"source": "test.limited_article_stars[user_id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.limited_article_stars[user_id]"
}, },
{ {
"cardinality": "Child", "cardinality": "one-to-many",
"source": "test.comments[commenter_id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.comments[commenter_id]"
}, },
{ {
"cardinality": "Child", "cardinality": "one-to-many",
"source": "test.users_projects[user_id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.users_projects[user_id]"
}, },
{ {
"cardinality": "Child", "cardinality": "one-to-many",
"source": "test.users_tasks[user_id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.users_tasks[user_id]"
}, },
{ {
"cardinality": "Many", "cardinality": "many-to-many",
"junction": "private.article_stars[article_id][user_id]", "junction": "private.article_stars[user_id][article_id]",
"source": "test.articles[id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.articles[id]"
}, },
{ {
"cardinality": "Many", "cardinality": "many-to-many",
"junction": "test.articleStars[articleId][userId]", "junction": "test.articleStars[userId][articleId]",
"source": "test.articles[id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.articles[id]"
}, },
{ {
"cardinality": "Many", "cardinality": "many-to-many",
"junction": "test.limited_article_stars[article_id][user_id]", "junction": "test.limited_article_stars[user_id][article_id]",
"source": "test.articles[id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.articles[id]"
}, },
{ {
"cardinality": "Many", "cardinality": "many-to-many",
"junction": "test.users_projects[project_id][user_id]", "junction": "test.users_projects[user_id][project_id]",
"source": "test.projects[id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.projects[id]"
}, },
{ {
"cardinality": "Many", "cardinality": "many-to-many",
"junction": "test.users_projects[project_id][user_id]", "junction": "test.users_projects[user_id][project_id]",
"source": "test.materialized_projects[id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.materialized_projects[id]"
}, },
{ {
"cardinality": "Many", "cardinality": "many-to-many",
"junction": "test.users_projects[project_id][user_id]", "junction": "test.users_projects[user_id][project_id]",
"source": "test.projects_view[id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.projects_view[id]"
}, },
{ {
"cardinality": "Many", "cardinality": "many-to-many",
"junction": "test.users_projects[project_id][user_id]", "junction": "test.users_projects[user_id][project_id]",
"source": "test.projects_view_alt[t_id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.projects_view_alt[t_id]"
}, },
{ {
"cardinality": "Many", "cardinality": "many-to-many",
"junction": "test.users_tasks[task_id][user_id]", "junction": "test.users_tasks[user_id][task_id]",
"source": "test.tasks[id]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.tasks[id]"
}, },
{ {
"cardinality": "Many", "cardinality": "many-to-many",
"junction": "test.users_tasks[task_id][user_id]", "junction": "test.users_tasks[user_id][task_id]",
"source": "test.filtered_tasks[myId]", "source": "test.users[id]",
"target": "test.users[id]" "target": "test.filtered_tasks[myId]"
} }
], ],
"hint": "Disambiguate by choosing a relationship from the `details` key", "hint": "Disambiguate by choosing a relationship from the `details` key",
"message": "More than one relationship was found for users and id" "message": "More than one relationship was found for users and id"