Changeset 127
- Timestamp:
- 12/21/05 02:48:04
- Files:
-
- trunk/__init__.py (modified) (1 diff)
- trunk/arenas.py (modified) (2 diffs)
- trunk/doc/index.html (modified) (1 diff)
- trunk/doc/modeling.html (modified) (1 diff)
- trunk/schemas.py (added)
- trunk/storage/__init__.py (modified) (5 diffs)
- trunk/storage/db.py (modified) (3 diffs)
- trunk/storage/storeado.py (modified) (5 diffs)
- trunk/storage/storemysql.py (modified) (3 diffs)
- trunk/storage/storepypgsql.py (modified) (2 diffs)
- trunk/storage/storeshelve.py (modified) (2 diffs)
- trunk/storage/storesqlite.py (modified) (4 diffs)
- trunk/test/test_dejavu.py (modified) (1 diff)
- trunk/test/test_storeburned.py (modified) (2 diffs)
- trunk/test/test_storecaching.py (modified) (2 diffs)
- trunk/test/test_storemsaccess.py (modified) (1 diff)
- trunk/test/test_storemysql.py (modified) (1 diff)
- trunk/test/test_storeproxy.py (modified) (2 diffs)
- trunk/test/test_storepypgsql.py (modified) (1 diff)
- trunk/test/test_storeshelve.py (modified) (1 diff)
- trunk/test/test_storesqlite.py (modified) (1 diff)
- trunk/test/test_storesqlserver.py (modified) (1 diff)
- trunk/test/zoo_fixture.py (modified) (3 diffs)
- trunk/units.py (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/__init__.py
r126 r127 42 42 from dejavu.containers import * 43 43 from dejavu.errors import * 44 from dejavu.schemas import * 44 45 from dejavu.units import * 45 46 from dejavu import logic trunk/arenas.py
r126 r127 149 149 self.storage(cls).create_storage(cls) 150 150 151 def has_storage(self, cls): 152 return self.storage(cls).has_storage(cls) 153 154 def drop_storage(self, cls): 155 self.storage(cls).drop_storage(cls) 156 157 def add_property(self, cls, name): 158 self.storage(cls).add_property(cls, name) 159 160 def drop_property(self, cls, name): 161 self.storage(cls).drop_property(cls, name) 162 163 def rename_property(self, cls, oldname, newname): 164 self.storage(cls).rename_property(cls, oldname, newname) 165 151 166 def migrate_class(self, cls, new_store): 152 167 """migrate_class(cls, new_store). Copy all units of cls to new_store.""" … … 249 264 classes = [c for c in self.arena._registered_classes.iterkeys() 250 265 if issubclass(c, cls)] 266 if not classes: 267 # Even the requested class is not registered. 268 raise errors.UnrecallableError("The '%s' class is not registered." 269 % cls.__name__) 251 270 252 271 for cls in classes: trunk/doc/index.html
r126 r127 56 56 <li>Loading Stores</li> 57 57 <li>Registering Unit Classes</li> 58 <li>Managing Schema Changes</li> 58 59 </ul> 59 60 </li> trunk/doc/modeling.html
r126 r127 688 688 the chain of associations between two Unit classes.</p> 689 689 690 <h4>Managing Schema Changes</h4> 691 <p>The <tt>Schema</tt> class helps you manage changes to your 692 Dejavu model throughout its lifetime. For example, let's say that we deploy 693 our Archaeology application at various libraries around the world. After 694 a year, one of the developers wishes to implement a new reporting feature; 695 however, it would be easiest to build if the Unit Property names could 696 be exposed to the users. Unfortunately, our "ArchID" property on the 697 Biography class isn't very informative. It would be better if we could 698 rename that to "ArchaeologistID":</p> 699 700 <pre>class ArchBioSchema(dejavu.Schema): 701 702 guid = 'da39a3ee5e6b4b0d3255bfef95601890afd80709' 703 latest = 2 704 705 def upgrade_to_2(self): 706 self.arena.rename_property(Biography, "ArchID", "ArchaeologistID") 707 708 abs = ArchBioSchema(arena) 709 abs.upgrade() 710 </pre> 711 712 <p>Assuming we've already made the change to our model, the above example 713 renames the property in the persistence layer (the database). There are 714 also <tt class='def'>add_property(name)</tt> and 715 <tt class='def'>drop_property(name)</tt> methods. There may be more methods 716 in future versions of Dejavu; this feature is pretty new.</p> 717 718 <p>The example also declares this change to be "version 2" of our schema. 719 If you examine the base Schema class, you will see that it already has an 720 <tt>upgrade_to_0</tt> method and an <tt>upgrade_to_1</tt> method. The 721 "zeroth" upgrade makes no schema changes; it merely marks all deployed 722 databases with "version 0". The upgrade to version 1 will automatically 723 create all of your existing tables for you. Once you've made your first 724 change to the initial schema, you can write your own <tt>upgrade_to_2</tt> 725 method as we did above.</p> 726 727 <p>If you call <tt class='def'>upgrade(version)</tt> with a version argument, 728 then your deployment will be upgraded to that version. If no argument is 729 given, the installation will be upgraded to <tt>Schema.latest</tt>. You 730 can even skip steps (i.e. remove broken steps) if it comes to that.</p> 731 732 <h5>The DeployedVersion Unit</h5> 733 734 <p>The <tt>Schema</tt> class uses a magic table in the database to keep 735 track of each deployment's schema version. The Unit class is called 736 "DeployedVersion", and it has ID and Version attributes.</p> 737 738 <p>The ID attribute will be set to whatever your <tt>Schema.guid</tt> 739 is. It's a simple way to isolate multiple installed Dejavu applications. 740 A given application should use the same guid throughout its lifetime. 741 I used <tt>sha.new().hexdigest()</tt> to generate the example. Feel free 742 to use sha.new, a guid generator, a descriptive name, or whatever you 743 like.</p> 744 745 <p>If you introduce schemas after you've deployed, you should probably 746 skip upgrade #1 by manually setting <tt>DeployedVersion.Version</tt> 747 to 1 (or whatever is accurate) at each of your installations.</p> 748 749 690 750 <hr /> 691 751 trunk/storage/__init__.py
r126 r127 40 40 raise NotImplementedError 41 41 42 def create_storage(self, unitClass):43 pass44 45 42 def reserve(self, unit): 46 43 """Reserve storage space for the Unit.""" … … 55 52 56 53 def distinct(self, cls, attrs, expr=None): 57 """ distinct(cls, attrs, expr=None) ->Distinct values for given attributes."""54 """Distinct values for given attributes.""" 58 55 raise NotImplementedError 59 56 60 57 def multirecall(self, classes, expr): 61 """multirecall(classes, expr) -> Full inner join units from each class.""" 62 raise NotImplementedError 58 """Full inner join units from each class.""" 59 raise NotImplementedError 60 61 # Schemas # 62 63 def create_database(self): 64 pass 65 66 def drop_database(self): 67 pass 68 69 def create_storage(self, cls): 70 pass 71 72 def has_storage(self, cls): 73 pass 74 75 def drop_storage(self, cls): 76 pass 77 78 def add_property(self, cls, name): 79 pass 80 81 def drop_property(self, cls, name): 82 pass 83 84 def rename_property(self, cls, oldname, newname): 85 pass 63 86 64 87 … … 90 113 self.nextstore.destroy(unit) 91 114 92 def create_storage(self, unitClass):93 if self.nextstore:94 self.nextstore.create_storage(unitClass)95 96 115 def reserve(self, unit): 97 116 """Reserve storage space for the Unit.""" … … 113 132 if self.nextstore: 114 133 return self.nextstore.multirecall(classes, expr) 134 135 # Schemas # 136 137 def create_database(self): 138 if self.nextstore: 139 self.nextstore.create_database() 140 141 def drop_database(self): 142 if self.nextstore: 143 self.nextstore.drop_database() 144 145 def create_storage(self, cls): 146 if self.nextstore: 147 self.nextstore.create_storage(cls) 148 149 def has_storage(self, cls): 150 if self.nextstore: 151 return self.nextstore.has_storage(cls) 152 return False 153 154 def drop_storage(self, cls): 155 if self.nextstore: 156 self.nextstore.drop_storage(cls) 157 158 def add_property(self, cls, name): 159 if self.nextstore: 160 self.nextstore.add_property(cls, name) 161 162 def drop_property(self, cls, name): 163 if self.nextstore: 164 self.nextstore.drop_property(cls, name) 165 166 def rename_property(self, cls, oldname, newname): 167 if self.nextstore: 168 self.nextstore.rename_property(cls, oldname, newname) 115 169 116 170 … … 335 389 # .cancel does nothing if the thread is already finished. 336 390 self.timer.cancel() 391 392 def add_property(self, cls, name): 393 self.sweep(cls) 394 if self.nextstore: 395 self.nextstore.add_property(cls, name) 396 397 def drop_property(self, cls, name): 398 self.sweep(cls) 399 if self.nextstore: 400 self.nextstore.drop_property(cls, name) 401 402 def rename_property(self, cls, oldname, newname): 403 self.sweep(cls) 404 if self.nextstore: 405 self.nextstore.rename_property(cls, oldname, newname) 337 406 338 407 trunk/storage/db.py
r126 r127 867 867 getattr(conn, self.close_connection_method)() 868 868 869 def create_database(self): 870 self.execute("CREATE DATABASE %s;" % self.sql_name(self.dbname)) 871 872 def drop_database(self): 873 self.execute("DROP DATABASE %s;" % self.sql_name(self.dbname)) 874 875 def create_storage(self, unitClass): 876 clsname = unitClass.__name__ 877 tablename = self.table_name(clsname) 878 typename = self.typeAdapter.coerce 879 880 fields = [] 881 for key in unitClass.properties(): 882 fields.append(u'%s %s' % (self.column_name(clsname, key), 883 typename(unitClass, key))) 884 self.execute(u'CREATE TABLE %s (%s);' % (tablename, ", ".join(fields))) 885 886 for index in unitClass.indices(): 887 i = self.table_name("i" + clsname + index) 888 self.execute(u'CREATE INDEX %s ON %s (%s);' % 889 (i, tablename, self.column_name(clsname, index))) 890 891 def select(self, unitClass, expr, fields=None, distinct=False): 892 clsname = unitClass.__name__ 869 def select(self, cls, expr, fields=None, distinct=False): 870 clsname = cls.__name__ 893 871 tablename = self.table_name(clsname) 894 872 if fields: … … 1058 1036 (self.column_name(clsname, key), val)) 1059 1037 1060 sql = ('UPDATE %s SET %s WHERE %s;' % 1061 (self.table_name(clsname), u", ".join(parms), 1062 self.id_clause(unit))) 1063 self.execute(sql) 1038 if parms: 1039 sql = ('UPDATE %s SET %s WHERE %s;' % 1040 (self.table_name(clsname), u", ".join(parms), 1041 self.id_clause(unit))) 1042 self.execute(sql) 1064 1043 unit.cleanse() 1065 1044 … … 1296 1275 if acceptable: 1297 1276 yield unitset 1277 1278 # Schemas # 1279 1280 def create_database(self): 1281 self.execute("CREATE DATABASE %s;" % self.sql_name(self.dbname)) 1282 1283 def drop_database(self): 1284 self.execute("DROP DATABASE %s;" % self.sql_name(self.dbname)) 1285 1286 def create_storage(self, cls): 1287 """Create storage for the given class.""" 1288 clsname = cls.__name__ 1289 tablename = self.table_name(clsname) 1290 typename = self.typeAdapter.coerce 1291 1292 fields = [] 1293 for key in cls.properties(): 1294 fields.append(u'%s %s' % (self.column_name(clsname, key), 1295 typename(cls, key))) 1296 self.execute(u'CREATE TABLE %s (%s);' % (tablename, ", ".join(fields))) 1297 1298 for index in cls.indices(): 1299 i = self.table_name("i" + clsname + index) 1300 self.execute(u'CREATE INDEX %s ON %s (%s);' % 1301 (i, tablename, self.column_name(clsname, index))) 1302 1303 def has_storage(self, cls): 1304 try: 1305 # Must use fetch here instead of execute, because e.g. MySQL 1306 # must call store_result if the query has a result set 1307 # (or it will crash on a subsequent execute). 1308 self.fetch("SELECT * FROM %s WHERE 1=0;" % self.table_name(cls.__name__)) 1309 except: 1310 return False 1311 return True 1312 1313 def drop_storage(self, cls): 1314 self.execute(u'DROP TABLE %s;' % self.table_name(cls.__name__)) 1315 1316 def add_property(self, cls, name): 1317 clsname = cls.__name__ 1318 self.execute("ALTER TABLE %s ADD COLUMN %s %s;" % 1319 (self.table_name(clsname), 1320 self.column_name(clsname, name), 1321 self.typeAdapter.coerce(cls, name), 1322 )) 1323 1324 def drop_property(self, cls, name): 1325 clsname = cls.__name__ 1326 self.execute("ALTER TABLE %s DROP COLUMN %s;" % 1327 (self.table_name(clsname), 1328 self.column_name(clsname, name))) 1329 1330 def rename_property(self, cls, oldname, newname): 1331 clsname = cls.__name__ 1332 oldname = self.column_name(clsname, oldname) 1333 newname = self.column_name(clsname, newname) 1334 if oldname != newname: 1335 self.execute("ALTER TABLE %s RENAME COLUMN %s TO %s;" % 1336 (self.table_name(clsname), oldname, newname)) 1298 1337 1299 1338 trunk/storage/storeado.py
r126 r127 44 44 adLockOptimistic = 3 45 45 adLockBatchOptimistic = 4 46 47 adSchemaColumns = 3 48 adSchemaTables = 20 46 49 47 50 adUseClient = 3 … … 357 360 x.args += (query, ) 358 361 conn = None 359 raise x360 361 def fetch(self, query, conn=None ):362 raise 363 364 def fetch(self, query, conn=None, schema=False): 362 365 """fetch(query, conn=None) -> rowdata, columns.""" 363 366 if conn is None: 364 367 conn = self.connection() 365 self.arena.log(query, dejavu.LOGSQL)366 368 367 res = win32com.client.Dispatch(r'ADODB.Recordset')368 # Uncomment the following to get .Recordcount369 # res.CursorLocation = adUseClient370 369 try: 371 if self.threaded: 372 # 'conn' will be a ConnectionWrapper object, which .Open 373 # won't accept. Pass the unwrapped connection instead. 374 res.Open(query, conn.conn, adOpenForwardOnly, adLockReadOnly) 370 if schema: 371 res = conn.OpenSchema(query) 375 372 else: 376 res.Open(query, conn, adOpenForwardOnly, adLockReadOnly) 373 self.arena.log(query, dejavu.LOGSQL) 374 res = win32com.client.Dispatch(r'ADODB.Recordset') 375 if self.threaded: 376 # 'conn' will be a ConnectionWrapper object, which .Open 377 # won't accept. Pass the unwrapped connection instead. 378 res.Open(query, conn.conn, adOpenForwardOnly, adLockReadOnly) 379 else: 380 res.Open(query, conn, adOpenForwardOnly, adLockReadOnly) 377 381 except pywintypes.com_error, x: 378 382 try: … … 382 386 x.args += (query, ) 383 387 conn = None 384 raise x388 raise 385 389 386 390 columns = [(x.Name, x.Type) for x in res.Fields] … … 397 401 398 402 return data, columns 399 403 400 404 def version(self): 401 405 adoconn = win32com.client.Dispatch(r'ADODB.Connection') 402 406 return "ADO Version: %s" % adoconn.Version 407 408 def get_tables(self, conn=None): 409 return self.fetch(adSchemaTables, conn=conn, schema=True) 410 411 def has_storage(self, cls): 412 names = [x[2] for x in self.get_tables()[0]] 413 return self.table_name(cls.__name__, quoted=False) in names 414 415 def get_columns(self, conn=None): 416 return self.fetch(adSchemaColumns, conn=conn, schema=True) 417 418 def rename_property(self, cls, oldname, newname): 419 clsname = cls.__name__ 420 tblname = self.table_name(clsname, quoted=False) 421 oldname = self.column_name(clsname, oldname, quoted=False) 422 newname = self.column_name(clsname, newname, quoted=False) 423 424 conn = self.connection() 425 try: 426 cat = win32com.client.Dispatch(r'ADOX.Catalog') 427 cat.ActiveConnection = conn 428 cat.Tables(tblname).Columns(oldname).Name = newname 429 except pywintypes.com_error, x: 430 conn = None 431 cat = None 432 raise 403 433 404 434 … … 492 522 adoconn.Execute("DROP DATABASE %s;" % self.sql_name(self.dbname)) 493 523 adoconn.Close() 524 525 def add_property(self, cls, name): 526 clsname = cls.__name__ 527 # SQL Server doesn't use the "COLUMN" keyword with "ADD" 528 self.execute("ALTER TABLE %s ADD %s %s;" % 529 (self.table_name(clsname), 530 self.column_name(clsname, name), 531 self.typeAdapter.coerce(cls, name), 532 )) 533 534 def rename_property(self, cls, oldname, newname): 535 clsname = cls.__name__ 536 oldname = self.column_name(clsname, oldname, quoted=False) 537 newname = self.column_name(clsname, newname, quoted=False) 538 if oldname != newname: 539 self.execute("EXEC sp_rename '%s.%s', '%s', 'COLUMN'" % 540 (self.table_name(clsname), oldname, newname)) 494 541 495 542 trunk/storage/storemysql.py
r126 r127 98 98 # They also won't truncate trailing spaces like VARCHAR does. 99 99 if bytes <= 255: 100 return u"VARBINARY(%s) CHARACTER SET utf8" % bytes100 return u"VARBINARY(%s)" % bytes 101 101 elif bytes < 2 ** 16: 102 102 return "BLOB" … … 173 173 return _mysql.connect(**tmplconn) 174 174 175 def create_database(self):176 # _mysql has create_db and drop_db commands, but they're deprecated.177 sql = 'CREATE DATABASE %s;' % self.sql_name(self.dbname)178 conn = self._template_conn()179 self.execute(sql, conn)180 conn.close()181 182 def drop_database(self):183 sql = 'DROP DATABASE %s;' % self.sql_name(self.dbname)184 conn = self._template_conn()185 self.execute(sql, conn)186 conn.close()187 188 def version(self):189 conn = self._template_conn()190 rowdata, cols = self.fetch("SELECT version();", conn)191 conn.close()192 return "MySQL Version: %s" % rowdata[0][0]193 194 def create_storage(self, unitClass):195 clsname = unitClass.__name__196 tablename = self.table_name(clsname)197 198 coerce = self.typeAdapter.coerce199 fields = []200 for key in unitClass.properties():201 fields.append(u'%s %s' % (self.column_name(clsname, key),202 coerce(unitClass, key)))203 self.execute(u'CREATE TABLE %s (%s);' % (tablename, ", ".join(fields)))204 205 for index in unitClass.indices():206 i = self.table_name("i" + clsname + index)207 208 dbtype = coerce(unitClass, index)209 if dbtype.endswith('BLOB') or dbtype == 'TEXT':210 # MySQL won't allow indexes on a BLOB field211 # without a specific length.212 self.execute(u'CREATE INDEX %s ON %s (%s(%s));' %213 (i, tablename,214 self.column_name(clsname, index), 255))215 else:216 self.execute(u'CREATE INDEX %s ON %s (%s);' %217 (i, tablename,218 self.column_name(clsname, index)))219 220 175 def fetch(self, query, conn=None): 221 176 """fetch(query, conn=None) -> rowdata, columns. … … 238 193 (self.table_name(unit.__class__.__name__), 239 194 self.id_clause(unit))) 240 195 196 def version(self): 197 conn = self._template_conn() 198 rowdata, cols = self.fetch("SELECT version();", conn) 199 conn.close() 200 return "MySQL Version: %s" % rowdata[0][0] 201 202 # Schemas # 203 204 def create_database(self): 205 # _mysql has create_db and drop_db commands, but they're deprecated. 206 sql = 'CREATE DATABASE %s;' % self.sql_name(self.dbname) 207 conn = self._template_conn() 208 self.execute(sql, conn) 209 conn.close() 210 211 def drop_database(self): 212 sql = 'DROP DATABASE %s;' % self.sql_name(self.dbname) 213 conn = self._template_conn() 214 self.execute(sql, conn) 215 conn.close() 216 217 def create_storage(self, cls): 218 clsname = cls.__name__ 219 tablename = self.table_name(clsname) 220 221 coerce = self.typeAdapter.coerce 222 fields = [] 223 for key in cls.properties(): 224 fields.append(u'%s %s' % (self.column_name(clsname, key), 225 coerce(cls, key))) 226 self.execute(u'CREATE TABLE %s (%s);' % (tablename, ", ".join(fields))) 227 228 for index in cls.indices(): 229 i = self.table_name("i" + clsname + index) 230 231 dbtype = coerce(cls, index) 232 if dbtype.endswith('BLOB') or dbtype == 'TEXT': 233 # MySQL won't allow indexes on a BLOB field 234 # without a specific length. 235 self.execute(u'CREATE INDEX %s ON %s (%s(%s));' % 236 (i, tablename, 237 self.column_name(clsname, index), 255)) 238 else: 239 self.execute(u'CREATE INDEX %s ON %s (%s);' % 240 (i, tablename, 241 self.column_name(clsname, index))) 242 243 def rename_property(self, cls, oldname, newname): 244 clsname = cls.__name__ 245 oldcolname = self.column_name(clsname, oldname) 246 newcolname = self.column_name(clsname, newname) 247 if oldcolname != newcolname: 248 self.execute("ALTER TABLE %s CHANGE %s %s %s;" % 249 (self.table_name(clsname), oldcolname, newcolname, 250 self.typeAdapter.coerce(cls, newname))) 251 trunk/storage/storepypgsql.py
r126 r127 84 84 return libpq.PQconnectdb(tmplconn) 85 85 86 def create_database(self):87 c = self._template_conn()88 self.execute('CREATE DATABASE %s' % self.sql_name(self.dbname), c)89 c.finish()90 91 def drop_database(self):92 c = self._template_conn()93 self.execute("DROP DATABASE %s;" % self.sql_name(self.dbname), c)94 c.finish()95 96 86 def version(self): 97 87 c = self._template_conn() … … 114 104 115 105 return data, columns 106 107 def create_database(self): 108 c = self._template_conn() 109 self.execute('CREATE DATABASE %s' % self.sql_name(self.dbname), c) 110 c.finish() 111 112 def drop_database(self): 113 c = self._template_conn() 114 self.execute("DROP DATABASE %s;" % self.sql_name(self.dbname), c) 115 c.finish() 116 117 def has_storage(self, cls): 118 # For some odd reason, libpq errors if you try to filter by tablename. 119 sql = u"SELECT tablename FROM pg_tables" 120 data, cols = self.fetch(sql) 121 return [self.table_name(cls.__name__, quoted=False)] in data 116 122 trunk/storage/storeshelve.py
r126 r127 111 111 lock.release() 112 112 113 def create_database(self):114 if not os.path.exists(self.shelvepath):115 os.makedirs(self.shelvepath)116 117 def drop_database(self):118 while self.shelves:119 clsname, shelf = self.shelves.popitem()120 shelf.close()121 tbl = os.path.join(self.shelvepath, clsname)122 os.remove(tbl)123 124 113 def version(self): 125 114 import sys 126 115 return "Shelve version: %s" % sys.version 127 128 def create_storage(self, unitClass):129 pass130 116 131 117 def view(self, cls, fields, expr=None): … … 252 238 yield unitrow 253 239 240 # Schemas # 241 242 def create_database(self): 243 if not os.path.exists(self.shelvepath): 244 os.makedirs(self.shelvepath) 245 246 def drop_database(self): 247 while self.shelves: 248 clsname, shelf = self.shelves.popitem() 249 shelf.close() 250 tbl = os.path.join(self.shelvepath, clsname) 251 os.remove(tbl) 252 253 def create_storage(self, cls): 254 pass 255 256 def has_storage(self, cls): 257 return cls.__name__ in self.shelves 258 259 def drop_storage(self, cls): 260 clsname = cls.__name__ 261 shelf = self.shelves.pop(clsname) 262 shelf.close() 263 tbl = os.path.join(self.shelvepath, clsname) 264 os.remove(tbl) 265 266 def add_property(self, cls, name): 267 data, lock = self.shelf(cls) 268 try: 269 for id, props in data.items(): 270 props[name] = None 271 data[id] = props 272 finally: 273 lock.release() 274 275 def drop_property(self, cls, name): 276 data, lock = self.shelf(cls) 277 try: 278 for id, props in data.items(): 279 del props[name] 280 data[id] = props 281 finally: 282 lock.release() 283 284 def rename_property(self, cls, oldname, newname): 285 data, lock = self.shelf(cls) 286 try: 287 for id, props in data.items(): 288 props[newname] = props[oldname] 289 del props[oldname] 290 data[id] = props 291 finally: 292 lock.release() trunk/storage/storesqlite.py
r126 r127 16 16 import warnings 17 17 warnings.warn(_escape_warning, dejavu.StorageWarning) 18 19 _add_column_support = (_version >= storage.Version([3, 2, 0])) 20 _rename_table_support = (_version >= storage.Version([3, 1, 0])) 18 21 19 22 … … 163 166 # SQLite should create the DB if missing. 164 167 return _sqlite.connect(self.database, self.mode) 165 166 create_database = _get_conn167 168 def drop_database(self):169 self.shutdown()170 # This should accept relative or absolute paths171 os.remove(self.database)172 168 173 169 def version(self): … … 192 188 (self.table_name(unit.__class__.__name__), 193 189 self.id_clause(unit))) 194 195 def create_storage(self, unitClass):196 clsname = unitClass.__name__197 tablename = self.table_name(clsname)198 199 # SQLite is typeless.200 fields = [self.column_name(clsname, key)201 for key in unitClass.properties()]202 203 self.execute(u'CREATE TABLE %s (%s);' % (tablename, ", ".join(fields)))204 for index in unitClass.indices():205 i = self.table_name("i" + clsname + index)206 self.execute(u'CREATE INDEX %s ON %s (%s);' %207 (i, tablename, self.column_name(clsname, index)))208 190 209 191 def join(self, unitjoin): … … 287 269 (u', '.join(colnames), joins, w)) 288 270 return statement, imp, columns 289 271 272 # Schemas # 273 274 create_database = _get_conn 275 276 def drop_database(self): 277 self.shutdown() 278 # This should accept relative or absolute paths 279 os.remove(self.database) 280 281 def create_storage(self, cls): 282 clsname = cls.__name__ 283 tablename = self.table_name(clsname) 284 285 # SQLite is typeless. 286 fields = [self.column_name(clsname, key) for key in cls.properties()] 287 288 self.execute(u'CREATE TABLE %s (%s);' % (tablename, ", ".join(fields))) 289 for index in cls.indices(): 290 i = self.table_name("i" + clsname + index) 291 self.execute(u'CREATE INDEX %s ON %s (%s);' % 292 (i, tablename, self.column_name(clsname, index))) 293 294 def add_property(self, cls, name): 295 clsname = cls.__name__ 296 297 if _add_column_support: 298 self.execute("ALTER TABLE %s ADD COLUMN %s;" % 299 (self.table_name(clsname), 300 self.column_name(clsname, name))) 301 else: 302 tablename = self.table_name(clsname, quoted=False) 303 304 # Create a temporary table with the new schema (no indices). 305 # The schema should already be changed in the model layer. 306 props = list(cls.properties()) 307 fields = ", ".join([self.column_name(clsname, key) for key in props]) 308 self.execute("CREATE TABLE [temp_%s] (%s);" % (tablename, fields)) 309 oldfields = [] 310 for key in props: 311 if key == name: 312 oldfields.append(self.toAdapter.coerce(None)) 313 else: 314 oldfields.append(self.column_name(clsname, key)) 315 self.execute("INSERT INTO [temp_%s] SELECT %s FROM [%s];" % 316 (tablename, ", ".join(oldfields), tablename)) 317 318 # Drop and re-create the old table. 319 self.execute("DROP TABLE [%s];" % tablename) 320 self.create_storage(cls) 321 self.execute("INSERT INTO [%s] SELECT * FROM [temp_%s];" % 322 (tablename, tablename)) 323 self.execute("DROP TABLE [temp_%s];" % tablename) 324 325 def drop_property(self, cls, name): 326 clsname = cls.__name__ 327 tablename = self.table_name(clsname, quoted=False) 328 329 # Create a temporary table with the new schema (no indices). 330 # The schema should already be changed in the model layer. 331 fields = ", ".join([self.column_name(clsname, key) 332 for key in cls.properties()]) 333 self.execute("CREATE TABLE [temp_%s] (%s);" % (tablename, fields)) 334 self.execute("INSERT INTO [temp_%s] SELECT %s FROM [%s];" % 335 (tablename, fields, tablename)) 336 337 # Drop and re-create the old table. 338 self.execute("DROP TABLE [%s];" % tablename) 339 self.create_storage(cls) 340 self.execute("INSERT INTO [%s] SELECT * FROM [temp_%s];" % 341 (tablename, tablename)) 342 self.execute("DROP TABLE [temp_%s];" % tablename) 343 344 def rename_property(self, cls, oldname, newname): 345 clsname = cls.__name__ 346 tablename = self.table_name(clsname, quoted=False) 347 348 # Create a temporary table with the new schema (no indices). 349 # The schema should already be changed in the model layer. 350 props = list(cls.properties()) 351 fields = ", ".join([self.column_name(clsname, key) for key in props]) 352 self.execute("CREATE TABLE [temp_%s] (%s);" % (tablename, fields)) 353 oldfields = [] 354 for key in props: 355 if key == newname: 356 oldfields.append("%s AS %s" % (self.column_name(clsname, oldname), 357 self.column_name(clsname, newname))) 358 else: 359 oldfields.append(self.column_name(clsname, key)) 360 self.execute("INSERT INTO [temp_%s] SELECT %s FROM [%s];" % 361 (tablename, ", ".join(oldfields), tablename)) 362 363 # Drop and re-create the old table. 364 self.execute("DROP TABLE [%s];" % tablename) 365 self.create_storage(cls) 366 self.execute("INSERT INTO [%s] SELECT * FROM [temp_%s];" % 367 (tablename, tablename)) 368 self.execute("DROP TABLE [temp_%s];" % tablename) trunk/test/test_dejavu.py
r126 r127 9 9 10 10 arena.add_store("default", "dejavu.storage.CachingProxy") 11 arena.register(Animal) 12 arena.register(Lecture) 13 arena.register(Vet) 14 arena.register(Visit) 15 arena.register(Zoo) 11 16 12 17 trunk/test/test_storeburned.py
r126 r127 1 1 import os 2 import zoo_fixture3 2 4 zoo_fixture.init()5 3 shelvedb = "dejavu.storage.storeshelve.StorageManagerShelve" 6 4 proxiedopts = {u'Path': os.path.join(os.getcwd(), os.path.dirname(__file__))} 7 zoo_fixture.arena.add_store('shelvedb', shelvedb, proxiedopts)8 9 5 opts = {'Next Store': 'shelvedb', 10 6 'Lifetime': '10 minutes', … … 14 10 15 11 def run(): 12 import zoo_fixture 13 # Isolate schema changes from one test to the next. 14 reload(zoo_fixture) 15 zoo_fixture.init() 16 zoo_fixture.arena.add_store('shelvedb', shelvedb, proxiedopts) 16 17 zoo_fixture.run(SM_class, opts) 17 18 trunk/test/test_storecaching.py
r126 r127 1 1 import os 2 import zoo_fixture3 2 4 zoo_fixture.init()5 3 shelvedb = "dejavu.storage.storeshelve.StorageManagerShelve" 6 4 proxiedopts = {u'Path': os.path.join(os.getcwd(), os.path.dirname(__file__))} 7 zoo_fixture.arena.add_store('shelvedb', shelvedb, proxiedopts)8 9 5 opts = {'Next Store': 'shelvedb', 10 6 'Lifetime': '10 minutes', … … 14 10 15 11 def run(): 12 import zoo_fixture 13 # Isolate schema changes from one test to the next. 14 reload(zoo_fixture) 15 16 zoo_fixture.init() 17 zoo_fixture.arena.add_store('shelvedb', shelvedb, proxiedopts) 16 18 zoo_fixture.run(SM_class, opts) 17 19 trunk/test/test_storemsaccess.py
r126 r127 17 17 def run(): 18 18 import zoo_fixture 19 # Isolate schema changes from one test to the next. 20 reload(zoo_fixture) 19 21 zoo_fixture.init() 20 22 zoo_fixture.run(SM_class, opts) trunk/test/test_storemysql.py
r126 r127 19 19 def run(): 20 20 import zoo_fixture 21 # Isolate schema changes from one test to the next. 22 reload(zoo_fixture) 21 23 zoo_fixture.init() 22 24 zoo_fixture.run(SM_class, opts) trunk/test/test_storeproxy.py
r126 r127 1 1 import os 2 import zoo_fixture3 2 4 zoo_fixture.init()5 3 shelvedb = "dejavu.storage.storeshelve.StorageManagerShelve" 6 4 proxiedopts = {u'Path': os.path.join(os.getcwd(), os.path.dirname(__file__))} 7 zoo_fixture.arena.add_store('shelvedb', shelvedb, proxiedopts)8 9 5 opts = {'Next Store': 'shelvedb', 10 6 'Lifetime': '10 minutes', … … 14 10 15 11 def run(): 12 import zoo_fixture 13 # Isolate schema changes from one test to the next. 14 reload(zoo_fixture) 15 zoo_fixture.init() 16 zoo_fixture.arena.add_store('shelvedb', shelvedb, proxiedopts) 16 17 zoo_fixture.run(SM_class, opts) 17 18 trunk/test/test_storepypgsql.py
r126 r127 23 23 def run(): 24 24 import zoo_fixture 25 # Isolate schema changes from one test to the next. 26 reload(zoo_fixture) 25 27 zoo_fixture.init() 26 28 zoo_fixture.run(SM_class, opts) trunk/test/test_storeshelve.py
r126 r127 13 13 def run(): 14 14 import zoo_fixture 15 # Isolate schema changes from one test to the next. 16 reload(zoo_fixture) 15 17 zoo_fixture.init() 16 18 zoo_fixture.run(SM_class, opts) trunk/test/test_storesqlite.py
r126 r127 13 13 def run(): 14 14 import zoo_fixture 15 # Isolate schema changes from one test to the next. 16 reload(zoo_fixture) 15 17 zoo_fixture.init() 16 18 zoo_fixture.run(SM_class, opts) trunk/test/test_storesqlserver.py
r126 r127 16 16 def run(): 17 17 import zoo_fixture 18 # Isolate schema changes from one test to the next. 19 reload(zoo_fixture) 18 20 zoo_fixture.init() 19 21 zoo_fixture.run(SM_class, opts) trunk/test/zoo_fixture.py
r126 r127 700 700 ## lion.LastEscape = datetime.datetime(2005, 12, 25, 8, 14) 701 701 ## box.commit_since("rollback point name") 702 703 def testzzzz_Schema_Upgrade(self): 704 # Must run last. 705 zs = ZooSchema(arena) 706 707 # In this first upgrade, we simulate the case where the code was 708 # upgraded, and the database schema upgrade performed afterward. 709 # The Schema.latest property is set, and upgrade() is called with 710 # no argument (which should upgrade us to "latest"). 711 Animal.set_property("ExhibitID") 712 zs.latest = 2 713 zs.upgrade() 714 715 # In this example, we simulate the developer who wants to put 716 # model changes inline with database changes (see upgrade_to_3). 717 # We do not set latest, but instead supply an arg to upgrade(). 718 zs.upgrade(3) 719 720 # Test that Animals have a new "Family" property, and an ExhibitID. 721 box = arena.new_sandbox() 722 emp = box.unit(Animal, Family='Emperor Penguin') 723 self.assertEqual(emp.ExhibitID, 'The Penguin Encounter') 702 724 703 725 … … 720 742 arena.logflags = dejavu.LOGSQL 721 743 744 745 class ZooSchema(dejavu.Schema): 746 747 latest = 1 748 749 def upgrade_to_2(self): 750 self.arena.add_property(Animal, "ExhibitID") 751 box = self.arena.new_sandbox() 752 for exhibit in box.recall(Exhibit): 753 for animalID in exhibit.Animals: 754 a = box.unit(Animal, ID=animalID) 755 if a: 756 # Exhibits are identified by ZooID and Name 757 a.ZooID = exhibit.ZooID 758 a.ExhibitID = exhibit.Name 759 box.flush_all() 760 761 def upgrade_to_3(self): 762 Animal.remove_property("Species") 763 Animal.set_property("Family") 764 765 # Note that we drop this column in a separate step from step 2. 766 # If we had mixed model properties and SM properties in step 2, 767 # we could have done this all in one step. But this is a better 768 # demonstration of the possibilities. ;) 769 Exhibit.remove_property("Animals") 770 self.arena.drop_property(Exhibit, "Animals") 771 772 self.arena.rename_property(Animal, "Species", "Family") 773 774 722 775 def setup(SM_class, opts): 723 776 """setup(SM_class, opts). Set up storage for Zoo classes.""" … … 731 784 engines.register_classes(arena) 732 785 733 for cls in (Animal, Zoo, Exhibit, Lecture, Vet, Visit, 734 engines.UnitEngine, 735 engines.UnitCollection, engines.UnitEngineRule): 736 arena.create_storage(cls) 786 ZooSchema(arena).upgrade() 737 787 738 788 trunk/units.py
r126 r127 519 519 except TypeError, x: 520 520 x.args += (self.__class__.__name__, self._properties.keys()) 521 raise x521 raise 522 522 523 523 def dirty(self): … … 539 539 cls.set_property(key, typ, False, descriptor) 540 540 set_properties = classmethod(set_properties) 541 542 def remove_property(cls, key): 543 delattr(cls, key) 544 del cls._properties[key] 545 remove_property = classmethod(remove_property) 541 546 542 547 def indices(cls): … … 549 554 except AttributeError, x: 550 555 x.args += (cls, key) 551 raise x556 raise 552 557 return tuple(product) 553 558 indices = classmethod(indices)
