mirror of
https://code.sup39.dev/repos/Wqawg
synced 2024-12-29 00:24:51 +09:00
Reachability sets of graph nodes using SQL
This commit is contained in:
parent
87205772bb
commit
c340508385
1 changed files with 165 additions and 0 deletions
|
@ -51,6 +51,15 @@ module Database.Persist.Local.Sql
|
|||
, rmconnects
|
||||
, rconnectsm
|
||||
, rmconnectsm
|
||||
-- * Finding the nodes reachable from a given node or set of nodes
|
||||
-- $reachable
|
||||
-- ** Standard
|
||||
, reachable
|
||||
, xreachable
|
||||
-- ** Undirected
|
||||
, ureachable
|
||||
-- ** Reversed
|
||||
, rreachable
|
||||
-- * Finding paths
|
||||
-- $path
|
||||
-- ** Standard
|
||||
|
@ -793,6 +802,162 @@ rmconnectsm
|
|||
-> ReaderT SqlBackend m Bool
|
||||
rmconnectsm = xmconnectsm FollowBackward []
|
||||
|
||||
-- $reachable
|
||||
-- Finding the nodes reachable from a given set of starting nodes.
|
||||
--
|
||||
-- Names consist of:
|
||||
--
|
||||
-- 1. An optional direction parameter, specifying which nodes to visit next.
|
||||
--
|
||||
-- [(none)] forward: follow edge direction
|
||||
-- [@u@] undirectional: ignore edge direction
|
||||
-- [@r@] reversed: walk edges in reverse
|
||||
-- [@x@] user defined: specify which paths to follow
|
||||
--
|
||||
-- 2. Base name: @reachable@.
|
||||
|
||||
-- | It more-or-less looks like this:
|
||||
--
|
||||
-- > WITH RECURSIVE
|
||||
-- > temp (id, path, cycle) AS (
|
||||
-- > SELECT 3, ARRAY[3], FALSE
|
||||
-- > UNION ALL
|
||||
-- > SELECT edge.parent,
|
||||
-- > temp.path || edge.parent,
|
||||
-- > edge.parent = ANY(temp.path)
|
||||
-- > FROM edge INNER JOIN temp
|
||||
-- > ON edge.child = temp.id
|
||||
-- > WHERE NOT temp.cycle
|
||||
-- > )
|
||||
-- > SELECT DISTINCT id
|
||||
-- > FROM temp
|
||||
-- > WHERE NOT cycle
|
||||
xreachable'
|
||||
:: ( MonadIO m
|
||||
, PersistEntityGraph node edge
|
||||
, SqlBackend ~ PersistEntityBackend node
|
||||
, SqlBackend ~ PersistEntityBackend edge
|
||||
)
|
||||
=> FollowDirection
|
||||
-> [Filter edge]
|
||||
-> [Key node]
|
||||
-> Maybe Int -- filter on path length max
|
||||
-> Proxy (node, edge)
|
||||
-> ReaderT SqlBackend m [Key node]
|
||||
xreachable' follow filter initials mlen proxy = do
|
||||
conn <- ask
|
||||
let tNode = entityDef $ dummyFromFst proxy
|
||||
tEdge = entityDef $ dummyFromSnd proxy
|
||||
fwd = persistFieldDef $ destFieldFromProxy proxy
|
||||
bwd = persistFieldDef $ sourceFieldFromProxy proxy
|
||||
temp = DBName "temp_hierarchy_cte"
|
||||
tid = DBName "id"
|
||||
tpath = DBName "path"
|
||||
tcycle = DBName "cycle"
|
||||
dbname = connEscapeName conn
|
||||
t ^* f = dbname t <> "." <> dbname f
|
||||
t <#> s = dbname t <> " INNER JOIN " <> dbname s
|
||||
t <# s = dbname t <> " LEFT OUTER JOIN " <> dbname s
|
||||
|
||||
filt = filterClause False conn filter
|
||||
fvals = getFiltsValues conn filter
|
||||
sqlStep forward backward = mconcat
|
||||
[ "SELECT "
|
||||
, entityDB tEdge ^* fieldDB forward, ", "
|
||||
, temp ^* tpath, " || ", entityDB tEdge ^* fieldDB forward, ", "
|
||||
, entityDB tEdge ^* fieldDB forward, " = ANY(", temp ^* tpath, ")"
|
||||
, " FROM ", entityDB tEdge <#> temp
|
||||
, " ON ", entityDB tEdge ^* fieldDB backward, " = ", temp ^* tid
|
||||
, if T.null filt
|
||||
then " WHERE NOT " <> temp ^* tcycle
|
||||
else filt <> " AND NOT " <> temp ^* tcycle
|
||||
]
|
||||
|
||||
sql = mconcat
|
||||
[ "WITH RECURSIVE "
|
||||
, dbname temp
|
||||
, " ("
|
||||
, T.intercalate "," $ map dbname [tid, tpath, tcycle]
|
||||
, ") AS ( SELECT "
|
||||
, entityDB tNode ^* fieldDB (entityId tNode), ", "
|
||||
, "ARRAY[", entityDB tNode ^* fieldDB (entityId tNode), "], "
|
||||
, "FALSE"
|
||||
, " FROM ", dbname $ entityDB tNode
|
||||
, " WHERE ", entityDB tNode ^* fieldDB (entityId tNode)
|
||||
, " IN ?"
|
||||
, " UNION ALL "
|
||||
, case follow of
|
||||
FollowForward -> sqlStep fwd bwd
|
||||
FollowBackward -> sqlStep bwd fwd
|
||||
FollowBoth -> mconcat
|
||||
[ "("
|
||||
, sqlStep fwd bwd
|
||||
, " UNION ALL "
|
||||
, sqlStep bwd fwd
|
||||
, ")"
|
||||
]
|
||||
, " ) SELECT DISTINCT ", temp ^* tid
|
||||
, " FROM ", dbname temp
|
||||
, " WHERE NOT ", temp ^* tcycle
|
||||
, case mlen of
|
||||
Nothing -> ""
|
||||
Just _ -> " AND array_length(", temp ^* tpath, ", 1) <= ?"
|
||||
]
|
||||
toP = fmap toPersistValue
|
||||
toPL = PersistList . map toPersistValue
|
||||
vals = toPL initials : fvals ++ toP mlen ?: []
|
||||
rawSql sql vals
|
||||
|
||||
reachable
|
||||
:: ( MonadIO m
|
||||
, PersistEntityGraph node edge
|
||||
, SqlBackend ~ PersistEntityBackend node
|
||||
, SqlBackend ~ PersistEntityBackend edge
|
||||
)
|
||||
=> [Key node]
|
||||
-> Maybe Int
|
||||
-> Proxy (node, edge)
|
||||
-> ReaderT SqlBackend m [Key node]
|
||||
reachable = xreachable FollowForward []
|
||||
|
||||
xreachable
|
||||
:: ( MonadIO m
|
||||
, PersistEntityGraph node edge
|
||||
, SqlBackend ~ PersistEntityBackend node
|
||||
, SqlBackend ~ PersistEntityBackend edge
|
||||
)
|
||||
=> FollowDirection
|
||||
-> [Filter edge]
|
||||
-> [Key node]
|
||||
-> Maybe Int
|
||||
-> Proxy (node, edge)
|
||||
-> ReaderT SqlBackend m [Key node]
|
||||
xreachable = xreachable'
|
||||
|
||||
ureachable
|
||||
:: ( MonadIO m
|
||||
, PersistEntityGraph node edge
|
||||
, SqlBackend ~ PersistEntityBackend node
|
||||
, SqlBackend ~ PersistEntityBackend edge
|
||||
)
|
||||
=> [Key node]
|
||||
-> Maybe Int
|
||||
-> Proxy (node, edge)
|
||||
-> ReaderT SqlBackend m [Key node]
|
||||
ureachable = xreachable FollowBoth []
|
||||
|
||||
rreachable
|
||||
:: ( MonadIO m
|
||||
, PersistEntityGraph node edge
|
||||
, SqlBackend ~ PersistEntityBackend node
|
||||
, SqlBackend ~ PersistEntityBackend edge
|
||||
)
|
||||
=> [Key node]
|
||||
-> Maybe Int
|
||||
-> Proxy (node, edge)
|
||||
-> ReaderT SqlBackend m [Key node]
|
||||
rreachable = xreachable FollowBackward []
|
||||
|
||||
-- $path
|
||||
-- Findings paths between graph nodes.
|
||||
--
|
||||
|
|
Loading…
Reference in a new issue