Skip to content

Commit ca89599

Browse files
author
Richard Jones
committed
backport of rdbms backend fix
1 parent e6ff94a commit ca89599

File tree

2 files changed

+104
-118
lines changed

2 files changed

+104
-118
lines changed

roundup/backends/rdbms_common.py

Lines changed: 56 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# $Id: rdbms_common.py,v 1.27.2.3 2003-02-12 00:03:38 richard Exp $
1+
# $Id: rdbms_common.py,v 1.27.2.4 2003-02-27 11:21:02 richard Exp $
22
''' Relational database (SQL) backend common code.
33
44
Basics:
@@ -177,128 +177,80 @@ def determine_columns(self, properties):
177177
cols.sort()
178178
return cols, mls
179179

180-
def update_class(self, spec, dbspec):
180+
def update_class(self, spec, old_spec):
181181
''' Determine the differences between the current spec and the
182182
database version of the spec, and update where necessary
183183
'''
184-
spec_schema = spec.schema()
185-
if spec_schema == dbspec:
186-
# no save needed for this one
184+
new_spec = spec
185+
new_has = new_spec.properties.has_key
186+
187+
new_spec = new_spec.schema()
188+
if new_spec == old_spec:
189+
# no changes
187190
return 0
191+
188192
if __debug__:
189193
print >>hyperdb.DEBUG, 'update_class FIRING'
190194

191195
# key property changed?
192-
if dbspec[0] != spec_schema[0]:
196+
if old_spec[0] != new_spec[0]:
193197
if __debug__:
194198
print >>hyperdb.DEBUG, 'update_class setting keyprop', `spec[0]`
195199
# XXX turn on indexing for the key property
196200

197-
# dict 'em up
198-
spec_propnames,spec_props = [],{}
199-
for propname,prop in spec_schema[1]:
200-
spec_propnames.append(propname)
201-
spec_props[propname] = prop
202-
dbspec_propnames,dbspec_props = [],{}
203-
for propname,prop in dbspec[1]:
204-
dbspec_propnames.append(propname)
205-
dbspec_props[propname] = prop
206-
207-
# now compare
208-
for propname in spec_propnames:
209-
prop = spec_props[propname]
210-
if dbspec_props.has_key(propname) and prop==dbspec_props[propname]:
201+
# detect multilinks that have been removed, and drop their table
202+
old_has = {}
203+
for name,prop in old_spec[1]:
204+
old_has[name] = 1
205+
if not new_has(name) and isinstance(prop, Multilink):
206+
# it's a multilink, and it's been removed - drop the old
207+
# table
208+
sql = 'drop table %s_%s'%(spec.classname, prop)
209+
if __debug__:
210+
print >>hyperdb.DEBUG, 'update_class', (self, sql)
211+
self.cursor.execute(sql)
211212
continue
212-
if __debug__:
213-
print >>hyperdb.DEBUG, 'update_class ADD', (propname, prop)
213+
old_has = old_has.has_key
214214

215-
if not dbspec_props.has_key(propname):
216-
# add the property
217-
if isinstance(prop, Multilink):
218-
# all we have to do here is create a new table, easy!
215+
# now figure how we populate the new table
216+
fetch = [] # fetch these from the old table
217+
properties = spec.getprops()
218+
for propname,x in new_spec[1]:
219+
prop = properties[propname]
220+
if isinstance(prop, Multilink):
221+
if not old_has(propname):
222+
# we need to create the new table
219223
self.create_multilink_table(spec, propname)
220-
continue
221-
222-
# no ALTER TABLE, so we:
223-
# 1. pull out the data, including an extra None column
224-
oldcols, x = self.determine_columns(dbspec[1])
225-
oldcols.append('id')
226-
oldcols.append('__retired__')
227-
cn = spec.classname
228-
sql = 'select %s,%s from _%s'%(','.join(oldcols), self.arg, cn)
229-
if __debug__:
230-
print >>hyperdb.DEBUG, 'update_class', (self, sql, None)
231-
self.cursor.execute(sql, (None,))
232-
olddata = self.cursor.fetchall()
233-
234-
# 2. drop the old table
235-
self.cursor.execute('drop table _%s'%cn)
236-
237-
# 3. create the new table
238-
cols, mls = self.create_class_table(spec)
239-
# ensure the new column is last
240-
cols.remove('_'+propname)
241-
assert oldcols == cols, "Column lists don't match!"
242-
cols.append('_'+propname)
243-
244-
# 4. populate with the data from step one
245-
s = ','.join([self.arg for x in cols])
246-
scols = ','.join(cols)
247-
sql = 'insert into _%s (%s) values (%s)'%(cn, scols, s)
248-
249-
# GAH, nothing had better go wrong from here on in... but
250-
# we have to commit the drop...
251-
# XXX this isn't necessary in sqlite :(
252-
self.conn.commit()
253-
254-
# do the insert
255-
for row in olddata:
256-
self.sql(sql, tuple(row))
224+
elif old_has(propname):
225+
# we copy this col over from the old table
226+
fetch.append('_'+propname)
227+
228+
# select the data out of the old table
229+
fetch.append('id')
230+
fetch.append('__retired__')
231+
fetchcols = ','.join(fetch)
232+
cn = spec.classname
233+
sql = 'select %s from _%s'%(fetchcols, cn)
234+
if __debug__:
235+
print >>hyperdb.DEBUG, 'update_class', (self, sql)
236+
self.cursor.execute(sql)
237+
olddata = self.cursor.fetchall()
257238

258-
else:
259-
# modify the property
260-
if __debug__:
261-
print >>hyperdb.DEBUG, 'update_class NOOP'
262-
pass # NOOP in gadfly
239+
# drop the old table
240+
self.cursor.execute('drop table _%s'%cn)
263241

264-
# and the other way - only worry about deletions here
265-
for propname in dbspec_propnames:
266-
prop = dbspec_props[propname]
267-
if spec_props.has_key(propname):
268-
continue
242+
# create the new table
243+
self.create_class_table(spec)
244+
245+
if olddata:
246+
# do the insert
247+
args = ','.join([self.arg for x in fetch])
248+
sql = 'insert into _%s (%s) values (%s)'%(cn, fetchcols, args)
269249
if __debug__:
270-
print >>hyperdb.DEBUG, 'update_class REMOVE', `prop`
250+
print >>hyperdb.DEBUG, 'update_class', (self, sql, olddata[0])
251+
for entry in olddata:
252+
self.cursor.execute(sql, *entry)
271253

272-
# delete the property
273-
if isinstance(prop, Multilink):
274-
sql = 'drop table %s_%s'%(spec.classname, prop)
275-
if __debug__:
276-
print >>hyperdb.DEBUG, 'update_class', (self, sql)
277-
self.cursor.execute(sql)
278-
else:
279-
# no ALTER TABLE, so we:
280-
# 1. pull out the data, excluding the removed column
281-
oldcols, x = self.determine_columns(spec.properties.items())
282-
oldcols.append('id')
283-
oldcols.append('__retired__')
284-
# remove the missing column
285-
oldcols.remove('_'+propname)
286-
cn = spec.classname
287-
sql = 'select %s from _%s'%(','.join(oldcols), cn)
288-
self.cursor.execute(sql, (None,))
289-
olddata = sql.fetchall()
290-
291-
# 2. drop the old table
292-
self.cursor.execute('drop table _%s'%cn)
293-
294-
# 3. create the new table
295-
cols, mls = self.create_class_table(self, spec)
296-
assert oldcols != cols, "Column lists don't match!"
297-
298-
# 4. populate with the data from step one
299-
qs = ','.join([self.arg for x in cols])
300-
sql = 'insert into _%s values (%s)'%(cn, s)
301-
self.cursor.execute(sql, olddata)
302254
return 1
303255

304256
def create_class_table(self, spec):

test/test_db.py

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: test_db.py,v 1.63 2002-12-12 09:31:04 richard Exp $
18+
# $Id: test_db.py,v 1.63.2.1 2003-02-27 11:21:04 richard Exp $
1919

2020
import unittest, os, shutil, time
2121

@@ -76,6 +76,53 @@ def setUp(self):
7676
self.db = anydbm.Database(config, 'admin')
7777
setupSchema(self.db, 1, anydbm)
7878

79+
#
80+
# schema mutation
81+
#
82+
def testAddProperty(self):
83+
self.db.issue.create(title="spam", status='1')
84+
self.db.commit()
85+
86+
self.db.issue.addprop(fixer=Link("user"))
87+
# force any post-init stuff to happen
88+
self.db.post_init()
89+
props = self.db.issue.getprops()
90+
keys = props.keys()
91+
keys.sort()
92+
self.assertEqual(keys, ['activity', 'assignedto', 'creation',
93+
'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
94+
'nosy', 'status', 'superseder', 'title'])
95+
self.assertEqual(self.db.issue.get('1', "fixer"), None)
96+
97+
def testRemoveProperty(self):
98+
self.db.issue.create(title="spam", status='1')
99+
self.db.commit()
100+
101+
del self.db.issue.properties['title']
102+
self.db.post_init()
103+
props = self.db.issue.getprops()
104+
keys = props.keys()
105+
keys.sort()
106+
self.assertEqual(keys, ['activity', 'assignedto', 'creation',
107+
'creator', 'deadline', 'files', 'foo', 'id', 'messages',
108+
'nosy', 'status', 'superseder'])
109+
self.assertEqual(self.db.issue.list(), ['1'])
110+
111+
def testAddRemoveProperty(self):
112+
self.db.issue.create(title="spam", status='1')
113+
self.db.commit()
114+
115+
self.db.issue.addprop(fixer=Link("user"))
116+
del self.db.issue.properties['title']
117+
self.db.post_init()
118+
props = self.db.issue.getprops()
119+
keys = props.keys()
120+
keys.sort()
121+
self.assertEqual(keys, ['activity', 'assignedto', 'creation',
122+
'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
123+
'nosy', 'status', 'superseder'])
124+
self.assertEqual(self.db.issue.list(), ['1'])
125+
79126
def testIDGeneration(self):
80127
id1 = self.db.issue.create(title="spam", status='1')
81128
id2 = self.db.issue.create(title="eggs", status='2')
@@ -205,19 +252,6 @@ def testKeyValue(self):
205252
self.db.user.retire(newid)
206253
self.assertRaises(KeyError, self.db.user.lookup, 'spam')
207254

208-
def testNewProperty(self):
209-
self.db.issue.create(title="spam", status='1')
210-
self.db.issue.addprop(fixer=Link("user"))
211-
# force any post-init stuff to happen
212-
self.db.post_init()
213-
props = self.db.issue.getprops()
214-
keys = props.keys()
215-
keys.sort()
216-
self.assertEqual(keys, ['activity', 'assignedto', 'creation',
217-
'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
218-
'nosy', 'status', 'superseder', 'title'])
219-
self.assertEqual(self.db.issue.get('1', "fixer"), None)
220-
221255
def testRetire(self):
222256
self.db.issue.create(title="spam", status='1')
223257
b = self.db.status.get('1', 'name')

0 commit comments

Comments
 (0)