~launchpad-pqm/launchpad/devel

10637.3.7 by Guilherme Salgado
merge devel
1
#!/usr/bin/python -S
8687.15.9 by Karl Fogel
Add the copyright header block to more files (everything under database/).
2
#
3
# Copyright 2009 Canonical Ltd.  This software is licensed under the
4
# GNU Affero General Public License version 3 (see the file LICENSE).
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
5
6
__metaclass__ = type
7
8677.1.3 by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout
8
# pylint: disable-msg=W0403
9
import _pythonpath
10
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
11
import psycopg, sys, os, re
12
from security import DbSchema, CursorWrapper
13
from ConfigParser import SafeConfigParser, NoOptionError
14
1806 by Canonical.com Patch Queue Manager
Always use sys.path.insert instead of sys.path.append to give precedence for launchpad libs that are inside our tree. r=stub
15
sys.path.insert(0, os.path.join(
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
16
    os.path.dirname(__file__), os.pardir, os.pardir, 'lib'
17
    ))
18
19
config = SafeConfigParser()
20
config.read(['diagram.cfg'])
21
22
def trim(wanted_tables):
23
    '''Drop everything we don't want in the diagram'''
24
    con = psycopg.connect('dbname=launchpad_dev')
25
    cur = CursorWrapper(con.cursor())
26
    done = False
27
28
    # Drop everything we don't want to document
29
    schema = DbSchema(con)
30
    all_objs = schema.values()
31
    while not done:
32
        schema = DbSchema(con)
33
        for obj in schema.values():
34
            if obj.fullname in wanted_tables:
35
                continue
36
            if obj.type == "view":
37
                print 'Dropping view %s' % obj.fullname
38
                cur.execute("DROP VIEW %s CASCADE" % obj.fullname)
39
                break
40
            elif obj.type == "table":
41
                print 'Dropping table %s' % obj.fullname
42
                cur.execute("DROP TABLE %s CASCADE" % obj.fullname)
43
                break
44
            if obj == all_objs[-1]:
45
                done = True
46
    con.commit()
47
48
class Universe:
49
    def __contains__(self, i):
50
        '''The universe contains everything'''
51
        return True
52
10293.3.1 by Max Bowsher
Remove use of the deprecated sets module.
53
all_tables = set()
54
graphed_tables = set()
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
55
56
def tartup(filename, outfile, section):
57
    dot = open(filename).read()
58
59
    # Shorten timestamp declarations
60
    dot = re.subn('timestamp without time zone', 'timestamp', dot)[0]
61
62
    # Collapse all whitespace that is safe
63
    dot = re.subn('\s+', ' ', dot)[0]
64
    dot = re.subn(';', ';\n', dot)[0]
65
66
    lines = dot.split('\n')
67
68
    counter = 0
69
    wanted_tables = [
70
        s.strip() for s in config.get(section, 'tables').split(',')
71
        if s.strip()
72
        ]
73
74
    if '*' in wanted_tables:
75
        wanted_tables = Universe()
76
    else:
77
        exploded_wanted_tables = []
78
        excluded_tables = []
79
        for table in wanted_tables:
80
            if table.endswith('+'):
81
                if table.endswith('++'):
82
                    table = table[:-2]
83
                    exploded_wanted_tables.extend(explode(table, True))
84
                else:
85
                    table = table[:-1]
86
                    exploded_wanted_tables.extend(explode(table, False))
87
            if table.endswith('-'):
88
                table = table[:-1]
89
                excluded_tables.append(table)
90
            else:
91
                exploded_wanted_tables.append(table)
92
        wanted_tables = [
93
            t for t in exploded_wanted_tables
94
            if t not in excluded_tables
95
            ]
96
        for t in wanted_tables:
97
            graphed_tables.add(t)
98
99
    for i in xrange(0, len(lines)):
100
        line = lines[i]
8677.1.3 by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout
101
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
102
        # Trim tables we don't want to see
103
        m = re.search(r'^\s* "(.+?)" \s+ \[shape', line, re.X)
104
        if m is not None:
105
            table = m.group(1)
106
            all_tables.add(table)
107
            if table not in wanted_tables:
108
                lines[i] = ''
109
                continue
110
111
        # Trim foreign key relationships as specified, replacing with phantom
112
        # links
113
        m = re.search(
1363 by Canonical.com Patch Queue Manager
Refactored VSourcePackageReleasePublishing for Keybuk
114
                r'^\s*"(.+?)" \s -> \s "(.*?)" \s '
115
                r'\[label="(.*?)"\]; \s* $',
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
116
                line, re.X
117
                )
118
        if m is None:
119
            continue
120
121
        counter += 1
122
123
        t1 = m.group(1)
124
        t2 = m.group(2)
125
126
        # No links from an unwanted table to any other tables
127
        if t1 not in wanted_tables:
128
            lines[i] = ''
129
            continue
8677.1.3 by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout
130
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
131
        # Links to ourself are fine, unless the table is not wanted
132
        if t1 == t2:
133
            continue
8677.1.3 by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout
134
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
135
        # Get allowed links
136
        allowed_link = True
137
        if t2 not in wanted_tables:
138
            allowed_link = False
139
        else:
8677.1.3 by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout
140
            for source, end in [(t1, t2), (t2, t1)]:
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
141
                try:
142
                    allowed = config.get(section, source)
143
                except NoOptionError:
144
                    continue
145
                allowed = [a.strip() for a in allowed.split(',') if a.strip()]
146
                if end not in allowed:
147
                    allowed_link = False
148
                    break
149
        if allowed_link:
150
            continue
151
152
        fake_node = 'fake_%s_%d' % (t2, counter)
153
        counter += 1
154
        lines[i] = '''
155
            "%(fake_node)s" [shape="ellipse",label="%(t2)s",color=red ];
1363 by Canonical.com Patch Queue Manager
Refactored VSourcePackageReleasePublishing for Keybuk
156
            "%(t1)s" -> "%(fake_node)s" [label="", len=.01, w=10000];
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
157
            ''' % vars()
158
    open(outfile, 'w').write('\n'.join(lines))
159
160
def explode(table, two_way=False):
161
    con = psycopg.connect(config.get('DEFAULT', 'dbconnect'))
162
    cur = con.cursor()
163
    cur.execute('''
164
        SELECT
165
            src.relname AS src,
166
            dst.relname AS dst
167
        FROM
168
            pg_constraint,
169
            pg_class AS src,
170
            pg_class AS dst
171
        WHERE
172
            (src.relname=%(table)s OR dst.relname=%(table)s)
173
            AND src.oid = conrelid
174
            AND dst.oid = confrelid
175
        ''', vars())
176
    references = list(cur.fetchall())
177
    rv = []
178
    for src, dst in references:
179
        if two_way:
180
            rv.append(src)
181
            rv.append(dst)
182
        elif src == table:
183
            rv.append(dst)
184
    return rv
185
8677.1.3 by Stuart Bishop
All scripts need to import _pythonpath to function correctly with buildout
186
4195.1.1 by Brad Crittenden
Implement upload and management of files associated with a product release.
187
def main(filetypes):
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
188
4195.1.1 by Brad Crittenden
Implement upload and management of files associated with a product release.
189
    print filetypes
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
190
    # Run postgresql_autodoc, creating autodoc.dot
191
    cmd = (
192
            "postgresql_autodoc -f autodoc -t dot -s public "
193
            "-d launchpad_dev -l %s -u postgres" % os.pardir
194
            )
195
    rv = os.system(cmd)
196
    assert rv == 0, 'Error %d running %r' % (rv, cmd)
197
198
    for section in config.sections():
199
        if sys.argv[1:]:
200
            render = False
201
            for a in sys.argv[1:]:
202
                if section in a:
203
                    render = True
204
                    break
205
            if not render:
206
                continue
207
        # Munge the dot file because by default it is renders badly
208
        print 'Tarting up autodoc.dot into +%s.dot' % section
209
        tartup('autodoc.dot', '+%s.dot' % section, section)
210
211
        # Render
4195.1.1 by Brad Crittenden
Implement upload and management of files associated with a product release.
212
        for lang in filetypes:
213
            print lang
1363 by Canonical.com Patch Queue Manager
Refactored VSourcePackageReleasePublishing for Keybuk
214
            cmd = config.get(section, '%s_command' % lang)
215
216
            print (
217
                    'Producing %(section)s.%(lang)s from %(section)s.dot '
218
                    'using %(lang)s' % vars()
219
                    )
220
221
            csection = section.capitalize()
222
223
            cmd = (
224
                '%(cmd)s -Glabel=%(csection)s -o %(section)s.%(lang)s '
225
                '-T%(lang)s +%(section)s.dot' % vars()
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
226
                )
1363 by Canonical.com Patch Queue Manager
Refactored VSourcePackageReleasePublishing for Keybuk
227
            print repr(cmd)
228
            rv = os.system(cmd)
229
            assert rv == 0, 'Error %d running %r' % (rv, cmd)
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
230
231
    ungraphed_tables = [t for t in all_tables if t not in graphed_tables]
232
    if ungraphed_tables:
233
        print "The following tables are not on any diagrams except '*': ",
234
        print ', '.join(ungraphed_tables)
235
236
if __name__ == '__main__':
237
    os.chdir('diagrams')
4195.1.1 by Brad Crittenden
Implement upload and management of files associated with a product release.
238
    filetypes = ['svg', 'ps', 'png']
239
    ft = []
240
    while 1:
241
        try:
242
            if sys.argv[1] in filetypes:
243
                ft.append(sys.argv[1])
244
                sys.argv = sys.argv[1:]
245
            else:
246
                break
247
        except IndexError:
248
            break
249
250
    if not ft:
251
        ft = filetypes
252
253
    main(ft)
1339 by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut)
254