1
1
import networkx as nx
2
2
import itertools
3
+ import re
3
4
from collections import defaultdict , OrderedDict
4
5
from .errors import DataJointError
5
6
6
7
8
+ def unite_master_parts (lst ):
9
+ """
10
+ re-order a list of table names so that part tables immediately follow their master tables without breaking
11
+ the topological order.
12
+ Without this correction, a simple topological sort may insert other descendants between master and parts.
13
+ The input list must be topologically sorted.
14
+ :example:
15
+ unite_master_parts(
16
+ ['`s`.`a`', '`s`.`a__q`', '`s`.`b`', '`s`.`c`', '`s`.`c__q`', '`s`.`b__q`', '`s`.`d`', '`s`.`a__r`']) ->
17
+ ['`s`.`a`', '`s`.`a__q`', '`s`.`a__r`', '`s`.`b`', '`s`.`b__q`', '`s`.`c`', '`s`.`c__q`', '`s`.`d`']
18
+ """
19
+ for i in range (2 , len (lst )):
20
+ name = lst [i ]
21
+ match = re .match (r'(?P<master>`\w+`.`\w+)__\w+`' , name )
22
+ if match : # name is a part table
23
+ master = match .group ('master' )
24
+ for j in range (i - 1 , - 1 , - 1 ):
25
+ if lst [j ] == master + '`' or lst [j ].startswith (master + '__' ):
26
+ # move from the ith position to the (j+1)th position
27
+ lst [j + 1 :i + 1 ] = [name ] + lst [j + 1 :i ]
28
+ break
29
+ else :
30
+ raise DataJointError ("Found a part table {name} without its master table." .format (name = name ))
31
+ return lst
32
+
33
+
7
34
class Dependencies (nx .DiGraph ):
8
35
"""
9
36
The graph of dependencies (foreign keys) between loaded tables.
@@ -16,15 +43,22 @@ class Dependencies(nx.DiGraph):
16
43
def __init__ (self , connection = None ):
17
44
self ._conn = connection
18
45
self ._node_alias_count = itertools .count ()
46
+ self ._loaded = False
19
47
super ().__init__ (self )
20
48
21
- def load (self ):
49
+ def clear (self ):
50
+ self ._loaded = False
51
+ super ().clear ()
52
+
53
+ def load (self , force = True ):
22
54
"""
23
55
Load dependencies for all loaded schemas.
24
56
This method gets called before any operation that requires dependencies: delete, drop, populate, progress.
25
57
"""
26
-
27
58
# reload from scratch to prevent duplication of renamed edges
59
+ if self ._loaded and not force :
60
+ return
61
+
28
62
self .clear ()
29
63
30
64
# load primary key info
@@ -77,6 +111,7 @@ def load(self):
77
111
78
112
if not nx .is_directed_acyclic_graph (self ): # pragma: no cover
79
113
raise DataJointError ('DataJoint can only work with acyclic dependencies' )
114
+ self ._loaded = True
80
115
81
116
def parents (self , table_name , primary = None ):
82
117
"""
@@ -86,6 +121,7 @@ def parents(self, table_name, primary=None):
86
121
attribute are considered.
87
122
:return: dict of tables referenced by the foreign keys of table
88
123
"""
124
+ self .load (force = False )
89
125
return {p [0 ]: p [2 ] for p in self .in_edges (table_name , data = True )
90
126
if primary is None or p [2 ]['primary' ] == primary }
91
127
@@ -97,6 +133,7 @@ def children(self, table_name, primary=None):
97
133
attribute are considered.
98
134
:return: dict of tables referencing the table through foreign keys
99
135
"""
136
+ self .load (force = False )
100
137
return {p [1 ]: p [2 ] for p in self .out_edges (table_name , data = True )
101
138
if primary is None or p [2 ]['primary' ] == primary }
102
139
@@ -105,17 +142,19 @@ def descendants(self, full_table_name):
105
142
:param full_table_name: In form `schema`.`table_name`
106
143
:return: all dependent tables sorted in topological order. Self is included.
107
144
"""
145
+ self .load (force = False )
108
146
nodes = self .subgraph (
109
147
nx .algorithms .dag .descendants (self , full_table_name ))
110
- return [full_table_name ] + list (
111
- nx .algorithms .dag .topological_sort (nodes ))
148
+ return unite_master_parts ( [full_table_name ] + list (
149
+ nx .algorithms .dag .topological_sort (nodes )))
112
150
113
151
def ancestors (self , full_table_name ):
114
152
"""
115
153
:param full_table_name: In form `schema`.`table_name`
116
154
:return: all dependent tables sorted in topological order. Self is included.
117
155
"""
156
+ self .load (force = False )
118
157
nodes = self .subgraph (
119
158
nx .algorithms .dag .ancestors (self , full_table_name ))
120
- return [ full_table_name ] + list (reversed (list (
121
- nx .algorithms .dag .topological_sort (nodes ))))
159
+ return list (reversed ( unite_master_parts (list (
160
+ nx .algorithms .dag .topological_sort (nodes )) + [ full_table_name ]) ))
0 commit comments