Changeset 110
- Timestamp:
- 11/26/05 08:19:06
- Files:
-
- trunk/arenas.py (modified) (2 diffs)
- trunk/doc/framework.html (modified) (1 diff)
- trunk/doc/index.html (modified) (2 diffs)
- trunk/doc/managing.html (modified) (5 diffs)
- trunk/doc/modeling.html (modified) (11 diffs)
- trunk/doc/storage.html (modified) (2 diffs)
- trunk/storage/__init__.py (modified) (2 diffs)
- trunk/storage/db.py (modified) (11 diffs)
- trunk/storage/storeado.py (modified) (1 diff)
- trunk/storage/storemysql.py (modified) (1 diff)
- trunk/storage/storeshelve.py (modified) (1 diff)
- trunk/storage/storesqlite.py (modified) (1 diff)
- trunk/test/zoo_fixture.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/arenas.py
r109 r110 272 272 return [x for x in self.xrecall(cls, expr)] 273 273 274 def multirecall(self, *pairs):275 """multirecall( (cls1, expr1), ...) -> [[unit, ...], [unit, ...], ...]276 Recall units of each cls which match eachexpr.277 278 Units of each additional cls /expr pair will be recalled; however,279 only those Units with associations to Units in the PRIMARY set will280 be returned. For you database guys, it's a set of inner joins,281 ALL of which are between the FIRST set and the subsequent set(s).282 283 Instead of single Units, each yielded value will be a tuple of284 Units, in the same order as the cls args were supplied. This285 facilitates unpacking in iterative consumercode like:286 287 for invoice, price in sandbox.multirecall( Invoice, f, Price, None):274 def multirecall(self, classes, expr=None): 275 """multirecall(classes, expr) -> [[unit, ...], [unit, ...], ...] 276 Recall units of each cls if they together match the expr. 277 278 Units of each additional cls pair will be recalled; however, only 279 those Units with associations to Units in the previous set will be 280 returned. For you database guys, it's a set of inner joins, each of 281 which is between each class and its direct antecedent in the list. 282 283 Each yielded value will be a list of Units, in the same order as 284 the classes arg. This facilitates unpacking in iterative consumer 285 code like: 286 287 for invoice, price in sandbox.multirecall([Invoice, Price], f): 288 288 deal_with(invoice) 289 289 deal_with(price) 290 290 """ 291 291 292 self.arena.log("RECALL %s" % ", ".join(["(%s: %s)" % (c.__name__, e) 293 for c, e in pairs]), 294 LOGRECALL) 295 store = self.arena.storage(pairs[0][0]) 296 for c, e in pairs: 292 self.arena.log("RECALL %s %s" % 293 (", ".join([c.__name__ for c in classes]), expr), 294 LOGRECALL) 295 296 store = self.arena.storage(classes[0]) 297 for c in classes[1:]: 297 298 if self.arena.storage(c) is not store: 298 299 raise ValueError(u"multirecall() does not support multiple" … … 304 305 # in read-only scripts, it should be OK for now. But if you mutate 305 306 # Units and then call multirecall, expect inconsistent results. 306 for unitset in store.multirecall( *pairs):307 for unitset in store.multirecall(classes, expr): 307 308 confirmed = True 308 309 for index in xrange(len(unitset)): trunk/doc/framework.html
r109 r110 82 82 supplied, all stored Units of the specified class must be examined. 83 83 </li> 84 <li><tt>multirecall(self, *pairs):</tt> Recommended.84 <li><tt>multirecall(self, classes, expr):</tt> Recommended. 85 85 This method must return an iterable of lists; each item in each list 86 will be a Unit. The Units must be of the supplied class , and87 must match the Expression, for each (cls, expr) pair in *pairs.86 will be a Unit. The Units must be of the supplied classes, in order, 87 and must all match Expression(*resultset) together. 88 88 </li> 89 89 </ul> trunk/doc/index.html
r109 r110 47 47 <li>Associations between Unit Classes 48 48 <ul> 49 <li><tt>related_units</tt> methods</li> 49 <li><tt>Unit.add()</tt></li> 50 <li>"Related units" methods</li> 50 51 </ul> 51 52 </li> … … 65 66 <li>Early binding</li> 66 67 <li>External functions within Expressions</li> 67 <li>Combining Expressions</li>68 68 <li>Using <tt>filter</tt> to form Expressions</li> 69 69 <li>Using <tt>comparison</tt> to form Expressions</li> 70 <li>Combining Expressions</li> 70 71 <li>Exporting the <tt>logic</tt> module</li> 71 72 </ul> trunk/doc/managing.html
r109 r110 34 34 35 35 <p>It may be obvious, but we'll be explicit, here. The lambda which you pass 36 into an Expression must possess a singlepositional argument, which will36 into an Expression must possess a positional argument, which will 37 37 always be bound to a Unit instance. In the example above, it's named 'x', 38 38 but you can use any name you like. Using lambdas as a base means that we … … 41 41 our 'x' object will apply to Unit Properties for that Unit object. 42 42 That is, <tt>x.Date</tt> becomes <tt>unit.Date</tt>.</p> 43 44 <p>You can also do fancier things with Expressions (although the vast 45 majority of the time, you won't need to in order to use Dejavu):</p> 46 <pre>>>> logic.Expression(lambda x, y, z: "Dave" in x.Name and y.Age > 65) 47 logic.Expression(lambda x, y, z: ('Dave' in x.Name) and (y.Age > 65)) 48 >>> logic.Expression(lambda *units, **kw: units and 49 ... (units[0].Width > units[0].Height or 50 ... units[0].Color in kw['Colors'])) 51 logic.Expression(lambda *units, **kw: (units) and 52 ((units[0].Width > units[0].Height) or 53 (units[0].Color in kw['Colors']))) 54 >>> 55 </pre> 43 56 44 57 <h4>Early binding</h4> … … 156 169 below).</p> 157 170 158 <h4>Combining Expressions</h4>159 <p>Expressions are combinable; by using the <tt>&</tt> operator, the two160 expressions are combined with an adjoining logical "and". For example:161 <pre>>>> a = logic.Expression(lambda x: x.Size > 3)162 >>> b = logic.Expression(lambda x: x.Size <= 15)163 >>> c = a & b164 >>> c165 logic.Expression(lambda x: (x.Size > 3) and (x.Size <= 15))</pre>166 The <tt>+</tt> operator works just like the <tt>&</tt> operator. The167 <tt>|</tt> operator combines the two Expressions with a logical 'or'.</p>168 169 171 <h4>Using <tt>filter</tt> to form Expressions</h4> 170 172 <p>The <tt>logic</tt> module also provides convenient methods to … … 210 212 operators (described earlier) to produce more complex Expressions.</p> 211 213 214 <h4>Combining Expressions</h4> 215 <p>Expressions are combinable; by using the <tt>&</tt> operator, the two 216 expressions are combined with an adjoining logical "and". For example: 217 <pre>>>> a = logic.Expression(lambda x: x.Size > 3) 218 >>> b = logic.Expression(lambda x: x.Size <= 15) 219 >>> c = a & b 220 >>> c 221 logic.Expression(lambda x: (x.Size > 3) and (x.Size <= 15))</pre> 222 The <tt>+</tt> operator works just like the <tt>&</tt> operator. The 223 <tt>|</tt> operator combines the two Expressions with a logical 'or'.</p> 224 225 <p>When you combine two Expressions with dissimilar argument lists, 226 what happens? The Expression class doesn't really care what the argument 227 names are, just their order, so the names might not come out as you might 228 expect; however, the logic is preserved:</p> 229 230 <pre>>>> f = logic.filter(Name='Bruce') 231 >>> f 232 logic.Expression(lambda x: x.Name == 'Bruce') 233 >>> g = logic.Expression(lambda a, b, **kw: a.Name + b.Surname == kw['Full Name']) 234 >>> 235 >>> f + g 236 logic.Expression(lambda x, b, **kw: (x.Name == 'Bruce') 237 and (x.Name + b.Surname == kw['Full Name'])) 238 >>> g + f 239 logic.Expression(lambda a, b, **kw: (a.Name + b.Surname == kw['Full Name']) 240 and (a.Name == 'Bruce')) 241 </pre> 242 212 243 <h4>Exporting the <tt>logic</tt> module</h4> 213 244 <p>The <tt>logic</tt> module (and <tt>codewalk</tt>, on which it is built) … … 218 249 219 250 <p>In particular, <tt>logic.Expression</tt> objects can operate on <i>any</i> 220 Python object , not just dejavu <tt>Unit</tt> instances. If you wish to251 Python objects, not just dejavu <tt>Unit</tt> instances. If you wish to 221 252 provide additional logic functions (as dejavu does), simply inject them 222 253 into <tt>logic</tt>'s globals.</p> trunk/doc/modeling.html
r109 r110 75 75 the classmethod <tt class='def'>Unit.set_property()</tt>. For example, 76 76 the following two classes are equivalent: 77 <pre>class Publication(Unit):77 <pre>class Book(Unit): 78 78 Content = UnitProperty(unicode) 79 79 80 class Publication(Unit): pass81 Publication.set_property('Content', unicode)</pre>80 class Book(Unit): pass 81 Book.set_property('Content', unicode)</pre> 82 82 83 83 Declarations outside of the class body allow more dynamic setting of … … 85 85 the <tt class='def'>Unit.set_properties()</tt> classmethod: 86 86 87 <pre>class Publication(Unit): pass88 Publication.set_properties({'Content': unicode,89 'Publisher': unicode,90 'Year': int,91 })</pre>87 <pre>class Book(Unit): pass 88 Book.set_properties({'Content': unicode, 89 'Publisher': unicode, 90 'Year': int, 91 })</pre> 92 92 </p> 93 93 … … 100 100 snippets are equivalent: 101 101 102 <pre>pub = Publication()102 <pre>pub = Book() 103 103 pub.Publisher = 'Walter J. Black' 104 104 pub.Year = 1928 105 105 106 pub = Publication()106 pub = Book() 107 107 pub.adjust(Publisher='Walter J. Black', Year=1928) 108 108 109 pub = Publication(Publisher='Walter J. Black', Year=1928)</pre>109 pub = Book(Publisher='Walter J. Black', Year=1928)</pre> 110 110 </p> 111 111 … … 292 292 An example recall operation: 293 293 <pre>>>> e = logic.Expression(lambda x: x.Year == 1928) 294 >>> units = box.recall( Publication, e)294 >>> units = box.recall(Book, e) 295 295 >>> [x.Title for x in units] 296 296 [u'The Giant Horse of Oz', u'Kai Lung Unrolls His Mat', … … 319 319 "property_name=value". The method will form an equivalent Expression 320 320 for you from the keyword args. For example: 321 <pre>>>> book = box.unit( Publication, ID=1)321 <pre>>>> book = box.unit(Book, ID=1) 322 322 >>> if book: 323 323 ... print book.Title … … 328 328 329 329 <h5>multirecall()</h5> 330 <p>The <tt class='def'>multirecall</tt> method will take multiple pairs of 331 (<tt>cls</tt>, <tt>expr</tt>), and return a series of unitsets. Each 332 unitset will be a list of units, one per cls passed in to the method. 333 <pre>pubs = box.multirecall((Publisher, logic.filter(ID=4)), 334 (Publication, None))</pre> 335 This example will retrieve a list of (Publisher, Publication) pairs.</p> 330 <p>The <tt class='def'>multirecall(classes, expr)</tt> method yields 331 a series of unitsets. Each unitset will be a list of units, one per 332 class in the <tt>classes</tt> arg. The <tt>expr</tt> arg should be a 333 <tt>logic.Expression</tt> which can evaluate all of the units in 334 any given unitset at once. 335 <pre>pubs = box.multirecall((Publisher, Book), 336 logic.Expression(lambda p, b: p.ID == 4))</pre> 337 This example will retrieve a series of (Publisher, Book) pairs.</p> 336 338 337 339 <p>In database terminology, the multirecall method performs a series of 338 full inner joins between the first unit class and each subsequent class.339 That is, class 2 is joined to class 1, then class 3 is joined to class 1, 340 etcetera.Since each join is an inner join, the result set is guaranteed340 full inner joins between each class and its neighbor. That is, class 1 341 is joined to class 2, then class 2 is joined to class 3, etcetera. 342 Since each join is an inner join, the result set is guaranteed 341 343 to contain a complete set of units for each iteration; however, repeated 342 344 units will reference the same object; in the example above, each Publisher … … 344 346 Publisher. So we might iterate over "pubs" multiple times, but each time, 345 347 the first unit in the set will be the same unit instance.</p> 348 349 <p>The relationships (joins) between each class are specified by 350 Unit Associations (see <a href='#associations'>below</a>).</p> 346 351 347 352 <h4>Forgetting and Repressing</h4> … … 428 433 429 434 430 < h3>Associations between Unit Classes</h3>435 <a name='associations'><h3>Associations between Unit Classes</h3></a> 431 436 <p>Once you've put together some Unit classes, chances are you're going to 432 437 want to associate them. Generally, this is accomplished by creating a … … 496 501 </p> 497 502 498 <h4> <tt>related_units</tt>methods</h4>503 <h4>"Related units" methods</h4> 499 504 <p>To make querying easier, each of the two Unit classes will gain a new 500 <i>related_units</i>method which simplifies looking up related instances505 "related units" method which simplifies looking up related instances 501 506 of the other class. The new method for Unit_B will have the name of Unit_A, 502 507 and vice-versa. In our example: … … 517 522 We've only created three Biographies at this point, so we can print the list 518 523 easily. At the other extreme (when you have hundreds of Biographies to filter), 519 you can pass an optional <tt>Expression</tt> object to the related_unitsmethod.524 you can pass an optional <tt>Expression</tt> object to the "related units" method. 520 525 When you do, the list of associated Units will be filtered accordingly.</p> 521 526 522 527 <p>Notice that, because our relationship is one-to-many, <b>the two 523 <tt>related_units</tt>methods behave differently</b>. The "one"528 "related units" methods behave differently</b>. The "one" 524 529 (Archaeologist) which is retrieving the "many" (Biography) retrieves 525 530 a list. The "many" retrieving the "one" retrieves a single Unit. … … 530 535 would have returned lists).</p> 531 536 532 <p>Because the related_unitsmethod names are formed automatically, you need537 <p>Because the "related units" method names are formed automatically, you need 533 538 to take care not to use the names of Unit classes for your Unit properties. 534 539 In our example, we used "ArchID" for the name of our "foreign key". If we 535 540 had used "Archaeologist" instead, we would have had problems; when we 536 541 associated the classes, the <i>property</i> named "Archaeologist" would 537 have collided with the <i> related_unitsmethod</i> named "Archaeologist".542 have collided with the <i>"related units" method</i> named "Archaeologist". 538 543 Be careful when naming your properties, and plan for the future. The best 539 544 approach is probably to end your property name with "ID" every time.</p> 540 545 541 546 <p>Unlike some other ORM's, Dejavu doesn't cache far Units within the near 542 Unit. Each time you call the related_unitsmethod, the data is recalled547 Unit. Each time you call the "related units" method, the data is recalled 543 548 from your Sandbox. It is quite probable that those far Units are still 544 549 sitting in memory in the Sandbox, but they're not going to persist in 545 550 the near Unit itself in any way.</p> 546 551 547 <p>Finally, some of you may want to override the default related_units552 <p>Finally, some of you may want to override the default "related units" 548 553 methods. Feel free; <tt>Unit.associate</tt> takes two optional arguments, 549 554 which should be subclasses of the UnitAssociation descriptor. See the trunk/doc/storage.html
r109 r110 249 249 <h5>Caching Proxy</h5> 250 250 <p>Use this class to persist Units in memory between client connections. 251 It usually proxies another Storage Manager. At this time, the Caching Proxy 252 doesn't implement distinct() or multirecall(). Configuration entries:</p> 251 It usually proxies another Storage Manager. Configuration entries:</p> 253 252 <ul> 254 253 <li><b>Class:</b> <tt>dejavu.storage.CachingProxy</tt></li> … … 281 280 this Storage Manager recalls all Units at once upon the first request, 282 281 and won't recall them again from storage. They are "burned" into memory 283 for the lifetime of the application. At this time, the Burned Proxy 284 doesn't implement distinct() or multirecall(). Configuration entries:</p> 282 for the lifetime of the application. Configuration entries:</p> 285 283 <ul> 286 284 <li><b>Class:</b> <tt>dejavu.storage.BurnedProxy</tt></li> trunk/storage/__init__.py
r109 r110 58 58 raise NotImplementedError 59 59 60 def multirecall(self, *pairs):61 """multirecall( *pairs) -> Full inner join units for each (cls, expr) pair."""60 def multirecall(self, classes, expr): 61 """multirecall(classes, expr) -> Full inner join units from each class.""" 62 62 raise NotImplementedError 63 63 … … 109 109 return self.nextstore.distinct(cls, attrs, expr) 110 110 111 def multirecall(self, *pairs):112 """multirecall( *pairs) -> Full inner join units for each (cls, expr) pair."""113 if self.nextstore: 114 return self.nextstore.multirecall( *pairs)111 def multirecall(self, classes, expr): 112 """multirecall(classes, expr) -> Full inner join units from each class.""" 113 if self.nextstore: 114 return self.nextstore.multirecall(classes, expr) 115 115 116 116 trunk/storage/db.py
r109 r110 392 392 393 393 394 class TableRef: 395 def __init__(self, tablename): 396 self.tablename = tablename 397 394 398 # Stack sentinels 395 399 class Sentinel(object): … … 401 405 return 'Stack Sentinel: %s' % self.name 402 406 403 table_arg = Sentinel('Table Arg')404 407 kw_arg = Sentinel('Keyword Arg') 405 408 # cannot_represent exists so that a portion of an Expression can be … … 430 433 sql_cmp_op = ('<', '<=', '=', '!=', '>', '>=', 'in', 'not in') 431 434 432 def __init__(self, tablename , expr, adapter=AdapterToSQL()):433 self.tablename = tablename435 def __init__(self, tablenames, expr, adapter=AdapterToSQL()): 436 self.tablenames = tablenames 434 437 self.expr = expr 435 438 self.adapter = adapter … … 506 509 507 510 def visit_LOAD_FAST(self, lo, hi): 508 if lo + (hi << 8) < self.co_argcount: 509 self.stack.append(table_arg) 511 arg_index = lo + (hi << 8) 512 if arg_index < self.co_argcount: 513 self.stack.append(TableRef(self.tablenames[arg_index])) 510 514 else: 511 515 self.stack.append(kw_arg) … … 514 518 name = self.co_names[lo + (hi << 8)] 515 519 tos = self.stack.pop() 516 if tos is table_arg:520 if isinstance(tos, TableRef): 517 521 # Call another function to make subclassing easier. 518 self.stack.append(self.column_name( name))522 self.stack.append(self.column_name(tos.tablename, name)) 519 523 else: 520 524 # tos.name will reference an attribute of the tos object. … … 628 632 self.stack.append("NOT (" + op + ")") 629 633 630 def column_name(self, name):634 def column_name(self, tablename, name): 631 635 # This is valid SQL for PostgreSQL only and should be overridden. 632 636 # If you want to use a map from UnitProperty names to legacy DB … … 634 638 # want to override StorageManager.identifier and perform the 635 639 # same map lookup there. 636 return '%s."%s"' % ( self.tablename, name)640 return '%s."%s"' % (tablename, name) 637 641 638 642 # --------------------------- Dispatchees --------------------------- # … … 884 888 else: 885 889 sql = u'SELECT * FROM %s' % tablename 886 w, i = self.where( unitClass, expr)890 w, i = self.where((self.tablename(unitClass),), expr) 887 891 if len(w) > 0: 888 892 w = u" WHERE " + w … … 892 896 return sql, i 893 897 894 def where(self, cls, expr):895 decom = self.decompiler( self.tablename(cls), expr, self.toAdapter)898 def where(self, tablenames, expr): 899 decom = self.decompiler(tablenames, expr, self.toAdapter) 896 900 return decom.code(), decom.imperfect 897 901 … … 1131 1135 for row in data] 1132 1136 1133 def multiselect(self, pairs):1137 def multiselect(self, classes, expr): 1134 1138 t = self.tablename 1135 1139 i = self.identifier 1136 firstcls = pairs[0][0] 1137 tablenames = [] 1140 1141 tablenames = [t(cls) for cls in classes] 1142 if expr is None: 1143 expr = logic.Expression(lambda *args: True) 1144 w, imp = self.where(tablenames, expr) 1145 1138 1146 # Because various databases may mangle column names, we explicitly 1139 1147 # order the requested columns (instead of using *). 1140 1148 columns = [] 1141 wheres = [] 1142 imps = [] 1143 1144 for cls, expr in pairs: 1145 tablenames.append(t(cls)) 1149 joins = [] 1150 basecls = firstcls = classes[0] 1151 for cls in classes: 1146 1152 # Place the ID property first in case others depend upon it. 1147 1153 keys = ['ID'] + [k for k in cls.properties() if k != 'ID'] 1148 1154 columns.extend([(cls, k) for k in keys]) 1149 1155 1150 if expr is None:1151 expr = logic.Expression(lambda x: True)1152 w, imp = self.where(cls, expr)1153 wheres.append(w)1154 imps.append(imp)1155 1156 1156 if cls is not firstcls: 1157 spath = self.arena.associations.shortest_path( firstcls, cls)1158 # Thisshould be firstcls in every case.1157 spath = self.arena.associations.shortest_path(basecls, cls) 1158 # cls1 should be firstcls in every case. 1159 1159 cls1 = spath.pop(0) 1160 1160 for cls2 in spath: 1161 1161 ua = cls1._associations[cls2.__name__] 1162 wheres.append("(%s.%s = %s.%s)" %1163 (t(cls1), i(ua.nearKey),1164 t(cls2), i(ua.farKey)))1162 joins.append("(%s.%s = %s.%s)" % (t(cls1), i(ua.nearKey), 1163 t(cls2), i(ua.farKey))) 1164 tablenames.append(t(cls2)) 1165 1165 cls1 = cls2 1166 1167 # Remove any duplicate entries in the where clauses 1168 # (there may be several from the _join_clauses). 1169 wheres = dict.fromkeys(wheres).keys() 1170 1171 names = ["%s.%s" % (t(cls), i(key)) for cls, key in columns] 1172 tbls = u', '.join(tablenames) 1173 w = u' AND '.join(wheres) 1174 statement = "SELECT %s FROM %s WHERE %s" % (u', '.join(names), tbls, w) 1175 return statement, imps, columns 1176 1177 def multirecall(self, *pairs): 1178 """multirecall(*pairs) -> Full inner join units for each (cls, expr) pair.""" 1179 sql, imps, supplied_cols = self.multiselect(pairs) 1166 basecls = cls 1167 1168 # Remove any duplicate entries in the join clauses. 1169 # Note that we assume join clauses are perfect. 1170 joins = dict.fromkeys(joins).keys() 1171 tablenames = dict.fromkeys(tablenames).keys() 1172 1173 w = u' AND '.join([w] + joins) 1174 1175 colnames = ["%s.%s" % (t(cls), i(key)) for cls, key in columns] 1176 statement = ("SELECT %s FROM %s WHERE %s" % 1177 (u', '.join(colnames), u', '.join(tablenames), w)) 1178 return statement, imp, columns 1179 1180 def multirecall(self, classes, expr): 1181 """multirecall(classes, expr) -> Full inner join units.""" 1182 sql, imp, supplied_cols = self.multiselect(classes, expr) 1180 1183 data, recvd_cols = self.fetch(sql) 1181 1184 … … 1211 1214 index += 1 1212 1215 1216 unitset = [] 1217 for cls in classes: 1218 unit = units[cls] 1219 unit.cleanse() 1220 unitset.append(unit) 1221 1213 1222 # If our SQL is imperfect, don't yield units to the 1214 1223 # caller unless they pass expr(unit). 1215 1224 acceptable = True 1216 unitset = [] 1217 for pair, imp in zip(pairs, imps): 1218 c, e = pair 1219 unit = units[c] 1220 unit.cleanse() 1221 if imp: 1222 acceptable &= e(unit) 1223 if not acceptable: 1224 break 1225 unitset.append(unit) 1225 if imp: 1226 acceptable = expr(*unitset) 1226 1227 if acceptable: 1227 1228 yield unitset trunk/storage/storeado.py
r109 r110 250 250 self.stack.append(op1 + " " + self.sql_cmp_op[op] + " " + op2) 251 251 252 def column_name(self, name):253 return '%s.[%s]' % ( self.tablename, name)252 def column_name(self, tablename, name): 253 return '%s.[%s]' % (tablename, name) 254 254 255 255 # --------------------------- Dispatchees --------------------------- # trunk/storage/storemysql.py
r109 r110 53 53 class MySQLDecompiler(db.SQLDecompiler): 54 54 55 def column_name(self, name):55 def column_name(self, tablename, name): 56 56 # MySQL forces lowercase column names. 57 return '%s.`%s`' % ( self.tablename, name.lower())57 return '%s.`%s`' % (tablename, name.lower()) 58 58 59 59 # --------------------------- Dispatchees --------------------------- # trunk/storage/storeshelve.py
r109 r110 161 161 lock.release() 162 162 163 def multirecall(self, *pairs): 164 """multirecall(*pairs) -> Full inner join units for each (cls, expr) pair.""" 165 pairs = list(pairs) 166 firstcls, firstexpr = pairs.pop(0) 167 masterset = [[x] for x in self.recall(firstcls, firstexpr)] 168 169 for cls, expr in pairs: 170 tests = [] 171 spath = self.arena.associations.shortest_path(firstcls, cls) 172 cls1 = spath.pop(0) # This should be firstcls in every case. 173 for cls2 in spath: 174 subset = [x for x in self.recall(cls2, expr)] 175 ua = cls1._associations[cls2.__name__] 176 tests.append((subset, ua.nearKey, ua.farKey)) 177 cls1 = cls2 163 def multirecall(self, classes, expr): 164 """multirecall(classes, expr) -> Full inner join units.""" 165 166 firstcls = classes[0] 167 tables = {} 168 joins = dict([(cls, None) for cls in classes]) 169 # TODO: deconstruct expr into a set of subexpr's, one for 170 # each class in classes. 171 filters = dict([(cls, None) for cls in classes]) 172 173 def combine(nearValue, farKey, *classes): 174 classes = list(classes) 175 thiscls = classes.pop(0) 178 176 179 newmasterset = [] 180 for row in masterset: 181 list1 = [row[0]] 182 for subset, leftkey, rightkey in tests: 183 matches = [] 184 for unit1 in list1: 185 matches.extend([unit2 for unit2 in subset 186 if getattr(unit1, leftkey) 187 == getattr(unit2, rightkey)]) 188 list1 = matches 177 # Use cached table if present 178 cached = (thiscls in tables) 179 if cached: 180 table = tables[thiscls] 181 else: 182 table = self.recall(thiscls, filters[thiscls]) 183 tables[thiscls] = newcache = [] 184 185 nextClass = None 186 if classes: 187 nextClass = classes[0] 188 ua = thiscls._associations[nextClass.__name__] 189 nextNearKey, nextFarKey = ua.nearKey, ua.farKey 190 191 for unit in table: 192 # Note that the caching happens only if the optimization 193 # filters succeed; however, it doesn't depend on whether 194 # the join test succeeds or fails. 195 if not cached: 196 newcache.append(unit) 189 197 190 # Take the final matches and join each with the current row. 191 for unit in matches: 192 newmasterset.append(row + [unit]) 193 masterset = newmasterset 194 195 return masterset 196 198 # Test against join constraint 199 if farKey and getattr(unit, farKey) != nearValue: 200 continue 201 202 if nextClass: 203 newNearVal = getattr(unit, nextNearKey) 204 for subunits in combine(newNearVal, nextFarKey, *classes): 205 yield [unit,] + subunits 206 else: 207 yield [unit,] 208 209 for unitrow in combine(None, None, *classes): 210 if expr(*unitrow): 211 yield unitrow 212 trunk/storage/storesqlite.py
r109 r110 43 43 class SQLiteDecompiler(db.SQLDecompiler): 44 44 45 def column_name(self, name):46 return '%s.[%s]' % ( self.tablename, name)45 def column_name(self, tablename, name): 46 return '%s.[%s]' % (tablename, name) 47 47 48 48 # --------------------------- Dispatchees --------------------------- # trunk/test/zoo_fixture.py
r109 r110 401 401 self.assertEqual(escapees, [4]) 402 402 403 def test_6_Multiselect(self): 404 box = arena.new_sandbox() 405 f = logic.filter(Name='San Diego Zoo') 403 def test_6_Multirecall(self): 404 # Multirecall isn't designed with caching proxies in mind. 405 # If we use any, sweep out all their units before proceeding. 406 for store in arena.stores.itervalues(): 407 if hasattr(store, "sweep_all"): 408 store.sweep_all() 409 410 box = arena.new_sandbox() 411 412 f = logic.Expression(lambda z, a: z.Name == 'San Diego Zoo') 406 413 zooed_animals = [(z, a) for z, a in 407 box.multirecall((Zoo, f), (Animal, None))] 414 box.multirecall([Zoo, Animal], f)] 415 self.assertEqual(len(zooed_animals), 2) 416 408 417 SDZ = box.unit(Zoo, Name='San Diego Zoo') 409 self.assertEqual(len(zooed_animals), 2)410 418 aid = 0 411 419 for z, a in zooed_animals: … … 414 422 aid = id(a) 415 423 416 # Assert that multirecalls with no matching secondaryunits returns417 # no matches for the initial class .418 leo = logic. filter(Species='Leopard')424 # Assert that multirecalls with no matching related units returns 425 # no matches for the initial class, since all joins are INNER. 426 leo = logic.Expression(lambda z, a: a.Species == 'Leopard') 419 427 zooed_animals = [(z, a) for z, a in 420 box.multirecall( (Zoo, f), (Animal, leo))]428 box.multirecall([Zoo, Animal], f + leo)] 421 429 self.assertEqual(len(zooed_animals), 0) 430 431 # Try a multiple-arg expression 432 f = logic.Expression(lambda a, z: a.Legs >= 4 and z.Admission < 10) 433 animal_zoos = [(a, z) for a, z in box.multirecall([Animal, Zoo], f)] 434 self.assertEqual(len(animal_zoos), 3) 435 names = [a.Species for a, z in animal_zoos] 436 names.sort() 437 self.assertEqual(names, ['Leopard', 'Millipede', 'Tiger']) 438 439 # Let's try three joined classes just for the sadistic fun of it. 440 f = logic.Expression(lambda a, z, v: z.Name == 'Sea_World') 441 azv = [(a, z, v) for a, z, v in 442 box.multirecall([Animal, Zoo, Vet], f)] 443 self.assertEqual(len(azv), 2) 422 444 423 445 def test_7_Multithreading(self):
