diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 5e5819418..98a2c43a0 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -100,8 +100,8 @@ queryDbStructure schemas extraSearchPath prepared = do , dbProcs = procs } where - relsToMap = map sort . M.fromListWith (++) . map ((\(x,y) -> (x, [y])) . addKey) - addKey rel = (relTable rel, rel) + relsToMap = map sort . M.fromListWith (++) . map ((\(x, fSch, y) -> ((x, fSch), [y])) . addKey) + addKey rel = (relTable rel, qiSchema $ relForeignTable rel, rel) -- | Remove db objects that belong to an internal schema(not exposed through the API) from the DbStructure. removeInternal :: [Schema] -> DbStructure -> DbStructure @@ -109,7 +109,7 @@ removeInternal schemas dbStruct = DbStructure { dbTables = M.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas) $ dbTables dbStruct , dbRelationships = filter (\r -> qiSchema (relForeignTable r) `elem` schemas && not (hasInternalJunction r)) <$> - M.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas ) (dbRelationships dbStruct) + M.filterWithKey (\(QualifiedIdentifier sch _, _) _ -> sch `elem` schemas ) (dbRelationships dbStruct) , dbProcs = dbProcs dbStruct -- procs are only obtained from the exposed schemas, no need to filter them. } where diff --git a/src/PostgREST/DbStructure/Relationship.hs b/src/PostgREST/DbStructure/Relationship.hs index 391cba85f..4a48b39be 100644 --- a/src/PostgREST/DbStructure/Relationship.hs +++ b/src/PostgREST/DbStructure/Relationship.hs @@ -13,7 +13,7 @@ import qualified Data.Aeson as JSON import qualified Data.HashMap.Strict as M import PostgREST.DbStructure.Identifiers (FieldName, - QualifiedIdentifier) + QualifiedIdentifier, Schema) import Protolude @@ -53,4 +53,5 @@ data Junction = Junction isSelfReference :: Relationship -> Bool isSelfReference r = relTable r == relForeignTable r -type RelationshipsMap = M.HashMap QualifiedIdentifier [Relationship] +-- | Key based on the source table and the foreign table schema +type RelationshipsMap = M.HashMap (QualifiedIdentifier, Schema) [Relationship] diff --git a/src/PostgREST/OpenAPI.hs b/src/PostgREST/OpenAPI.hs index 7a04fb95d..0a446adff 100644 --- a/src/PostgREST/OpenAPI.hs +++ b/src/PostgREST/OpenAPI.hs @@ -100,7 +100,7 @@ makeProperty tbl rels col = (colName col, Inline s) rel = find (\case Relationship{relCardinality=(M2O _ relColumns)} -> [colName col] == (fst <$> relColumns) _ -> False - ) $ fromMaybe mempty $ M.lookup (QualifiedIdentifier (tableSchema tbl) (tableName tbl)) rels + ) $ fromMaybe mempty $ M.lookup (QualifiedIdentifier (tableSchema tbl) (tableName tbl), tableSchema tbl) rels fCol = (headMay . (\r -> snd <$> relColumns (relCardinality r)) =<< rel) fTbl = qiName . relForeignTable <$> rel fTblCol = (,) <$> fTbl <*> fCol diff --git a/src/PostgREST/Request/DbRequestBuilder.hs b/src/PostgREST/Request/DbRequestBuilder.hs index 913735751..b65e09ddc 100644 --- a/src/PostgREST/Request/DbRequestBuilder.hs +++ b/src/PostgREST/Request/DbRequestBuilder.hs @@ -157,47 +157,54 @@ findRel schema allRels origin target hint = rs -> Left $ AmbiguousRelBetween origin target rs where matchFKSingleCol hint_ card = case card of - O2M _ cols -> length cols == 1 && hint_ == head (fst <$> cols) - M2O _ cols -> length cols == 1 && hint_ == head (fst <$> cols) - _ -> False + O2M _ [(col, _)] -> hint_ == col + M2O _ [(col, _)] -> hint_ == col + _ -> False matchFKRefSingleCol hint_ card = case card of - O2M _ cols -> length cols == 1 && hint_ == head (snd <$> cols) - M2O _ cols -> length cols == 1 && hint_ == head (snd <$> cols) - _ -> False + O2M _ [(_, fCol)] -> hint_ == fCol + M2O _ [(_, fCol)] -> hint_ == fCol + _ -> False matchConstraint tar card = case card of - O2M cons _ -> tar == Just cons - M2O cons _ -> tar == Just cons + O2M cons _ -> tar == cons + M2O cons _ -> tar == cons _ -> False matchJunction hint_ card = case card of - M2M Junction{junTable} -> hint_ == Just (qiName junTable) + M2M Junction{junTable} -> hint_ == qiName junTable _ -> False rel = filter ( \Relationship{..} -> - -- foreign relationship need to be on the exposed schema - schema == qiSchema relForeignTable && - ( - -- /projects?select=clients(*) - target == qiName relForeignTable -- clients - || - -- /projects?select=projects_client_id_fkey(*) - matchConstraint (Just target) relCardinality -- projects_client_id_fkey - || - -- /projects?select=client_id(*) - matchFKSingleCol (Just target) relCardinality -- client_id - ) && ( - isNothing hint || -- hint is optional + case hint of + Nothing -> + -- /projects?select=clients(*) + target == qiName relForeignTable -- clients + || + -- /projects?select=projects_client_id_fkey(*) + matchConstraint target relCardinality -- projects_client_id_fkey + || + -- /projects?select=client_id(*) + matchFKSingleCol target relCardinality -- client_id + Just hnt -> + ( + -- /projects?select=clients(*) + target == qiName relForeignTable -- clients + || + -- /projects?select=projects_client_id_fkey(*) + matchConstraint target relCardinality -- projects_client_id_fkey + || + -- /projects?select=client_id(*) + matchFKSingleCol target relCardinality -- client_id + ) && ( + -- /projects?select=clients!projects_client_id_fkey(*) + matchConstraint hnt relCardinality || -- projects_client_id_fkey - -- /projects?select=clients!projects_client_id_fkey(*) - matchConstraint hint relCardinality || -- projects_client_id_fkey + -- /projects?select=clients!client_id(*) or /projects?select=clients!id(*) + matchFKSingleCol hnt relCardinality || -- client_id + matchFKRefSingleCol hnt relCardinality || -- id - -- /projects?select=clients!client_id(*) or /projects?select=clients!id(*) - matchFKSingleCol hint relCardinality || -- client_id - matchFKRefSingleCol hint relCardinality || -- id - - -- /users?select=tasks!users_tasks(*) many-to-many between users and tasks - matchJunction hint relCardinality -- users_tasks - ) - ) $ fromMaybe mempty $ M.lookup (QualifiedIdentifier schema origin) allRels + -- /users?select=tasks!users_tasks(*) many-to-many between users and tasks + matchJunction hnt relCardinality -- users_tasks + ) + ) $ fromMaybe mempty $ M.lookup (QualifiedIdentifier schema origin, schema) allRels -- previousAlias is only used for the case of self joins addJoinConditions :: Maybe Alias -> ReadRequest -> Either ApiRequestError ReadRequest diff --git a/test/spec/Feature/OpenApi/RootSpec.hs b/test/spec/Feature/OpenApi/RootSpec.hs index e5b8d3f96..420a3e6a4 100644 --- a/test/spec/Feature/OpenApi/RootSpec.hs +++ b/test/spec/Feature/OpenApi/RootSpec.hs @@ -26,7 +26,7 @@ spec = it "accepts application/json" $ request methodGet "/" [("Accept", "application/json")] "" `shouldRespondWith` - [json| { - "qiSchema":"test","qiName":"bars" - } |] + [json| + [{"qiSchema":"test","qiName":"authors_w_entities"},"test"] + |] { matchHeaders = [matchContentTypeJson] }