perf: Relationship list to a hash map

This commit is contained in:
steve-chavez
2022-04-25 21:41:51 -05:00
committed by Steve Chavez
parent e4006da9bc
commit 115dae7484
7 changed files with 51 additions and 55 deletions
+15 -19
View File
@@ -51,7 +51,8 @@ import PostgREST.DbStructure.Proc (PgType (..),
ProcsMap, RetType (..)) ProcsMap, RetType (..))
import PostgREST.DbStructure.Relationship (Cardinality (..), import PostgREST.DbStructure.Relationship (Cardinality (..),
Junction (..), Junction (..),
Relationship (..)) Relationship (..),
RelationshipsMap)
import PostgREST.DbStructure.Table (Column (..), Table (..), import PostgREST.DbStructure.Table (Column (..), Table (..),
TablesMap) TablesMap)
@@ -60,7 +61,7 @@ import Protolude
data DbStructure = DbStructure data DbStructure = DbStructure
{ dbTables :: TablesMap { dbTables :: TablesMap
, dbRelationships :: [Relationship] , dbRelationships :: RelationshipsMap
, dbProcs :: ProcsMap , dbProcs :: ProcsMap
} }
deriving (Generic, JSON.ToJSON) deriving (Generic, JSON.ToJSON)
@@ -95,22 +96,24 @@ queryDbStructure schemas extraSearchPath prepared = do
procs <- SQL.statement schemas $ allProcs pgVer prepared procs <- SQL.statement schemas $ allProcs pgVer prepared
let tabsWViewsPks = addViewPrimaryKeys tabs keyDeps let tabsWViewsPks = addViewPrimaryKeys tabs keyDeps
rels = addO2MRels $ addM2MRels tabsWViewsPks $ addViewM2ORels keyDeps m2oRels rels = relsToMap $ addO2MRels $ addM2MRels tabsWViewsPks $ addViewM2ORels keyDeps m2oRels
return $ removeInternal schemas $ DbStructure { return $ removeInternal schemas $ DbStructure {
dbTables = tabsWViewsPks dbTables = tabsWViewsPks
, dbRelationships = rels , dbRelationships = rels
, dbProcs = procs , dbProcs = procs
} }
where
relsToMap = map sort . M.fromListWith (++) . map ((\(x,y) -> (x, [y])) . addKey)
addKey rel = (relTable rel, rel)
-- | Remove db objects that belong to an internal schema(not exposed through the API) from the DbStructure. -- | Remove db objects that belong to an internal schema(not exposed through the API) from the DbStructure.
removeInternal :: [Schema] -> DbStructure -> DbStructure removeInternal :: [Schema] -> DbStructure -> DbStructure
removeInternal schemas dbStruct = removeInternal schemas dbStruct =
DbStructure { DbStructure {
dbTables = M.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas) $ dbTables dbStruct dbTables = M.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas) $ dbTables dbStruct
, dbRelationships = filter (\x -> qiSchema (relTable x) `elem` schemas && , dbRelationships = filter (\r -> qiSchema (relForeignTable r) `elem` schemas && not (hasInternalJunction r)) <$>
qiSchema (relForeignTable x) `elem` schemas && M.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas ) (dbRelationships dbStruct)
not (hasInternalJunction x)) $ dbRelationships dbStruct
, dbProcs = dbProcs dbStruct -- procs are only obtained from the exposed schemas, no need to filter them. , dbProcs = dbProcs dbStruct -- procs are only obtained from the exposed schemas, no need to filter them.
} }
where where
@@ -143,20 +146,13 @@ decodeTables =
decodeRels :: HD.Result [Relationship] decodeRels :: HD.Result [Relationship]
decodeRels = decodeRels =
map relFromRow <$> HD.rowList relRow HD.rowList relRow
where where
relRow = (,,,,,) relRow =
<$> column HD.text <*> column HD.text Relationship <$>
<*> column HD.text <*> column HD.text (QualifiedIdentifier <$> column HD.text <*> column HD.text) <*>
<*> column HD.text (QualifiedIdentifier <$> column HD.text <*> column HD.text) <*>
<*> compositeArrayColumn (M2O <$> column HD.text <*> compositeArrayColumn ((,) <$> compositeField HD.text <*> compositeField HD.text))
((,)
<$> compositeField HD.text
<*> compositeField HD.text)
relFromRow :: (Text, Text, Text, Text, Text, [(Text, Text)]) -> Relationship
relFromRow (rs, rt, frs, frt, cn, cs) =
Relationship (QualifiedIdentifier rs rt) (QualifiedIdentifier frs frt) (M2O cn cs)
decodeViewKeyDeps :: HD.Result [ViewKeyDependency] decodeViewKeyDeps :: HD.Result [ViewKeyDependency]
decodeViewKeyDeps = decodeViewKeyDeps =
+8 -4
View File
@@ -6,9 +6,11 @@ module PostgREST.DbStructure.Relationship
, Relationship(..) , Relationship(..)
, Junction(..) , Junction(..)
, isSelfReference , isSelfReference
, RelationshipsMap
) where ) 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, import PostgREST.DbStructure.Identifiers (FieldName,
QualifiedIdentifier) QualifiedIdentifier)
@@ -22,7 +24,7 @@ data Relationship = Relationship
, relForeignTable :: QualifiedIdentifier , relForeignTable :: QualifiedIdentifier
, relCardinality :: Cardinality , relCardinality :: Cardinality
} }
deriving (Eq, Generic, JSON.ToJSON) deriving (Eq, Ord, Generic, JSON.ToJSON)
-- | The relationship cardinality -- | The relationship cardinality
-- | https://en.wikipedia.org/wiki/Cardinality_(data_modeling) -- | https://en.wikipedia.org/wiki/Cardinality_(data_modeling)
@@ -34,7 +36,7 @@ data Cardinality
-- ^ many-to-one -- ^ many-to-one
| M2M Junction | M2M Junction
-- ^ many-to-many -- ^ many-to-many
deriving (Eq, Generic, JSON.ToJSON) deriving (Eq, Ord, Generic, JSON.ToJSON)
type FKConstraint = Text type FKConstraint = Text
@@ -46,7 +48,9 @@ data Junction = Junction
, junColumns1 :: [(FieldName, FieldName)] , junColumns1 :: [(FieldName, FieldName)]
, junColumns2 :: [(FieldName, FieldName)] , junColumns2 :: [(FieldName, FieldName)]
} }
deriving (Eq, Generic, JSON.ToJSON) deriving (Eq, Ord, Generic, JSON.ToJSON)
isSelfReference :: Relationship -> Bool isSelfReference :: Relationship -> Bool
isSelfReference r = relTable r == relForeignTable r isSelfReference r = relTable r == relForeignTable r
type RelationshipsMap = M.HashMap QualifiedIdentifier [Relationship]
+6 -5
View File
@@ -31,7 +31,8 @@ import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..))
import PostgREST.DbStructure.Proc (ProcDescription (..), import PostgREST.DbStructure.Proc (ProcDescription (..),
ProcParam (..)) ProcParam (..))
import PostgREST.DbStructure.Relationship (Cardinality (..), import PostgREST.DbStructure.Relationship (Cardinality (..),
Relationship (..)) Relationship (..),
RelationshipsMap)
import PostgREST.DbStructure.Table (Column (..), Table (..), import PostgREST.DbStructure.Table (Column (..), Table (..),
TablesMap) TablesMap)
import PostgREST.Version (docsVersion, prettyVersion) import PostgREST.Version (docsVersion, prettyVersion)
@@ -79,7 +80,7 @@ parseDefault colType colDefault =
where where
wrapInQuotations text = "\"" <> text <> "\"" wrapInQuotations text = "\"" <> text <> "\""
makeTableDef :: [Relationship] -> Table -> (Text, Schema) makeTableDef :: RelationshipsMap -> Table -> (Text, Schema)
makeTableDef rels t = makeTableDef rels t =
let tn = tableName t in let tn = tableName t in
(tn, (mempty :: Schema) (tn, (mempty :: Schema)
@@ -88,7 +89,7 @@ makeTableDef rels t =
& properties .~ fromList (makeProperty t rels <$> tableColumns t) & properties .~ fromList (makeProperty t rels <$> tableColumns t)
& required .~ fmap colName (filter (not . colNullable) $ tableColumns t)) & required .~ fmap colName (filter (not . colNullable) $ tableColumns t))
makeProperty :: Table -> [Relationship] -> Column -> (Text, Referenced Schema) makeProperty :: Table -> RelationshipsMap -> Column -> (Text, Referenced Schema)
makeProperty tbl rels col = (colName col, Inline s) makeProperty tbl rels col = (colName col, Inline s)
where where
e = if null $ colEnum col then Nothing else JSON.decode $ JSON.encode $ colEnum col e = if null $ colEnum col then Nothing else JSON.decode $ JSON.encode $ colEnum col
@@ -99,7 +100,7 @@ makeProperty tbl rels col = (colName col, Inline s)
rel = find (\case rel = find (\case
Relationship{relCardinality=(M2O _ relColumns)} -> [colName col] == (fst <$> relColumns) Relationship{relCardinality=(M2O _ relColumns)} -> [colName col] == (fst <$> relColumns)
_ -> False _ -> False
) rels ) $ fromMaybe mempty $ M.lookup (QualifiedIdentifier (tableSchema tbl) (tableName tbl)) rels
fCol = (headMay . (\r -> snd <$> relColumns (relCardinality r)) =<< rel) fCol = (headMay . (\r -> snd <$> relColumns (relCardinality r)) =<< rel)
fTbl = qiName . relForeignTable <$> rel fTbl = qiName . relForeignTable <$> rel
fTblCol = (,) <$> fTbl <*> fCol fTblCol = (,) <$> fTbl <*> fCol
@@ -320,7 +321,7 @@ escapeHostName "*6" = "0.0.0.0"
escapeHostName "!6" = "0.0.0.0" escapeHostName "!6" = "0.0.0.0"
escapeHostName h = h escapeHostName h = h
postgrestSpec :: [Relationship] -> [ProcDescription] -> [Table] -> (Text, Text, Integer, Text) -> Maybe Text -> Swagger postgrestSpec :: RelationshipsMap -> [ProcDescription] -> [Table] -> (Text, Text, Integer, Text) -> Maybe Text -> Swagger
postgrestSpec rels pds ti (s, h, p, b) sd = (mempty :: Swagger) postgrestSpec rels pds ti (s, h, p, b) sd = (mempty :: Swagger)
& basePath ?~ T.unpack b & basePath ?~ T.unpack b
& schemes ?~ [s'] & schemes ?~ [s']
+14 -19
View File
@@ -36,7 +36,8 @@ import PostgREST.DbStructure.Proc (ProcDescription (..),
procReturnsScalar) procReturnsScalar)
import PostgREST.DbStructure.Relationship (Cardinality (..), import PostgREST.DbStructure.Relationship (Cardinality (..),
Junction (..), Junction (..),
Relationship (..)) Relationship (..),
RelationshipsMap)
import PostgREST.Error (Error (..)) import PostgREST.Error (Error (..))
import PostgREST.Query.SqlFragment (sourceCTEName) import PostgREST.Query.SqlFragment (sourceCTEName)
import PostgREST.RangeQuery (NonnegRange, allRange, import PostgREST.RangeQuery (NonnegRange, allRange,
@@ -58,7 +59,7 @@ import Protolude hiding (from)
-- | Builds the ReadRequest tree on a number of stages. -- | Builds the ReadRequest tree on a number of stages.
-- | Adds filters, order, limits on its respective nodes. -- | Adds filters, order, limits on its respective nodes.
-- | Adds joins conditions obtained from resource embedding. -- | Adds joins conditions obtained from resource embedding.
readRequest :: Schema -> TableName -> Maybe Integer -> [Relationship] -> ApiRequest -> Either Error ReadRequest readRequest :: Schema -> TableName -> Maybe Integer -> RelationshipsMap -> ApiRequest -> Either Error ReadRequest
readRequest schema rootTableName maxRows allRels apiRequest = readRequest schema rootTableName maxRows allRels apiRequest =
mapLeft ApiRequestError $ mapLeft ApiRequestError $
treeRestrictRange maxRows (iAction apiRequest) =<< treeRestrictRange maxRows (iAction apiRequest) =<<
@@ -105,12 +106,12 @@ treeRestrictRange maxRows _ request = pure $ nodeRestrictRange maxRows <$> reque
nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode
nodeRestrictRange m (q@Select {range_=r}, i) = (q{range_=restrictRange m r }, i) nodeRestrictRange m (q@Select {range_=r}, i) = (q{range_=restrictRange m r }, i)
augmentRequestWithJoin :: Schema -> [Relationship] -> ReadRequest -> Either ApiRequestError ReadRequest augmentRequestWithJoin :: Schema -> RelationshipsMap -> ReadRequest -> Either ApiRequestError ReadRequest
augmentRequestWithJoin schema allRels request = augmentRequestWithJoin schema allRels request =
addRels schema allRels Nothing request addRels schema allRels Nothing request
>>= addJoinConditions Nothing >>= addJoinConditions Nothing
addRels :: Schema -> [Relationship] -> Maybe ReadRequest -> ReadRequest -> Either ApiRequestError ReadRequest addRels :: Schema -> RelationshipsMap -> Maybe ReadRequest -> ReadRequest -> Either ApiRequestError ReadRequest
addRels schema allRels parentNode (Node (query@Select{from=tbl}, (nodeName, _, alias, hint, joinType, depth)) forest) = addRels schema allRels parentNode (Node (query@Select{from=tbl}, (nodeName, _, alias, hint, joinType, depth)) forest) =
case parentNode of case parentNode of
Just (Node (Select{from=parentNodeQi, fromAlias=aliasQi}, _) _) -> Just (Node (Select{from=parentNodeQi, fromAlias=aliasQi}, _) _) ->
@@ -139,7 +140,7 @@ addRels schema allRels parentNode (Node (query@Select{from=tbl}, (nodeName, _, a
-- target = table / view / constraint / column-from-origin -- target = table / view / constraint / column-from-origin
-- hint = table / view / constraint / column-from-origin / column-from-target -- hint = table / view / constraint / column-from-origin / column-from-target
-- (hint can take table / view values to aid in finding the junction in an m2m relationship) -- (hint can take table / view values to aid in finding the junction in an m2m relationship)
findRel :: Schema -> [Relationship] -> NodeName -> NodeName -> Maybe Hint -> Either ApiRequestError Relationship findRel :: Schema -> RelationshipsMap -> NodeName -> NodeName -> Maybe Hint -> Either ApiRequestError Relationship
findRel schema allRels origin target hint = findRel schema allRels origin target hint =
case rel of case rel of
[] -> Left $ NoRelBetween origin target schema [] -> Left $ NoRelBetween origin target schema
@@ -172,23 +173,17 @@ findRel schema allRels origin target hint =
_ -> False _ -> False
rel = filter ( rel = filter (
\Relationship{..} -> \Relationship{..} ->
-- Both relationship ends need to be on the exposed schema -- foreign relationship need to be on the exposed schema
schema == qiSchema relTable && schema == qiSchema relForeignTable && schema == qiSchema relForeignTable &&
( (
-- /projects?select=clients(*) -- /projects?select=clients(*)
origin == qiName relTable && -- projects target == qiName relForeignTable -- clients
target == qiName relForeignTable || -- clients ||
-- /projects?select=projects_client_id_fkey(*) -- /projects?select=projects_client_id_fkey(*)
( matchConstraint (Just target) relCardinality -- projects_client_id_fkey
origin == qiName relTable && -- projects ||
matchConstraint (Just target) relCardinality -- projects_client_id_fkey
) ||
-- /projects?select=client_id(*) -- /projects?select=client_id(*)
( matchFKSingleCol (Just target) relCardinality -- client_id
origin == qiName relTable && -- projects
matchFKSingleCol (Just target) relCardinality -- client_id
)
) && ( ) && (
isNothing hint || -- hint is optional isNothing hint || -- hint is optional
@@ -202,7 +197,7 @@ findRel schema allRels origin target hint =
-- /users?select=tasks!users_tasks(*) many-to-many between users and tasks -- /users?select=tasks!users_tasks(*) many-to-many between users and tasks
matchJunction hint relCardinality -- users_tasks matchJunction hint relCardinality -- users_tasks
) )
) allRels ) $ fromMaybe mempty $ M.lookup (QualifiedIdentifier schema origin) allRels
-- previousAlias is only used for the case of self joins -- previousAlias is only used for the case of self joins
addJoinConditions :: Maybe Alias -> ReadRequest -> Either ApiRequestError ReadRequest addJoinConditions :: Maybe Alias -> ReadRequest -> Either ApiRequestError ReadRequest
+1 -1
View File
@@ -27,6 +27,6 @@ spec =
request methodGet "/" request methodGet "/"
[("Accept", "application/json")] "" `shouldRespondWith` [("Accept", "application/json")] "" `shouldRespondWith`
[json| { [json| {
"qiSchema":"test","qiName":"has_fk" "qiSchema":"test","qiName":"bars"
} |] } |]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
@@ -98,18 +98,18 @@ spec =
[json| [json|
{ {
"details": [ "details": [
{
"cardinality": "many-to-one",
"relationship": "agents_department_id_fkey using agents(department_id) and departments(id)",
"embedding": "agents with departments"
},
{ {
"cardinality": "one-to-many", "cardinality": "one-to-many",
"relationship": "departments_head_id_fkey using agents(id) and departments(head_id)", "relationship": "departments_head_id_fkey using agents(id) and departments(head_id)",
"embedding": "agents with departments" "embedding": "agents with departments"
},
{
"cardinality": "many-to-one",
"relationship": "agents_department_id_fkey using agents(department_id) and departments(id)",
"embedding": "agents with departments"
} }
], ],
"hint": "Try changing 'departments' to one of the following: 'departments!agents_department_id_fkey', 'departments!departments_head_id_fkey'. Find the desired relationship in the 'details' key.", "hint": "Try changing 'departments' to one of the following: 'departments!departments_head_id_fkey', 'departments!agents_department_id_fkey'. Find the desired relationship in the 'details' key.",
"message": "Could not embed because more than one relationship was found for 'agents' and 'departments'", "message": "Could not embed because more than one relationship was found for 'agents' and 'departments'",
"code": "PGRST201" "code": "PGRST201"
} }
+1 -1
View File
@@ -1843,7 +1843,7 @@ case accept
when 'application/openapi+json' then when 'application/openapi+json' then
return openapi; return openapi;
when 'application/json' then when 'application/json' then
return (current_setting('request.spec', true)::json)->'dbRelationships'->0->'relTable'; return (current_setting('request.spec', true)::json)->'dbRelationships'->0->0;
else else
return openapi; return openapi;
end case; end case;