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 (..))
import PostgREST.DbStructure.Relationship (Cardinality (..),
Junction (..),
Relationship (..))
Relationship (..),
RelationshipsMap)
import PostgREST.DbStructure.Table (Column (..), Table (..),
TablesMap)
@@ -60,7 +61,7 @@ import Protolude
data DbStructure = DbStructure
{ dbTables :: TablesMap
, dbRelationships :: [Relationship]
, dbRelationships :: RelationshipsMap
, dbProcs :: ProcsMap
}
deriving (Generic, JSON.ToJSON)
@@ -95,22 +96,24 @@ queryDbStructure schemas extraSearchPath prepared = do
procs <- SQL.statement schemas $ allProcs pgVer prepared
let tabsWViewsPks = addViewPrimaryKeys tabs keyDeps
rels = addO2MRels $ addM2MRels tabsWViewsPks $ addViewM2ORels keyDeps m2oRels
rels = relsToMap $ addO2MRels $ addM2MRels tabsWViewsPks $ addViewM2ORels keyDeps m2oRels
return $ removeInternal schemas $ DbStructure {
dbTables = tabsWViewsPks
, dbRelationships = rels
, 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.
removeInternal :: [Schema] -> DbStructure -> DbStructure
removeInternal schemas dbStruct =
DbStructure {
dbTables = M.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch `elem` schemas) $ dbTables dbStruct
, dbRelationships = filter (\x -> qiSchema (relTable x) `elem` schemas &&
qiSchema (relForeignTable x) `elem` schemas &&
not (hasInternalJunction x)) $ dbRelationships dbStruct
, dbRelationships = filter (\r -> qiSchema (relForeignTable r) `elem` schemas && not (hasInternalJunction r)) <$>
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
@@ -143,20 +146,13 @@ decodeTables =
decodeRels :: HD.Result [Relationship]
decodeRels =
map relFromRow <$> HD.rowList relRow
HD.rowList relRow
where
relRow = (,,,,,)
<$> column HD.text <*> column HD.text
<*> column HD.text <*> column HD.text
<*> column HD.text
<*> compositeArrayColumn
((,)
<$> 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)
relRow =
Relationship <$>
(QualifiedIdentifier <$> column HD.text <*> column HD.text) <*>
(QualifiedIdentifier <$> column HD.text <*> column HD.text) <*>
(M2O <$> column HD.text <*> compositeArrayColumn ((,) <$> compositeField HD.text <*> compositeField HD.text))
decodeViewKeyDeps :: HD.Result [ViewKeyDependency]
decodeViewKeyDeps =
+8 -4
View File
@@ -6,9 +6,11 @@ module PostgREST.DbStructure.Relationship
, Relationship(..)
, Junction(..)
, isSelfReference
, RelationshipsMap
) 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,
QualifiedIdentifier)
@@ -22,7 +24,7 @@ data Relationship = Relationship
, relForeignTable :: QualifiedIdentifier
, relCardinality :: Cardinality
}
deriving (Eq, Generic, JSON.ToJSON)
deriving (Eq, Ord, Generic, JSON.ToJSON)
-- | The relationship cardinality
-- | https://en.wikipedia.org/wiki/Cardinality_(data_modeling)
@@ -34,7 +36,7 @@ data Cardinality
-- ^ many-to-one
| M2M Junction
-- ^ many-to-many
deriving (Eq, Generic, JSON.ToJSON)
deriving (Eq, Ord, Generic, JSON.ToJSON)
type FKConstraint = Text
@@ -46,7 +48,9 @@ data Junction = Junction
, junColumns1 :: [(FieldName, FieldName)]
, junColumns2 :: [(FieldName, FieldName)]
}
deriving (Eq, Generic, JSON.ToJSON)
deriving (Eq, Ord, Generic, JSON.ToJSON)
isSelfReference :: Relationship -> Bool
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 (..),
ProcParam (..))
import PostgREST.DbStructure.Relationship (Cardinality (..),
Relationship (..))
Relationship (..),
RelationshipsMap)
import PostgREST.DbStructure.Table (Column (..), Table (..),
TablesMap)
import PostgREST.Version (docsVersion, prettyVersion)
@@ -79,7 +80,7 @@ parseDefault colType colDefault =
where
wrapInQuotations text = "\"" <> text <> "\""
makeTableDef :: [Relationship] -> Table -> (Text, Schema)
makeTableDef :: RelationshipsMap -> Table -> (Text, Schema)
makeTableDef rels t =
let tn = tableName t in
(tn, (mempty :: Schema)
@@ -88,7 +89,7 @@ makeTableDef rels t =
& properties .~ fromList (makeProperty t rels <$> 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)
where
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
Relationship{relCardinality=(M2O _ relColumns)} -> [colName col] == (fst <$> relColumns)
_ -> False
) rels
) $ fromMaybe mempty $ M.lookup (QualifiedIdentifier (tableSchema tbl) (tableName tbl)) rels
fCol = (headMay . (\r -> snd <$> relColumns (relCardinality r)) =<< rel)
fTbl = qiName . relForeignTable <$> rel
fTblCol = (,) <$> fTbl <*> fCol
@@ -320,7 +321,7 @@ escapeHostName "*6" = "0.0.0.0"
escapeHostName "!6" = "0.0.0.0"
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)
& basePath ?~ T.unpack b
& schemes ?~ [s']
+14 -19
View File
@@ -36,7 +36,8 @@ import PostgREST.DbStructure.Proc (ProcDescription (..),
procReturnsScalar)
import PostgREST.DbStructure.Relationship (Cardinality (..),
Junction (..),
Relationship (..))
Relationship (..),
RelationshipsMap)
import PostgREST.Error (Error (..))
import PostgREST.Query.SqlFragment (sourceCTEName)
import PostgREST.RangeQuery (NonnegRange, allRange,
@@ -58,7 +59,7 @@ import Protolude hiding (from)
-- | Builds the ReadRequest tree on a number of stages.
-- | Adds filters, order, limits on its respective nodes.
-- | 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 =
mapLeft ApiRequestError $
treeRestrictRange maxRows (iAction apiRequest) =<<
@@ -105,12 +106,12 @@ treeRestrictRange maxRows _ request = pure $ nodeRestrictRange maxRows <$> reque
nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode
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 =
addRels schema allRels Nothing request
>>= 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) =
case parentNode of
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
-- 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)
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 =
case rel of
[] -> Left $ NoRelBetween origin target schema
@@ -172,23 +173,17 @@ findRel schema allRels origin target hint =
_ -> False
rel = filter (
\Relationship{..} ->
-- Both relationship ends need to be on the exposed schema
schema == qiSchema relTable && schema == qiSchema relForeignTable &&
-- foreign relationship need to be on the exposed schema
schema == qiSchema relForeignTable &&
(
-- /projects?select=clients(*)
origin == qiName relTable && -- projects
target == qiName relForeignTable || -- clients
target == qiName relForeignTable -- clients
||
-- /projects?select=projects_client_id_fkey(*)
(
origin == qiName relTable && -- projects
matchConstraint (Just target) relCardinality -- projects_client_id_fkey
) ||
matchConstraint (Just target) relCardinality -- projects_client_id_fkey
||
-- /projects?select=client_id(*)
(
origin == qiName relTable && -- projects
matchFKSingleCol (Just target) relCardinality -- client_id
)
matchFKSingleCol (Just target) relCardinality -- client_id
) && (
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
matchJunction hint relCardinality -- users_tasks
)
) allRels
) $ fromMaybe mempty $ M.lookup (QualifiedIdentifier schema origin) allRels
-- previousAlias is only used for the case of self joins
addJoinConditions :: Maybe Alias -> ReadRequest -> Either ApiRequestError ReadRequest
+1 -1
View File
@@ -27,6 +27,6 @@ spec =
request methodGet "/"
[("Accept", "application/json")] "" `shouldRespondWith`
[json| {
"qiSchema":"test","qiName":"has_fk"
"qiSchema":"test","qiName":"bars"
} |]
{ matchHeaders = [matchContentTypeJson] }
@@ -98,18 +98,18 @@ spec =
[json|
{
"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",
"relationship": "departments_head_id_fkey using agents(id) and departments(head_id)",
"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'",
"code": "PGRST201"
}
+1 -1
View File
@@ -1843,7 +1843,7 @@ case accept
when 'application/openapi+json' then
return openapi;
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
return openapi;
end case;