root/branch/merger/sqlitedb.py @ 4406

Revision 4406, 4.8 kB (checked in by gpolo, 4 years ago)

Added merging of columns that do not reference other columns.

  • Property svn:eol-style set to native
Line 
1import os
2import shutil
3try:
4    from sqlite3 import dbapi2 as sqlite
5except ImportError:
6    from pysqlite2 import dbapi2 as sqlite
7
8def _dict_factory(cursor, row):
9    res = {}
10    for indx, col in enumerate(cursor.description):
11        res[col[0]] = row[indx]
12
13    return res
14
15class _DictCursor(sqlite.Cursor):
16    def __init__(self, *args, **kwargs):
17        sqlite.Cursor.__init__(self, *args, **kwargs)
18        self.row_factory = _dict_factory
19
20
21def merge(new_dbpath, old_dbpath, dry_run=False):
22    """Merge a newer database structure with an older (but very similar)
23    database."""
24    new = sqlite.connect(new_dbpath)
25    new_cursor = new.cursor(_DictCursor)
26
27    if not os.path.isfile(old_dbpath):
28        raise Exception("The older db %r is not a file." % old_dbpath)
29
30    old = sqlite.connect(old_dbpath)
31    old_cursor = old.cursor(_DictCursor)
32
33    new_tables = new_cursor.execute(
34            "SELECT name FROM sqlite_master "
35            "WHERE type='table'").fetchall()
36    for table in new_tables:
37        name = table['name']
38        # Verify that this table is in the older db, or not
39        old_table = old_cursor.execute(
40                "SELECT name FROM sqlite_master "
41                "WHERE type='table' AND name=?", (name, )).fetchone()
42
43        if old_table is None:
44            # The older db does not have this table, create it there as well
45            # the related triggers.
46            print "Adding the table '%s' and related triggers." % name
47            if dry_run:
48                continue
49
50            new_table_sql = new_cursor.execute(
51                    "SELECT sql FROM sqlite_master "
52                    "WHERE type='table' and NAME=?", (name, )).fetchone()
53            old_cursor.execute(new_table_sql['sql'])
54            new_table_triggers = new_cursor.execute(
55                    "SELECT sql FROM sqlite_master "
56                    "WHERE type='trigger' and NAME=?", (name, )).fetchall()
57            for result in new_table_triggers:
58                old_cursor.execute(result['sql'])
59
60        else:
61            # The old db already have this table but maybe columns or triggers
62            # for it changed.
63            res = new_cursor.execute("pragma table_info(%s)" % name)
64            new_table_info = res.fetchall()
65            new_by_col = {}
66            for col in new_table_info:
67                colname = col.pop('name')
68                new_by_col[colname] = col
69
70            # sqlite says pragmas may disappear at any time, so, supposing
71            # the table name does exist in the database and table_info is
72            # an empty result then "pragma table_info" disappeared!
73            if not new_table_info:
74                # Maybe do something special here (like a warning?).
75                return
76
77            res = old_cursor.execute("pragma table_info(%s)" % name)
78            old_table_info = res.fetchall()
79            old_by_col = {}
80            for col in old_table_info:
81                colname = col.pop('name')
82                old_by_col[colname] = col
83
84            for cname, cinfo in old_by_col.iteritems():
85                if cname not in new_by_col:
86                    # sqlite can't handle deletion of columns, this merge
87                    # can't be done.
88                    raise Exception("The table '%s' in the old database "
89                            "contains a column named '%s' which no longer "
90                            "exists in the new table, sqlite can't handle "
91                            "this." % (name, cname))
92                elif cinfo != new_by_col[cname]:
93                    # sqlite also can't handle changes in column type and
94                    # others
95                    raise Exception("The table '%s' in the old database "
96                            "differs in the column named '%s'. (%r != %r)" % (
97                                name, cname, cinfo, new_by_col[cname]))
98
99                del new_by_col[cname]
100
101            # Attempt to create new columns
102            # XXX not checking if they reference other columns yet.
103            for cname, cinfo in new_by_col.iteritems():
104                if cinfo['pk']:
105                    # sqlite doesn't allow creating new columns as primary key
106                    raise Exception("The table '%s' in the new database "
107                            "has a new column named '%s' which is a primary "
108                            "key, but sqlite can't handle this." % (
109                                name, cname))
110
111                query = "ALTER TABLE %s ADD COLUMN %s %s" % (
112                        name, cname, cinfo['type'])
113                if cinfo['notnull']:
114                    query += " NOT NULL"
115                if cinfo['dflt_value']:
116                    query += " DEFAULT %s" % cinfo['dflt_value']
117                old_cursor.execute(query)
118
119            # XXX check for triggers here
120
121
122if __name__ == "__main__":
123    import sys
124    merge(*sys.argv[1:3])
Note: See TracBrowser for help on using the browser.