~ibid-core/ibid/trunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/env python
# Copyright (c) 2009-2010, Stefano Rivera
# Released under terms of the MIT/X/Expat Licence. See COPYING for details.

from datetime import datetime
import gzip
import logging
from optparse import OptionParser, OptionGroup
from os.path import exists
from sys import path, stdin, stdout, stderr, exit

from sqlalchemy import select, DateTime
from twisted.python.modules import getModule

path.insert(0, '.')

import ibid
from ibid.compat import json
from ibid.config import FileConfig
from ibid.core import DatabaseManager
from ibid.db import metadata, upgrade_schemas
from ibid.db.types import IbidUnicode, IbidUnicodeText
from ibid.utils import ibid_version

version = ibid_version() or "bzr"

parser = OptionParser(usage='%prog [options...]', description=
"""Ibid Database management tool. Used for import, export, and upgrades.
Export format is JSON. FILE can be - for stdin/stdout or can end
in .gz for automatic Gzip compression.""",
version=("%prog " + version))

commands = OptionGroup(parser, 'Modes')
commands.add_option('-e', '--export', dest='export', metavar='FILE',
        help='Export DB contents to FILE.')
commands.add_option('-i', '--import', dest='import_', metavar='FILE',
        help='Import DB contents from FILE. DB must be empty first.')
commands.add_option('-u', '--upgrade', dest='upgrade', action='store_true',
        help='Upgrade DB schema. You should backup first.')
parser.add_option_group(commands)
parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
        default=False, help='Turn on debugging output to STDERR.')

(options, args) = parser.parse_args()

modes = sum(1 for mode in (options.import_, options.export, options.upgrade)
        if mode is not None)
if modes > 1:
    parser.error('Only one mode can be specified.')
elif modes == 0:
    parser.error('You must specify a mode.')

if options.verbose:
    logging.basicConfig(level=logging.DEBUG)
else:
    logging.basicConfig(level=logging.ERROR)

for module in getModule('ibid.plugins').iterModules():
    try:
        __import__(module.name)
    except Exception, e:
        print >> stderr, u"Couldn't load %s plugin: %s" % (
                module.name.replace('ibid.plugins.', ''), unicode(e))

ibid.config = FileConfig('ibid.ini')
ibid.config.merge(FileConfig('local.ini'))

def dump_table(table, db):
    """Return a list of dicts of the data in table.
    table is a SQLAlchemy table
    db is a SQLAlchemy session
    """
    sql = select([table])
    rows = []
    for row in db.execute(sql):
        out = {}
        for key in row.iterkeys():
            value = row[key]
            if isinstance(value, datetime):
                value = value.strftime('%Y-%m-%dT%H:%M:%SZ')
            out[key] = value
        rows.append(out)
    return rows

if options.upgrade is not None:
    db = DatabaseManager(check_schema_versions=False)['ibid']
    if not db.bind.has_table('schema'):
        print >> stderr, ("Database doesn't appear to contain an Ibid. "
                'Run ibid-setup.')
        exit(1)
    upgrade_schemas(db)

elif options.export is not None:
    if options.export == '-':
        output = stdout
    elif exists(options.export):
        print >> stderr, (
                'Output file (%s) exists, refusing to clobber it' %
                options.export)
        exit(1)
    elif options.export.endswith('.gz'):
        output = gzip.open(options.export, 'wb')
    else:
        output = open(options.export, 'wb')

    output.write(('# Ibid %(version)s Database dump for %(botname)s '
            'made on %(date)s\n') % {
        'version': version,
        'botname': ibid.config['botname'],
        'date': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
    })

    db = DatabaseManager()['ibid']
    dump = dict((table.name, dump_table(table, db))
            for table in metadata.tables.itervalues())

    output.write(json.dumps(dump, sort_keys=True, indent=4))
    output.close()

elif options.import_ is not None:
    db = DatabaseManager(check_schema_versions=False)['ibid']
    if db.bind.has_table('schema'):
        print >> stderr, ('Database looks like it already contains an Ibid. '
                'Refusing to clobber it.')
        exit(1)

    upgrade_schemas(db)

    if options.import_ == '-':
        input = stdin
    elif options.import_.endswith('.gz'):
        input = gzip.open(options.import_, 'rb')
    else:
        input = open(options.import_, 'rb')

    version = input.readline()
    dump = json.loads(input.read())

    dump_schema = dict((table['table'], table['version'])
            for table in dump['schema'])
    db_schema = dict((table['table'], table['version'])
            for table in dump_table(metadata.tables['schema'], db))
    if dump_schema != db_schema:
        print >> stderr, (
            "Dump schema doesn't match DB Schema. "
            'You must use the same version of ibid that you dumped with. '
            'Aborting.')
        exit(1)

    loaded = set()

    def load_table(table, data):
        """Load data into table.
        table is a table name
        data is a list of dicts of data
        """
        if table_name == 'schema':
            return

        dbtable = metadata.tables[table]
        for fk in dbtable.foreign_keys:
            dependency = fk.target_fullname.split('.')[0]
            if dependency not in loaded:
                load_table(dependency, dump[dependency])

        maxid = 0
        for row in data:
            if row['id'] > maxid:
                maxid = row['id']
            for field in row.iterkeys():
                if isinstance(dbtable.c[field].type, DateTime):
                    row[field] = datetime.strptime(row[field],
                            '%Y-%m-%dT%H:%M:%SZ')
                elif isinstance(dbtable.c[field].type,
                                (IbidUnicode, IbidUnicodeText)):
                    row[field] = unicode(row[field])
            sql = dbtable.insert().values(**row)
            db.execute(sql)
        if maxid != 0 and db.bind.engine.name == 'postgres':
            db.execute("SELECT setval('%s_id_seq', %i);" % (table, maxid + 1))
        db.commit()
        loaded.add(table)

    for table_name, table in dump.iteritems():
        if table_name not in loaded:
            load_table(table_name, table)

    db.close()