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