diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 8ecbdd28e..c00076507 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -63,8 +63,7 @@ import PostgREST.Config (AppConfig (..), import PostgREST.Config.PgVersion (PgVersion (..)) import PostgREST.ContentType (ContentType (..)) import PostgREST.DbStructure (DbStructure (..), - findIfView, findTable, - tablePKCols) + findIfView, tablePKCols) import PostgREST.DbStructure.Identifiers (FieldName, QualifiedIdentifier (..), Schema) @@ -417,7 +416,7 @@ handleDelete identifier context@(RequestContext _ ctxDbStructure ApiRequest{..} handleInfo :: Monad m => QualifiedIdentifier -> RequestContext -> Handler m Wai.Response handleInfo identifier RequestContext{..} = - case findTable identifier $ dbTables ctxDbStructure of + case M.lookup identifier $ dbTables ctxDbStructure of Just table -> return $ Wai.responseLBS HTTP.status200 [allOrigins, allowH table] mempty Nothing -> diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index e370619ca..215d9fea1 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -24,7 +24,6 @@ module PostgREST.DbStructure , accessibleTables , accessibleProcs , findIfView - , findTable , schemaDescription , tableCols , tablePKCols @@ -57,7 +56,8 @@ import PostgREST.DbStructure.Relationship (Cardinality (..), Junction (..), PrimaryKey (..), Relationship (..)) -import PostgREST.DbStructure.Table (Column (..), Table (..), TablesMap) +import PostgREST.DbStructure.Table (Column (..), Table (..), + TablesMap) import Protolude import Protolude.Unsafe (unsafeHead) @@ -80,11 +80,8 @@ tableCols dbs tSchema tName = filter (\Column{colTable=Table{tableSchema=s, tabl tablePKCols :: DbStructure -> Schema -> TableName -> [Text] tablePKCols dbs tSchema tName = pkName <$> filter (\pk -> tSchema == (tableSchema . pkTable) pk && tName == (tableName . pkTable) pk) (dbPrimaryKeys dbs) -findTable :: QualifiedIdentifier -> TablesMap -> Maybe Table -findTable identifier tbls = M.lookup identifier tbls - findIfView :: QualifiedIdentifier -> TablesMap -> Bool -findIfView identifier tbls = maybe False tableIsView $ findTable identifier tbls +findIfView identifier tbls = maybe False tableIsView $ M.lookup identifier tbls -- | The source table column a view column refers to type SourceColumn = (Column, ViewColumn) @@ -100,7 +97,7 @@ queryDbStructure schemas extraSearchPath prepared = do tabs <- SQL.statement mempty $ allTables pgVer prepared cols <- SQL.statement schemas $ allColumns tabs prepared srcCols <- SQL.statement (schemas, extraSearchPath) $ pfkSourceColumns cols prepared - m2oRels <- SQL.statement mempty $ allM2ORels tabs cols prepared + m2oRels <- SQL.statement mempty $ allM2ORels cols prepared keys <- SQL.statement mempty $ allPrimaryKeys tabs prepared procs <- SQL.statement schemas $ allProcs pgVer prepared @@ -121,15 +118,15 @@ removeInternal schemas dbStruct = DbStructure { dbTables = M.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas) $ dbTables dbStruct , dbColumns = filter (\x -> tableSchema (colTable x) `elem` schemas) (dbColumns dbStruct) - , dbRelationships = filter (\x -> tableSchema (relTable x) `elem` schemas && - tableSchema (relForeignTable x) `elem` schemas && + , dbRelationships = filter (\x -> qiSchema (relTable x) `elem` schemas && + qiSchema (relForeignTable x) `elem` schemas && not (hasInternalJunction x)) $ dbRelationships dbStruct , dbPrimaryKeys = filter (\x -> tableSchema (pkTable x) `elem` schemas) $ dbPrimaryKeys dbStruct , dbProcs = dbProcs dbStruct -- procs are only obtained from the exposed schemas, no need to filter them. } where hasInternalJunction rel = case relCardinality rel of - M2M Junction{junTable} -> tableSchema junTable `notElem` schemas + M2M Junction{junTable} -> qiSchema junTable `notElem` schemas _ -> False decodeTables :: HD.Result TablesMap @@ -160,9 +157,9 @@ decodeColumns tables = <*> nullableColumn HD.text <*> nullableColumn HD.text -decodeRels :: TablesMap -> [Column] -> HD.Result [Relationship] -decodeRels tables cols = - mapMaybe (relFromRow tables cols) <$> HD.rowList relRow +decodeRels :: [Column] -> HD.Result [Relationship] +decodeRels cols = + mapMaybe (relFromRow cols) <$> HD.rowList relRow where relRow = (,,,,,,) <$> column HD.text @@ -378,8 +375,8 @@ addViewM2ORels allSrcCols = concatMap (\rel@Relationship{..} -> rel : filter (\(c, _) -> c `elem` relCols) allSrcCols relSrcCols = srcColsGroupedByView relColumns relFSrcCols = srcColsGroupedByView relForeignColumns - getView :: [SourceColumn] -> Table - getView = colTable . snd . unsafeHead + getView :: [SourceColumn] -> QualifiedIdentifier + getView = (\t -> QualifiedIdentifier (tableSchema t) (tableName t)) . colTable . snd . unsafeHead srcCols `allSrcColsOf` cols = S.fromList (fst <$> srcCols) == S.fromList cols -- Relationship is dependent on the order of relColumns and relFColumns to get the join conditions right in the generated query. -- So we need to change the order of the SourceColumns to match the relColumns @@ -612,13 +609,13 @@ columnFromRow :: TablesMap -> columnFromRow tabs (s, t, n, desc, nul, typ, l, d, e) = buildColumn <$> table where buildColumn tbl = Column tbl n desc nul typ l d (parseEnum e) - table = findTable (QualifiedIdentifier s t) tabs + table = M.lookup (QualifiedIdentifier s t) tabs parseEnum :: Maybe Text -> [Text] parseEnum = maybe [] (split (==',')) -allM2ORels :: TablesMap -> [Column] -> Bool -> SQL.Statement () [Relationship] -allM2ORels tabs cols = - SQL.Statement sql HE.noParams (decodeRels tabs cols) +allM2ORels :: [Column] -> Bool -> SQL.Statement () [Relationship] +allM2ORels allCols = + SQL.Statement sql HE.noParams (decodeRels allCols) where sql = [q| SELECT ns1.nspname AS table_schema, @@ -643,13 +640,11 @@ allM2ORels tabs cols = WHERE confrelid != 0 ORDER BY (conrelid, column_info.nums) |] -relFromRow :: TablesMap -> [Column] -> (Text, Text, Text, [Text], Text, Text, [Text]) -> Maybe Relationship -relFromRow allTabs allCols (rs, rt, cn, rcs, frs, frt, frcs) = - Relationship <$> table <*> cols <*> tableF <*> colsF <*> pure (M2O cn) +relFromRow :: [Column] -> (Text, Text, Text, [Text], Text, Text, [Text]) -> Maybe Relationship +relFromRow allCols (rs, rt, cn, rcs, frs, frt, frcs) = + Relationship (QualifiedIdentifier rs rt) <$> cols <*> pure (QualifiedIdentifier frs frt) <*> colsF <*> pure (M2O cn) where findCol s t c = find (\col -> tableSchema (colTable col) == s && tableName (colTable col) == t && colName col == c) allCols - table = findTable (QualifiedIdentifier rs rt) allTabs - tableF = findTable (QualifiedIdentifier frs frt) allTabs cols = mapM (findCol rs rt) rcs colsF = mapM (findCol frs frt) frcs @@ -731,7 +726,7 @@ allPrimaryKeys tabs = pkFromRow :: TablesMap -> (Schema, Text, Text) -> Maybe PrimaryKey pkFromRow tabs (s, t, n) = PrimaryKey <$> table <*> pure n - where table = findTable (QualifiedIdentifier s t) tabs + where table = M.lookup (QualifiedIdentifier s t) tabs -- returns all the primary and foreign key columns which are referenced in views pfkSourceColumns :: [Column] -> Bool -> SQL.Statement ([Schema], [Schema]) [SourceColumn] diff --git a/src/PostgREST/DbStructure/Relationship.hs b/src/PostgREST/DbStructure/Relationship.hs index f765bb791..000f018bb 100644 --- a/src/PostgREST/DbStructure/Relationship.hs +++ b/src/PostgREST/DbStructure/Relationship.hs @@ -11,7 +11,8 @@ module PostgREST.DbStructure.Relationship import qualified Data.Aeson as JSON -import PostgREST.DbStructure.Table (Column (..), Table (..)) +import PostgREST.DbStructure.Identifiers (QualifiedIdentifier) +import PostgREST.DbStructure.Table (Column (..), Table) import Protolude @@ -23,9 +24,9 @@ import Protolude -- -- TODO merge relColumns and relForeignColumns to a tuple or Data.Bimap data Relationship = Relationship - { relTable :: Table + { relTable :: QualifiedIdentifier , relColumns :: [Column] - , relForeignTable :: Table + , relForeignTable :: QualifiedIdentifier , relForeignColumns :: [Column] , relCardinality :: Cardinality } @@ -44,7 +45,7 @@ type FKConstraint = Text -- | Junction table on an M2M relationship data Junction = Junction - { junTable :: Table + { junTable :: QualifiedIdentifier , junConstraint1 :: FKConstraint , junColumns1 :: [Column] , junConstraint2 :: FKConstraint diff --git a/src/PostgREST/DbStructure/Table.hs b/src/PostgREST/DbStructure/Table.hs index 68b4e91d1..9f48a1f91 100644 --- a/src/PostgREST/DbStructure/Table.hs +++ b/src/PostgREST/DbStructure/Table.hs @@ -4,11 +4,10 @@ module PostgREST.DbStructure.Table ( Column(..) , Table(..) - , tableQi , TablesMap ) where -import qualified Data.Aeson as JSON +import qualified Data.Aeson as JSON import qualified Data.HashMap.Strict as M import PostgREST.DbStructure.Identifiers (FieldName, @@ -34,9 +33,6 @@ data Table = Table instance Eq Table where Table{tableSchema=s1,tableName=n1} == Table{tableSchema=s2,tableName=n2} = s1 == s2 && n1 == n2 -tableQi :: Table -> QualifiedIdentifier -tableQi Table{tableSchema=s, tableName=n} = QualifiedIdentifier s n - data Column = Column { colTable :: Table , colName :: FieldName diff --git a/src/PostgREST/Error.hs b/src/PostgREST/Error.hs index 122c11d66..2847a1e90 100644 --- a/src/PostgREST/Error.hs +++ b/src/PostgREST/Error.hs @@ -34,12 +34,13 @@ import qualified PostgREST.ContentType as ContentType import PostgREST.Request.Types (ApiRequestError (..), QPError (..)) +import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..)) import PostgREST.DbStructure.Proc (ProcDescription (..), ProcParam (..)) import PostgREST.DbStructure.Relationship (Cardinality (..), Junction (..), Relationship (..)) -import PostgREST.DbStructure.Table (Column (..), Table (..)) +import PostgREST.DbStructure.Table (Column (..)) import Protolude @@ -153,28 +154,28 @@ compressedRel Relationship{..} = fmtEls els = "(" <> T.intercalate ", " els <> ")" in JSON.object $ - ("embedding" .= (tableName relTable <> " with " <> tableName relForeignTable :: Text)) + ("embedding" .= (qiName relTable <> " with " <> qiName relForeignTable :: Text)) : case relCardinality of M2M Junction{..} -> [ "cardinality" .= ("many-to-many" :: Text) - , "relationship" .= (tableName junTable <> " using " <> junConstraint1 <> fmtEls (colName <$> junColumns1) <> " and " <> junConstraint2 <> fmtEls (colName <$> junColumns2)) + , "relationship" .= (qiName junTable <> " using " <> junConstraint1 <> fmtEls (colName <$> junColumns1) <> " and " <> junConstraint2 <> fmtEls (colName <$> junColumns2)) ] M2O cons -> [ "cardinality" .= ("many-to-one" :: Text) - , "relationship" .= (cons <> " using " <> tableName relTable <> fmtEls (colName <$> relColumns) <> " and " <> tableName relForeignTable <> fmtEls (colName <$> relForeignColumns)) + , "relationship" .= (cons <> " using " <> qiName relTable <> fmtEls (colName <$> relColumns) <> " and " <> qiName relForeignTable <> fmtEls (colName <$> relForeignColumns)) ] O2M cons -> [ "cardinality" .= ("one-to-many" :: Text) - , "relationship" .= (cons <> " using " <> tableName relTable <> fmtEls (colName <$> relColumns) <> " and " <> tableName relForeignTable <> fmtEls (colName <$> relForeignColumns)) + , "relationship" .= (cons <> " using " <> qiName relTable <> fmtEls (colName <$> relColumns) <> " and " <> qiName relForeignTable <> fmtEls (colName <$> relForeignColumns)) ] relHint :: [Relationship] -> Text relHint rels = T.intercalate ", " (hintList <$> rels) where hintList Relationship{..} = - let buildHint rel = "'" <> tableName relForeignTable <> "!" <> rel <> "'" in + let buildHint rel = "'" <> qiName relForeignTable <> "!" <> rel <> "'" in case relCardinality of - M2M Junction{..} -> buildHint (tableName junTable) + M2M Junction{..} -> buildHint (qiName junTable) M2O cons -> buildHint cons O2M cons -> buildHint cons diff --git a/src/PostgREST/OpenAPI.hs b/src/PostgREST/OpenAPI.hs index 16226fd21..7cd174819 100644 --- a/src/PostgREST/OpenAPI.hs +++ b/src/PostgREST/OpenAPI.hs @@ -29,12 +29,14 @@ import PostgREST.Config (AppConfig (..), Proxy (..), isMalformedProxyUri, toURI) import PostgREST.DbStructure (DbStructure (..), tableCols, tablePKCols) +import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..)) import PostgREST.DbStructure.Proc (ProcDescription (..), ProcParam (..)) import PostgREST.DbStructure.Relationship (Cardinality (..), PrimaryKey (..), Relationship (..)) -import PostgREST.DbStructure.Table (Column (..), Table (..), TablesMap) +import PostgREST.DbStructure.Table (Column (..), Table (..), + TablesMap) import PostgREST.Version (docsVersion, prettyVersion) import PostgREST.ContentType @@ -103,7 +105,7 @@ makeProperty rels pks c = (colName c, Inline s) _ -> False ) rels fCol = colName <$> (headMay . relForeignColumns =<< rel) - fTbl = tableName . relForeignTable <$> rel + fTbl = qiName . relForeignTable <$> rel fTblCol = (,) <$> fTbl <*> fCol in (\(a, b) -> T.intercalate "" ["This is a Foreign Key to `", a, ".", b, "`."]) <$> fTblCol diff --git a/src/PostgREST/Query/QueryBuilder.hs b/src/PostgREST/Query/QueryBuilder.hs index 2f1d2668d..cedd68b09 100644 --- a/src/PostgREST/Query/QueryBuilder.hs +++ b/src/PostgREST/Query/QueryBuilder.hs @@ -25,7 +25,6 @@ import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..)) import PostgREST.DbStructure.Proc (ProcParam (..)) import PostgREST.DbStructure.Relationship (Cardinality (..), Relationship (..)) -import PostgREST.DbStructure.Table (Table (..)) import PostgREST.Request.Preferences (PreferResolution (..)) import PostgREST.Query.SqlFragment @@ -51,7 +50,7 @@ readRequestToQuery (Node (Select colSelects mainQi tblAlias implJoins logicFores (joins, selects) = foldr getJoinsSelects ([],[]) forest getJoinsSelects :: ReadRequest -> ([SQL.Snippet], [SQL.Snippet]) -> ([SQL.Snippet], [SQL.Snippet]) -getJoinsSelects rr@(Node (_, (name, Just Relationship{relCardinality=card,relTable=Table{tableName=table}}, alias, _, joinType, _)) _) (joins,selects) = +getJoinsSelects rr@(Node (_, (name, Just Relationship{relCardinality=card,relTable=QualifiedIdentifier{qiName=table}}, alias, _, joinType, _)) _) (joins,selects) = let subquery = readRequestToQuery rr in case card of M2O _ -> diff --git a/src/PostgREST/Request/DbRequestBuilder.hs b/src/PostgREST/Request/DbRequestBuilder.hs index 44de9e999..3b5107d13 100644 --- a/src/PostgREST/Request/DbRequestBuilder.hs +++ b/src/PostgREST/Request/DbRequestBuilder.hs @@ -37,8 +37,7 @@ import PostgREST.DbStructure.Proc (ProcDescription (..), import PostgREST.DbStructure.Relationship (Cardinality (..), Junction (..), Relationship (..)) -import PostgREST.DbStructure.Table (Column (..), Table (..), - tableQi) +import PostgREST.DbStructure.Table (Column (..)) import PostgREST.Error (Error (..)) import PostgREST.Query.SqlFragment (sourceCTEName) import PostgREST.RangeQuery (NonnegRange, allRange, @@ -88,7 +87,7 @@ rootWithRels schema rootTableName allRels action = case action of -- findRel can find relationships with sourceCTEName. toSourceRel :: Relationship -> Maybe Relationship toSourceRel r@Relationship{relTable=t} - | rootTableName == tableName t = Just $ r {relTable=t {tableName=_sourceCTEName}} + | rootTableName == qiName t = Just $ r {relTable=t {qiName=_sourceCTEName}} | otherwise = Nothing -- Build the initial tree with a Depth attribute so when a self join occurs we @@ -131,7 +130,7 @@ addRels :: Schema -> [Relationship] -> Maybe ReadRequest -> ReadRequest -> Eithe addRels schema allRels parentNode (Node (query@Select{from=tbl}, (nodeName, _, alias, hint, joinType, depth)) forest) = case parentNode of Just (Node (Select{from=parentNodeQi}, _) _) -> - let newFrom r = if qiName tbl == nodeName then tableQi (relForeignTable r) else tbl + let newFrom r = if qiName tbl == nodeName then relForeignTable r else tbl newReadNode = (\r -> (query{from=newFrom r}, (nodeName, Just r, alias, hint, joinType, depth))) <$> rel rel = findRel schema allRels (qiName parentNodeQi) nodeName hint in @@ -175,25 +174,25 @@ findRel schema allRels origin target hint = M2O cons -> tar == Just cons _ -> False matchJunction hint_ card = case card of - M2M Junction{junTable} -> hint_ == Just (tableName junTable) + M2M Junction{junTable} -> hint_ == Just (qiName junTable) _ -> False rel = filter ( \Relationship{..} -> -- Both relationship ends need to be on the exposed schema - schema == tableSchema relTable && schema == tableSchema relForeignTable && + schema == qiSchema relTable && schema == qiSchema relForeignTable && ( -- /projects?select=clients(*) - origin == tableName relTable && -- projects - target == tableName relForeignTable || -- clients + origin == qiName relTable && -- projects + target == qiName relForeignTable || -- clients -- /projects?select=projects_client_id_fkey(*) ( - origin == tableName relTable && -- projects + origin == qiName relTable && -- projects matchConstraint (Just target) relCardinality -- projects_client_id_fkey ) || -- /projects?select=client_id(*) ( - origin == tableName relTable && -- projects + origin == qiName relTable && -- projects matchFKSingleCol (Just target) relColumns -- client_id ) ) && ( @@ -217,7 +216,7 @@ addJoinConditions previousAlias (Node node@(query@Select{from=tbl}, nodeProps@(_ case rel of Just r@Relationship{relCardinality=M2M Junction{junTable}} -> let rq = augmentQuery r in - Node (rq{implicitJoins=tableQi junTable:implicitJoins rq}, nodeProps) <$> updatedForest + Node (rq{implicitJoins=junTable:implicitJoins rq}, nodeProps) <$> updatedForest Just r -> Node (augmentQuery r, nodeProps) <$> updatedForest Nothing -> Node node <$> updatedForest where @@ -235,9 +234,9 @@ addJoinConditions previousAlias (Node node@(query@Select{from=tbl}, nodeProps@(_ -- previousAlias and newAlias are used in the case of self joins getJoinConditions :: Maybe Alias -> Maybe Alias -> Relationship -> [JoinCondition] -getJoinConditions previousAlias newAlias (Relationship Table{tableSchema=tSchema, tableName=tN} cols Table{tableName=ftN} fCols card) = +getJoinConditions previousAlias newAlias (Relationship QualifiedIdentifier{qiSchema=tSchema, qiName=tN} cols QualifiedIdentifier{qiName=ftN} fCols card) = case card of - M2M (Junction Table{tableName=jtn} _ jc1 _ jc2) -> + M2M (Junction QualifiedIdentifier{qiName=jtn} _ jc1 _ jc2) -> zipWith (toJoinCondition tN jtn) cols jc1 ++ zipWith (toJoinCondition ftN jtn) fCols jc2 _ -> zipWith (toJoinCondition tN ftN) cols fCols diff --git a/test/spec/Feature/OpenApi/RootSpec.hs b/test/spec/Feature/OpenApi/RootSpec.hs index 3baa3bc77..4f460cbd3 100644 --- a/test/spec/Feature/OpenApi/RootSpec.hs +++ b/test/spec/Feature/OpenApi/RootSpec.hs @@ -27,9 +27,6 @@ spec = request methodGet "/" [("Accept", "application/json")] "" `shouldRespondWith` [json| { - "tableName": "orders_view", "tableSchema": "test", - "tableDeletable": true, "tableUpdatable": true, - "tableIsView":true, "tableInsertable": true, - "tableDescription": null + "qiSchema":"test","qiName":"orders_view" } |] { matchHeaders = [matchContentTypeJson] }