Changeset 420
- Timestamp:
- 03/11/07 02:33:51
- Files:
-
- trunk/arenas.py (modified) (6 diffs)
- trunk/errors.py (modified) (1 diff)
- trunk/schemas.py (modified) (1 diff)
- trunk/storage/__init__.py (modified) (4 diffs)
- trunk/storage/db.py (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/arenas.py
r419 r420 102 102 del self.stores[name] 103 103 104 def map_all(self ):104 def map_all(self, conflict_mode='error'): 105 105 """Map all registered classes to internal storage structures. 106 106 … … 112 112 This method is idempotent, but that doesn't mean cheap. Try not 113 113 to call it very often (once at app startup is usually enough). 114 115 conflict_mode: This argument determines what happens when there are 116 discrepancies between the Dejavu model and the actual database. 117 118 If 'error' (the default), MappingError is raised for the 119 first issue and the sync process is aborted. 120 121 If 'warn', then a warning is raised (instead of an error) 122 for each issue, and the sync process is not aborted. This 123 allows you to see all errors at once, without having to stop 124 and fix each one and then execute the process again. 125 126 If 'repair', then each issue will be resolved by changing 127 the database to match the model. 114 128 """ 115 129 storemap = {} … … 128 142 storemap.sort() 129 143 for order, store, classes in storemap: 130 store.map(classes )144 store.map(classes, conflict_mode) 131 145 132 146 def shutdown(self): … … 174 188 raise KeyError("No registered class found for '%s'." % classname) 175 189 176 def storage(self, cls ):190 def storage(self, cls, automap=True): 177 191 """Return the StorageManager which handles Units of the given class. 178 192 179 The results of this call will be cached for performance. Also, this 180 will call store.map(cls) on first retrieval. 193 The results of this call will be cached for performance. 194 195 If automap is True (the default), this method will call 196 store.map(cls) if the class is not already cached. 181 197 """ 182 198 store = self._registered_classes.get(cls) … … 186 202 store = self._find_storage(cls) 187 203 self._registered_classes[cls] = store 188 store.map([cls]) 204 if automap: 205 store.map([cls]) 189 206 return store 190 207 … … 213 230 def create_storage(self, cls): 214 231 """Create storage space for cls.""" 215 # Skip the map() call inside storage(). 216 store = self._registered_classes.get(cls) 217 if not store: 218 store = self._find_storage(cls) 219 self._registered_classes[cls] = store 232 store = self.storage(cls, automap=False) 220 233 store.create_storage(cls) 221 234 222 235 def has_storage(self, cls): 223 236 """If storage space for cls exists, return True (False otherwise).""" 224 import geniusql 225 try: 226 store = self.storage(cls) 227 except geniusql.errors.MappingError: 228 return False 229 237 store = self.storage(cls, automap=False) 230 238 return store.has_storage(cls) 231 239 trunk/errors.py
r419 r420 20 20 """Warning about functionality which is not supported by all SM's.""" 21 21 pass 22 23 class MappingError(DejavuError): 24 """Exception raised when a Unit class cannot be mapped to storage. 25 26 This exception should be raised when a consumer attempts to build 27 a map between a Unit class and existing internal storage structures. 28 Other exceptions may be raised when trying to find such a map after 29 it has already (supposedly) been created. That is, the questions 30 "do we have a map?" and "can we create a map?" are distinct. 31 The latter should raise this exception whenever possible. 32 The behavior of the former is not specified. 33 """ 34 pass trunk/schemas.py
r419 r420 140 140 easily if you run assert_storage before the upgrade methods. 141 141 """ 142 143 classes = self.arena._registered_classes.keys() 144 if DeployedVersion in classes: 145 classes.remove(DeployedVersion) 146 147 for cls in classes: 148 if not self.arena.has_storage(cls): 149 self.arena.create_storage(cls) 142 self.arena.map_all(conflict_mode='repair') 150 143 151 144 def assert_version(self): trunk/storage/__init__.py
r419 r420 1 1 """Storage Managers for Dejavu.""" 2 2 3 import re4 3 import datetime 5 import thread6 4 try: 7 5 import cPickle as pickle 8 6 except ImportError: 9 7 import pickle 10 11 from dejavu import logic, recur 8 import re 9 import thread 10 import warnings 11 12 from dejavu import errors, logic, recur 12 13 13 14 … … 60 61 # Schemas # 61 62 62 def map(self, classes): 63 """Map classes to internal storage structures.""" 64 pass 63 def map(self, classes, conflict_mode='error'): 64 """Map classes to internal storage. 65 66 conflict_mode: This argument determines what happens when there are 67 discrepancies between the Dejavu model and the actual storage. 68 69 If 'error' (the default), MappingError is raised for the 70 first issue and the mapping process is aborted. 71 72 If 'warn', then a warning is raised (instead of an error) 73 for each issue, and the mapping process is not aborted. 74 This allows you to see all errors at once, without having 75 to stop and fix each one and then execute the process again. 76 77 If 'repair', then each issue will be resolved by changing 78 storage to match the model. 79 """ 80 for cls in classes: 81 if not self.has_storage(cls): 82 if conflict_mode == 'repair': 83 self.create_storage(cls) 84 else: 85 msg = "%s: no storage found." % cls.__name__ 86 if conflict_mode == 'warn': 87 warnings.warn(msg) 88 else: 89 raise errors.MappingError(msg) 65 90 66 91 def create_database(self): … … 160 185 # Schemas # 161 186 162 def map(self, classes, warn=False): 163 """Map classes to internal storage structures. 164 165 If 'warn' is True, then any mapping errors are replaced by warnings. 166 This allows you to see all errors at once, without having to stop 167 and fix each one and then re-execute the process. 187 def map(self, classes, conflict_mode='error'): 188 """Map classes to internal storage. 189 190 conflict_mode: This argument determines what happens when there are 191 discrepancies between the Dejavu model and the actual database. 192 193 If 'error' (the default), MappingError is raised for the 194 first issue and the sync process is aborted. 195 196 If 'warn', then a warning is raised (instead of an error) 197 for each issue, and the sync process is not aborted. This 198 allows you to see all errors at once, without having to stop 199 and fix each one and then execute the process again. 200 201 If 'repair', then each issue will be resolved by changing 202 the database to match the model. 168 203 """ 169 self.nextstore.map(classes, warn)204 self.nextstore.map(classes, conflict_mode) 170 205 171 206 def create_database(self): … … 487 522 managers = { 488 523 "cache": CachingProxy, 524 "caching": CachingProxy, 489 525 "burned": BurnedProxy, 490 526 "proxy": ProxyStorage, trunk/storage/db.py
r419 r420 37 37 import dejavu 38 38 from dejavu import logic, storage, logflags, xray 39 from dejavu.errors import StorageWarning 39 from dejavu.errors import StorageWarning, MappingError 40 40 41 41 … … 68 68 self.db = self.databaseclass(**allOptions) 69 69 self.schema = self.db.schema(name) 70 71 if 'Prefix' in allOptions: 72 self.schema.prefix = allOptions['Prefix'] 70 73 71 74 def logger(msg): … … 418 421 auto_discover = True 419 422 420 def map(self, classes, warn=False):421 """Map classes to Table objects (if not found).423 def map(self, classes, conflict_mode='error'): 424 """Map classes to internal storage. 422 425 423 426 If self.auto_discover is True (the default), then Table/Column/Index … … 430 433 and changes to the database are not expected outside the model. 431 434 432 If 'warn' is True, then any mapping errors are replaced by warnings. 433 This allows you to see all errors at once, without having to stop 434 and fix each one and then re-execute the process. 435 conflict_mode: This argument determines what happens when there are 436 discrepancies between the Dejavu model and the actual database. 437 438 If 'error' (the default), MappingError is raised for the 439 first issue and the sync process is aborted. 440 441 If 'warn', then a warning is raised (instead of an error) 442 for each issue, and the sync process is not aborted. This 443 allows you to see all errors at once, without having to stop 444 and fix each one and then execute the process again. 445 446 If 'repair', then each issue will be resolved by changing 447 the database to match the model. 435 448 """ 436 449 if self.auto_discover: 437 self.sync(classes, warn)450 self.sync(classes, conflict_mode) 438 451 else: 439 452 for cls in classes: 440 clsname = cls.__name__ 441 if clsname in self.schema: 453 if self.has_storage(cls): 442 454 # If our consumer-side key is already present, skip this cls. 443 455 # This allows arena.storage() to auto-sync class by class … … 448 460 449 461 # Use the superclass call to avoid DROP/CREATE TABLE 450 dict.__setitem__(self.schema, cls name, t)451 452 def sync(self, classes, warn=False):462 dict.__setitem__(self.schema, cls.__name__, t) 463 464 def sync(self, classes, conflict_mode='error'): 453 465 """Map classes to existing Table objects (found via discovery). 454 466 455 If a matching Table/Column/Index cannot be found, KeyError is raised. 456 457 If 'warn' is True, then a warning is raised instead of an error. 458 This allows you to see all errors at once, without having to stop 459 and fix each one and then re-execute the process. 467 conflict_mode: This argument determines what happens when there are 468 discrepancies between the Dejavu model and the actual database. 469 470 If 'error' (the default), MappingError is raised for the 471 first issue and the sync process is aborted. 472 473 If 'warn', then a warning is raised (instead of an error) 474 for each issue, and the sync process is not aborted. This 475 allows you to see all errors at once, without having to stop 476 and fix each one and then execute the process again. 477 478 If 'repair', then each issue will be resolved by changing 479 the database to match the model. 460 480 """ 481 482 def notify(msg): 483 if conflict_mode == 'warn': 484 warnings.warn(msg) 485 else: 486 raise MappingError(msg) 487 461 488 for cls in classes: 462 489 clsname = cls.__name__ … … 471 498 tablename = self.schema.table_name(clsname) 472 499 try: 473 # Do we already have a map ?500 # Do we already have a map using the DB name? 474 501 table = self.schema[tablename] 502 self.schema.alias(table.name, clsname) 475 503 except KeyError: 476 504 # Can we create a map? Discover the DB table and try again. 477 505 try: 478 506 table = self.schema.discover(tablename) 507 self.schema.alias(table.name, clsname) 479 508 except geniusql.errors.MappingError: 480 509 msg = "%s: no such table %r." % (clsname, tablename) 481 if warn: 482 warnings.warn(msg) 510 if conflict_mode == 'repair': 511 self.create_storage(cls) 512 table = self.schema[clsname] 513 else: 514 notify(msg) 483 515 continue 484 else:485 raise geniusql.errors.MappingError(msg)486 487 self.schema.alias(table.name, clsname)488 516 489 517 # Match Column objects with class properties. … … 491 519 indices = cls.indices() 492 520 related = self.db.typeadapter.related 493 for ckey in cls.properties:494 colname = self.schema._column_name(table.name, ckey)521 for pkey in cls.properties: 522 colname = self.schema._column_name(table.name, pkey) 495 523 try: 496 524 col = dbcols[colname] 525 table.alias(colname, pkey) 497 526 except KeyError, x: 498 msg = "%s: no matching column found for %r." % (clsname, ckey) 499 if warn: 500 warnings.warn(msg) 527 msg = "%s: no column found for %r." % (clsname, pkey) 528 if conflict_mode == 'repair': 529 self.add_property(cls, pkey) 530 if pkey in cls.indices() and pkey not in table.indices: 531 self.add_index(cls, pkey) 532 col = table[pkey] 533 else: 534 notify(msg) 501 535 continue 536 537 # Check that the column.key matches our identifiers list; 538 # this is crucial for the proper operation of OLTP methods 539 # in geniusql.Table, which uses column.key to decide 540 # the unique identifiers for a given row of data. 541 if pkey in cls.identifiers and not col.key: 542 msg = ("%s: %r is an identifier, but the " 543 "column is not marked as a primary key." 544 % (clsname, pkey)) 545 if conflict_mode == 'repair': 546 col.key = True 547 table.add_primary() 502 548 else: 503 raise geniusql.errors.MappingError(msg) 549 notify(msg) 550 continue 551 elif col.key and not pkey in cls.identifiers: 552 msg = ("%s: %r is not an identifier, but the " 553 "column is marked as a primary key." 554 % (clsname, pkey)) 555 if conflict_mode == 'repair': 556 col.key = False 557 table.drop_primary() 558 # Just because the current pkey is not an identifier 559 # doesn't mean we have *no* identifiers. 560 if cls.identifiers: 561 table.add_primary() 562 else: 563 notify(msg) 564 continue 504 565 505 566 # Set imperfect_type 506 newtype = getattr(cls, ckey).type567 newtype = getattr(cls, pkey).type 507 568 if newtype != col.pytype: 508 569 col.imperfect_type = not related(newtype, col.pytype) 509 570 col.pytype = newtype 510 571 511 table.alias(col.name, ckey)512 513 572 # Try to find matching Index objects. Because index names are 514 573 # so platform-specific, we match attributes rather than names. 515 if ckey in indices:516 for ikey, idx in table.indices.ite ritems():574 if pkey in indices: 575 for ikey, idx in table.indices.items(): 517 576 if idx.colname == colname: 518 a = self.schema.table_name("i" + clsname + ckey)577 a = self.schema.table_name("i" + clsname + pkey) 519 578 table.indices.alias(ikey, a) 520 579 break 521 580 else: 522 msg = ("%s: no matching index found for %r." 523 % (clsname, ckey)) 524 if warn: 525 warnings.warn(msg) 581 msg = "%s: no index found for %r." % (clsname, pkey) 582 if conflict_mode == 'repair': 583 self.add_index(cls, pkey) 526 584 else: 527 raise geniusql.errors.MappingError(msg) 585 notify(msg) 586 continue 587 else: 588 if pkey in cls.identifiers and self.schema.db.pks_must_be_indexed: 589 pass 590 else: 591 for ikey, idx in table.indices.items(): 592 if idx.colname == colname: 593 msg = ("%s: index found for non-indexed %r." 594 % (clsname, pkey)) 595 if conflict_mode == 'repair': 596 self.drop_index(cls, ikey) 597 else: 598 notify(msg) 599 continue 528 600 529 601 # Set Table.references
