Skip to content

Commit d6bed0f

Browse files
author
Richard Jones
committed
fixed rdbms mutation of properties
1 parent cdede0a commit d6bed0f

File tree

2 files changed

+111
-123
lines changed

2 files changed

+111
-123
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.36 2003-02-26 23:42:54 richard Exp $
1+
# $Id: rdbms_common.py,v 1.37 2003-02-27 11:07:36 richard Exp $
22
''' Relational database (SQL) backend common code.
33
44
Basics:
@@ -178,128 +178,80 @@ def determine_columns(self, properties):
178178
cols.sort()
179179
return cols, mls
180180

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

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

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

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

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

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

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

305257
def create_class_table(self, spec):

test/test_db.py

Lines changed: 55 additions & 19 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.71 2003-02-15 23:19:01 kedder Exp $
18+
# $Id: test_db.py,v 1.72 2003-02-27 11:07:39 richard Exp $
1919

2020
import unittest, os, shutil, time
2121

@@ -85,6 +85,56 @@ def setUp(self):
8585
self.db = anydbm.Database(config, 'admin')
8686
setupSchema(self.db, 1, anydbm)
8787

88+
#
89+
# schema mutation
90+
#
91+
def testAddProperty(self):
92+
self.db.issue.create(title="spam", status='1')
93+
self.db.commit()
94+
95+
self.db.issue.addprop(fixer=Link("user"))
96+
# force any post-init stuff to happen
97+
self.db.post_init()
98+
props = self.db.issue.getprops()
99+
keys = props.keys()
100+
keys.sort()
101+
self.assertEqual(keys, ['activity', 'assignedto', 'creation',
102+
'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
103+
'nosy', 'status', 'superseder', 'title'])
104+
self.assertEqual(self.db.issue.get('1', "fixer"), None)
105+
106+
def testRemoveProperty(self):
107+
self.db.issue.create(title="spam", status='1')
108+
self.db.commit()
109+
110+
del self.db.issue.properties['title']
111+
self.db.post_init()
112+
props = self.db.issue.getprops()
113+
keys = props.keys()
114+
keys.sort()
115+
self.assertEqual(keys, ['activity', 'assignedto', 'creation',
116+
'creator', 'deadline', 'files', 'foo', 'id', 'messages',
117+
'nosy', 'status', 'superseder'])
118+
self.assertEqual(self.db.issue.list(), ['1'])
119+
120+
def testAddRemoveProperty(self):
121+
self.db.issue.create(title="spam", status='1')
122+
self.db.commit()
123+
124+
self.db.issue.addprop(fixer=Link("user"))
125+
del self.db.issue.properties['title']
126+
self.db.post_init()
127+
props = self.db.issue.getprops()
128+
keys = props.keys()
129+
keys.sort()
130+
self.assertEqual(keys, ['activity', 'assignedto', 'creation',
131+
'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
132+
'nosy', 'status', 'superseder'])
133+
self.assertEqual(self.db.issue.list(), ['1'])
134+
135+
#
136+
# basic operations
137+
#
88138
def testIDGeneration(self):
89139
id1 = self.db.issue.create(title="spam", status='1')
90140
id2 = self.db.issue.create(title="eggs", status='2')
@@ -224,19 +274,6 @@ def testKeyValue(self):
224274
newid2 = self.db.user.create(username="spam")
225275
self.assertNotEqual(newid, newid2)
226276

227-
def testNewProperty(self):
228-
self.db.issue.create(title="spam", status='1')
229-
self.db.issue.addprop(fixer=Link("user"))
230-
# force any post-init stuff to happen
231-
self.db.post_init()
232-
props = self.db.issue.getprops()
233-
keys = props.keys()
234-
keys.sort()
235-
self.assertEqual(keys, ['activity', 'assignedto', 'creation',
236-
'creator', 'deadline', 'files', 'fixer', 'foo', 'id', 'messages',
237-
'nosy', 'status', 'superseder', 'title'])
238-
self.assertEqual(self.db.issue.get('1', "fixer"), None)
239-
240277
def testRetire(self):
241278
self.db.issue.create(title="spam", status='1')
242279
b = self.db.status.get('1', 'name')
@@ -850,11 +887,10 @@ def setUp(self):
850887
setupSchema(self.db, 0, metakit)
851888

852889
def suite():
853-
l = []
854-
# l = [
855-
# unittest.makeSuite(anydbmDBTestCase, 'test'),
856-
# unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
857-
# ]
890+
l = [
891+
unittest.makeSuite(anydbmDBTestCase, 'test'),
892+
unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
893+
]
858894
# return unittest.TestSuite(l)
859895

860896
from roundup import backends

0 commit comments

Comments
 (0)