Mercurial > p > roundup > code
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") |
