comparison roundup/backends/rdbms_common.py @ 2077:3e0961d6d44d

Added the "actor" property. Metakit backend not done (still not confident I know how it's supposed to work ;) Currently it will come up as NULL in the RDBMS backends for older items. The *dbm backends will look up the journal. I hope to remedy the former before 0.7's release. Fixed a bunch of migration issues in the rdbms backends while I was at it (index changes for key prop changes) and simplified the class table update code for RDBMSes that have "alter table" in their command set (ie. not sqlite) ... migration from "version 1" to "version 2" still hasn't actually been tested yet though.
author Richard Jones <richard@users.sourceforge.net>
date Mon, 15 Mar 2004 05:50:20 +0000
parents 2a4309450202
children fb4bf55b94d7
comparison
equal deleted inserted replaced
2076:2a4309450202 2077:3e0961d6d44d
1 # $Id: rdbms_common.py,v 1.78 2004-03-12 05:36:26 richard Exp $ 1 # $Id: rdbms_common.py,v 1.79 2004-03-15 05:50:20 richard Exp $
2 ''' Relational database (SQL) backend common code. 2 ''' Relational database (SQL) backend common code.
3 3
4 Basics: 4 Basics:
5 5
6 - map roundup classes to relational tables 6 - map roundup classes to relational tables
186 version = self.database_schema.get('version', 1) 186 version = self.database_schema.get('version', 1)
187 if version == 1: 187 if version == 1:
188 # version 1 doesn't have the OTK, session and indexing in the 188 # version 1 doesn't have the OTK, session and indexing in the
189 # database 189 # database
190 self.create_version_2_tables() 190 self.create_version_2_tables()
191 # version 1 also didn't have the actor column
192 self.add_actor_column()
191 else: 193 else:
192 return 0 194 return 0
193 195
194 self.database_schema['version'] = self.current_db_version 196 self.database_schema['version'] = self.current_db_version
195 return 1 197 return 1
208 ''' Figure the column names and multilink properties from the spec 210 ''' Figure the column names and multilink properties from the spec
209 211
210 "properties" is a list of (name, prop) where prop may be an 212 "properties" is a list of (name, prop) where prop may be an
211 instance of a hyperdb "type" _or_ a string repr of that type. 213 instance of a hyperdb "type" _or_ a string repr of that type.
212 ''' 214 '''
213 cols = ['_activity', '_creator', '_creation'] 215 cols = ['_actor', '_activity', '_creator', '_creation']
214 mls = [] 216 mls = []
215 # add the multilinks separately 217 # add the multilinks separately
216 for col, prop in properties: 218 for col, prop in properties:
217 if isinstance(prop, Multilink): 219 if isinstance(prop, Multilink):
218 mls.append(col) 220 mls.append(col)
238 return 0 240 return 0
239 241
240 if __debug__: 242 if __debug__:
241 print >>hyperdb.DEBUG, 'update_class FIRING' 243 print >>hyperdb.DEBUG, 'update_class FIRING'
242 244
245 # detect key prop change for potential index change
246 keyprop_changes = 0
247 if new_spec[0] != old_spec[0]:
248 keyprop_changes = {'remove': old_spec[0], 'add': new_spec[0]}
249
243 # detect multilinks that have been removed, and drop their table 250 # detect multilinks that have been removed, and drop their table
244 old_has = {} 251 old_has = {}
245 for name,prop in old_spec[1]: 252 for name, prop in old_spec[1]:
246 old_has[name] = 1 253 old_has[name] = 1
247 if new_has(name) or not isinstance(prop, Multilink): 254 if new_has(name):
248 continue 255 continue
249 # it's a multilink, and it's been removed - drop the old 256
250 # table. First drop indexes. 257 if isinstance(prop, Multilink):
251 self.drop_multilink_table_indexes(spec.classname, ml) 258 # first drop indexes.
252 sql = 'drop table %s_%s'%(spec.classname, prop) 259 self.drop_multilink_table_indexes(spec.classname, ml)
260
261 # now the multilink table itself
262 sql = 'drop table %s_%s'%(spec.classname, prop)
263 else:
264 # if this is the key prop, drop the index first
265 if old_spec[0] == prop:
266 self.drop_class_table_key_index(spec.classname, prop)
267 del keyprop_changes['remove']
268
269 # drop the column
270 sql = 'alter table _%s drop column _%s'%(spec.classname, prop)
271
253 if __debug__: 272 if __debug__:
254 print >>hyperdb.DEBUG, 'update_class', (self, sql) 273 print >>hyperdb.DEBUG, 'update_class', (self, sql)
255 self.cursor.execute(sql) 274 self.cursor.execute(sql)
256 old_has = old_has.has_key 275 old_has = old_has.has_key
257 276
258 # now figure how we populate the new table 277 # if we didn't remove the key prop just then, but the key prop has
259 fetch = ['_activity', '_creation', '_creator'] 278 # changed, we still need to remove the old index
260 properties = spec.getprops() 279 if keyprop_changes.has_key('remove'):
261 for propname,x in new_spec[1]: 280 self.drop_class_table_key_index(spec.classname,
262 prop = properties[propname] 281 keyprop_changes['remove'])
263 if isinstance(prop, Multilink): 282
264 if force or not old_has(propname): 283 # add new columns
265 # we need to create the new table 284 for propname, x in new_spec[1]:
266 self.create_multilink_table(spec, propname) 285 if old_has(propname):
267 elif old_has(propname): 286 continue
268 # we copy this col over from the old table 287 sql = 'alter table _%s add column _%s varchar(255)'%(
269 fetch.append('_'+propname) 288 spec.classname, propname)
270
271 # select the data out of the old table
272 fetch.append('id')
273 fetch.append('__retired__')
274 fetchcols = ','.join(fetch)
275 cn = spec.classname
276 sql = 'select %s from _%s'%(fetchcols, cn)
277 if __debug__:
278 print >>hyperdb.DEBUG, 'update_class', (self, sql)
279 self.cursor.execute(sql)
280 olddata = self.cursor.fetchall()
281
282 # TODO: update all the other index dropping code
283 self.drop_class_table_indexes(cn, old_spec[0])
284
285 # drop the old table
286 self.cursor.execute('drop table _%s'%cn)
287
288 # create the new table
289 self.create_class_table(spec)
290
291 if olddata:
292 # do the insert
293 args = ','.join([self.arg for x in fetch])
294 sql = 'insert into _%s (%s) values (%s)'%(cn, fetchcols, args)
295 if __debug__: 289 if __debug__:
296 print >>hyperdb.DEBUG, 'update_class', (self, sql, olddata[0]) 290 print >>hyperdb.DEBUG, 'update_class', (self, sql)
297 for entry in olddata: 291 self.cursor.execute(sql)
298 self.cursor.execute(sql, tuple(entry)) 292
293 # if the new column is a key prop, we need an index!
294 if new_spec[0] == propname:
295 self.create_class_table_key_index(spec.classname, propname)
296 del keyprop_changes['add']
297
298 # if we didn't add the key prop just then, but the key prop has
299 # changed, we still need to add the new index
300 if keyprop_changes.has_key('add'):
301 self.create_class_table_key_index(spec.classname,
302 keyprop_changes['add'])
299 303
300 return 1 304 return 1
301 305
302 def create_class_table(self, spec): 306 def create_class_table(self, spec):
303 ''' create the class table for the given spec 307 ''' create the class table for the given spec
350 354
351 def drop_class_table_indexes(self, cn, key): 355 def drop_class_table_indexes(self, cn, key):
352 # drop the old table indexes first 356 # drop the old table indexes first
353 l = ['_%s_id_idx'%cn, '_%s_retired_idx'%cn] 357 l = ['_%s_id_idx'%cn, '_%s_retired_idx'%cn]
354 if key: 358 if key:
355 # key prop too?
356 l.append('_%s_%s_idx'%(cn, key)) 359 l.append('_%s_%s_idx'%(cn, key))
357 360
358 # TODO: update all the other index dropping code
359 table_name = '_%s'%cn 361 table_name = '_%s'%cn
360 for index_name in l: 362 for index_name in l:
361 if not self.sql_index_exists(table_name, index_name): 363 if not self.sql_index_exists(table_name, index_name):
362 continue 364 continue
363 index_sql = 'drop index '+index_name 365 index_sql = 'drop index '+index_name
364 if __debug__: 366 if __debug__:
365 print >>hyperdb.DEBUG, 'drop_index', (self, index_sql) 367 print >>hyperdb.DEBUG, 'drop_index', (self, index_sql)
366 self.cursor.execute(index_sql) 368 self.cursor.execute(index_sql)
367 369
370 def create_class_table_key_index(self, cn, key):
371 ''' create the class table for the given spec
372 '''
373 if __debug__:
374 print >>hyperdb.DEBUG, 'update_class setting keyprop %r'% \
375 key
376 index_sql3 = 'create index _%s_%s_idx on _%s(_%s)'%(cn, key,
377 cn, key)
378 if __debug__:
379 print >>hyperdb.DEBUG, 'create_index', (self, index_sql3)
380 self.cursor.execute(index_sql3)
381
382 def drop_class_table_key_index(self, cn, key):
383 table_name = '_%s'%cn
384 index_name = '_%s_%s_idx'%(cn, key)
385 if not self.sql_index_exists(table_name, index_name):
386 return
387 sql = 'drop index '+index_name
388 if __debug__:
389 print >>hyperdb.DEBUG, 'drop_index', (self, sql)
390 self.cursor.execute(sql)
391
368 def create_journal_table(self, spec): 392 def create_journal_table(self, spec):
369 ''' create the journal table for a class given the spec and 393 ''' create the journal table for a class given the spec and
370 already-determined cols 394 already-determined cols
371 ''' 395 '''
372 # journal table 396 # journal table
378 self.cursor.execute(sql) 402 self.cursor.execute(sql)
379 self.create_journal_table_indexes(spec) 403 self.create_journal_table_indexes(spec)
380 404
381 def create_journal_table_indexes(self, spec): 405 def create_journal_table_indexes(self, spec):
382 # index on nodeid 406 # index on nodeid
383 index_sql = 'create index %s_journ_idx on %s__journal(nodeid)'%( 407 sql = 'create index %s_journ_idx on %s__journal(nodeid)'%(
384 spec.classname, spec.classname) 408 spec.classname, spec.classname)
385 if __debug__: 409 if __debug__:
386 print >>hyperdb.DEBUG, 'create_index', (self, index_sql) 410 print >>hyperdb.DEBUG, 'create_index', (self, sql)
387 self.cursor.execute(index_sql) 411 self.cursor.execute(sql)
388 412
389 def drop_journal_table_indexes(self, classname): 413 def drop_journal_table_indexes(self, classname):
390 index_name = '%s_journ_idx'%classname 414 index_name = '%s_journ_idx'%classname
391 if not self.sql_index_exists('%s__journal'%classname, index_name): 415 if not self.sql_index_exists('%s__journal'%classname, index_name):
392 return 416 return
599 if not node.has_key('creator'): 623 if not node.has_key('creator'):
600 # add in the "calculated" properties (dupe so we don't affect 624 # add in the "calculated" properties (dupe so we don't affect
601 # calling code's node assumptions) 625 # calling code's node assumptions)
602 node = node.copy() 626 node = node.copy()
603 node['creation'] = node['activity'] = date.Date() 627 node['creation'] = node['activity'] = date.Date()
604 node['creator'] = self.getuid() 628 node['actor'] = node['creator'] = self.getuid()
605 629
606 # default the non-multilink columns 630 # default the non-multilink columns
607 for col, prop in cl.properties.items(): 631 for col, prop in cl.properties.items():
608 if not node.has_key(col): 632 if not node.has_key(col):
609 if isinstance(prop, Multilink): 633 if isinstance(prop, Multilink):
655 self.cache_lru.remove(key) 679 self.cache_lru.remove(key)
656 680
657 # add the special props 681 # add the special props
658 values = values.copy() 682 values = values.copy()
659 values['activity'] = date.Date() 683 values['activity'] = date.Date()
684 values['actor'] = self.getuid()
660 685
661 # make db-friendly 686 # make db-friendly
662 values = self.serialise(classname, values) 687 values = self.serialise(classname, values)
663 688
664 cl = self.classes[classname] 689 cl = self.classes[classname]
1063 1088
1064 'classname' must not collide with the name of an existing class, 1089 'classname' must not collide with the name of an existing class,
1065 or a ValueError is raised. The keyword arguments in 'properties' 1090 or a ValueError is raised. The keyword arguments in 'properties'
1066 must map names to property objects, or a TypeError is raised. 1091 must map names to property objects, or a TypeError is raised.
1067 ''' 1092 '''
1068 if (properties.has_key('creation') or properties.has_key('activity') 1093 for name in 'creation activity creator actor'.split():
1069 or properties.has_key('creator')): 1094 if properties.has_key(name):
1070 raise ValueError, '"creation", "activity" and "creator" are '\ 1095 raise ValueError, '"creation", "activity", "creator" and '\
1071 'reserved' 1096 '"actor" are reserved'
1072 1097
1073 self.classname = classname 1098 self.classname = classname
1074 self.properties = properties 1099 self.properties = properties
1075 self.db = weakref.proxy(db) # use a weak ref to avoid circularity 1100 self.db = weakref.proxy(db) # use a weak ref to avoid circularity
1076 self.key = '' 1101 self.key = ''
1130 raise KeyError, '"id" is reserved' 1155 raise KeyError, '"id" is reserved'
1131 1156
1132 if self.db.journaltag is None: 1157 if self.db.journaltag is None:
1133 raise DatabaseError, 'Database open read-only' 1158 raise DatabaseError, 'Database open read-only'
1134 1159
1135 if propvalues.has_key('creation') or propvalues.has_key('activity'): 1160 if propvalues.has_key('creator') or propvalues.has_key('actor') or \
1136 raise KeyError, '"creation" and "activity" are reserved' 1161 propvalues.has_key('creation') or propvalues.has_key('activity'):
1162 raise KeyError, '"creator", "actor", "creation" and '\
1163 '"activity" are reserved'
1137 1164
1138 # new node's id 1165 # new node's id
1139 newid = self.db.newid(self.classname) 1166 newid = self.db.newid(self.classname)
1140 1167
1141 # validate propvalues 1168 # validate propvalues
1356 del d['creation'] 1383 del d['creation']
1357 else: 1384 else:
1358 creation = None 1385 creation = None
1359 if d.has_key('activity'): 1386 if d.has_key('activity'):
1360 del d['activity'] 1387 del d['activity']
1388 if d.has_key('actor'):
1389 del d['actor']
1361 self.db.addjournal(self.classname, newid, 'create', {}, creator, 1390 self.db.addjournal(self.classname, newid, 'create', {}, creator,
1362 creation) 1391 creation)
1363 return newid 1392 return newid
1364 1393
1365 _marker = [] 1394 _marker = []
1391 if propname == 'creator': 1420 if propname == 'creator':
1392 if d.has_key('creator'): 1421 if d.has_key('creator'):
1393 return d['creator'] 1422 return d['creator']
1394 else: 1423 else:
1395 return self.db.getuid() 1424 return self.db.getuid()
1425 if propname == 'actor':
1426 if d.has_key('actor'):
1427 return d['actor']
1428 else:
1429 return self.db.getuid()
1396 1430
1397 # get the property (raises KeyErorr if invalid) 1431 # get the property (raises KeyErorr if invalid)
1398 prop = self.properties[propname] 1432 prop = self.properties[propname]
1399 1433
1400 if not d.has_key(propname): 1434 if not d.has_key(propname):
1431 node id, a ValueError is raised. 1465 node id, a ValueError is raised.
1432 ''' 1466 '''
1433 if not propvalues: 1467 if not propvalues:
1434 return propvalues 1468 return propvalues
1435 1469
1436 if propvalues.has_key('creation') or propvalues.has_key('activity'): 1470 if propvalues.has_key('creation') or propvalues.has_key('creator') or \
1437 raise KeyError, '"creation" and "activity" are reserved' 1471 propvalues.has_key('actor') or propvalues.has_key('activity'):
1472 raise KeyError, '"creation", "creator", "actor" and '\
1473 '"activity" are reserved'
1438 1474
1439 if propvalues.has_key('id'): 1475 if propvalues.has_key('id'):
1440 raise KeyError, '"id" is reserved' 1476 raise KeyError, '"id" is reserved'
1441 1477
1442 if self.db.journaltag is None: 1478 if self.db.journaltag is None:
2173 if protected: 2209 if protected:
2174 d['id'] = String() 2210 d['id'] = String()
2175 d['creation'] = hyperdb.Date() 2211 d['creation'] = hyperdb.Date()
2176 d['activity'] = hyperdb.Date() 2212 d['activity'] = hyperdb.Date()
2177 d['creator'] = hyperdb.Link('user') 2213 d['creator'] = hyperdb.Link('user')
2214 d['actor'] = hyperdb.Link('user')
2178 return d 2215 return d
2179 2216
2180 def addprop(self, **properties): 2217 def addprop(self, **properties):
2181 '''Add properties to this class. 2218 '''Add properties to this class.
2182 2219
2343 # Overridden methods: 2380 # Overridden methods:
2344 def __init__(self, db, classname, **properties): 2381 def __init__(self, db, classname, **properties):
2345 '''The newly-created class automatically includes the "messages", 2382 '''The newly-created class automatically includes the "messages",
2346 "files", "nosy", and "superseder" properties. If the 'properties' 2383 "files", "nosy", and "superseder" properties. If the 'properties'
2347 dictionary attempts to specify any of these properties or a 2384 dictionary attempts to specify any of these properties or a
2348 "creation" or "activity" property, a ValueError is raised. 2385 "creation", "creator", "activity" or "actor" property, a ValueError
2386 is raised.
2349 ''' 2387 '''
2350 if not properties.has_key('title'): 2388 if not properties.has_key('title'):
2351 properties['title'] = hyperdb.String(indexme='yes') 2389 properties['title'] = hyperdb.String(indexme='yes')
2352 if not properties.has_key('messages'): 2390 if not properties.has_key('messages'):
2353 properties['messages'] = hyperdb.Multilink("msg") 2391 properties['messages'] = hyperdb.Multilink("msg")

Roundup Issue Tracker: http://roundup-tracker.org/