Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

I think I've seen this ORM somewhere before...

Changeset 110

Show
Ignore:
Timestamp:
11/26/05 08:19:06
Author:
fumanchu
Message:

multirecall changes (BACKWARD INCOMPATIBLE):

  1. The signature has changed; multirecall now takes two arguments, a 'classes' tuple or list, and an 'expr'. The expr will have multiple *args, one for each class in 'classes'. [Hopefully, this will allow OUTER JOINs in the near future!]
  2. The behavior has changed; the pairs used to each be related to the first class; now, each class is related (INNER JOIN'ed) to the class before it in the 'classes' argument.
  3. db.SQLDecompiler has changed to handling multiple tables (*args) for a single Expression.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/arenas.py

    r109 r110  
    272272        return [x for x in self.xrecall(cls, expr)] 
    273273     
    274     def multirecall(self, *pairs): 
    275         """multirecall((cls1, expr1), ...) -> [[unit, ...], [unit, ...], ...] 
    276         Recall units of each cls which match each expr. 
    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 will 
    280         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 of 
    284         Units, in the same order as the cls args were supplied. This 
    285         facilitates unpacking in iterative consumer code 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): 
    288288            deal_with(invoice) 
    289289            deal_with(price) 
    290290        """ 
    291291         
    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:]: 
    297298            if self.arena.storage(c) is not store: 
    298299                raise ValueError(u"multirecall() does not support multiple" 
     
    304305        # in read-only scripts, it should be OK for now. But if you mutate 
    305306        # Units and then call multirecall, expect inconsistent results. 
    306         for unitset in store.multirecall(*pairs): 
     307        for unitset in store.multirecall(classes, expr): 
    307308            confirmed = True 
    308309            for index in xrange(len(unitset)): 
  • trunk/doc/framework.html

    r109 r110  
    8282        supplied, all stored Units of the specified class must be examined. 
    8383        </li> 
    84     <li><tt>multirecall(self, *pairs):</tt> Recommended. 
     84    <li><tt>multirecall(self, classes, expr):</tt> Recommended. 
    8585        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, and 
    87         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
    8888        </li> 
    8989</ul> 
  • trunk/doc/index.html

    r109 r110  
    4747    <li>Associations between Unit Classes 
    4848        <ul> 
    49         <li><tt>related_units</tt> methods</li> 
     49        <li><tt>Unit.add()</tt></li> 
     50        <li>"Related units" methods</li> 
    5051        </ul> 
    5152    </li> 
     
    6566        <li>Early binding</li> 
    6667        <li>External functions within Expressions</li> 
    67         <li>Combining Expressions</li> 
    6868        <li>Using <tt>filter</tt> to form Expressions</li> 
    6969        <li>Using <tt>comparison</tt> to form Expressions</li> 
     70        <li>Combining Expressions</li> 
    7071        <li>Exporting the <tt>logic</tt> module</li> 
    7172        </ul> 
  • trunk/doc/managing.html

    r109 r110  
    3434 
    3535<p>It may be obvious, but we'll be explicit, here. The lambda which you pass 
    36 into an Expression must possess a single positional argument, which will 
     36into an Expression must possess a positional argument, which will 
    3737always be bound to a Unit instance. In the example above, it's named 'x', 
    3838but you can use any name you like. Using lambdas as a base means that we 
     
    4141our 'x' object will apply to Unit Properties for that Unit object. 
    4242That 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 
     45majority 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) 
     47logic.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'])) 
     51logic.Expression(lambda *units, **kw: (units) and 
     52                 ((units[0].Width > units[0].Height) or 
     53                  (units[0].Color in kw['Colors']))) 
     54>>>  
     55</pre> 
    4356 
    4457<h4>Early binding</h4> 
     
    156169below).</p> 
    157170 
    158 <h4>Combining Expressions</h4> 
    159 <p>Expressions are combinable; by using the <tt>&</tt> operator, the two 
    160 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 & b 
    164 >>> c 
    165 logic.Expression(lambda x: (x.Size > 3) and (x.Size <= 15))</pre> 
    166 The <tt>+</tt> operator works just like the <tt>&</tt> operator. The 
    167 <tt>|</tt> operator combines the two Expressions with a logical 'or'.</p> 
    168  
    169171<h4>Using <tt>filter</tt> to form Expressions</h4> 
    170172<p>The <tt>logic</tt> module also provides convenient methods to 
     
    210212operators (described earlier) to produce more complex Expressions.</p> 
    211213 
     214<h4>Combining Expressions</h4> 
     215<p>Expressions are combinable; by using the <tt>&</tt> operator, the two 
     216expressions 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 
     221logic.Expression(lambda x: (x.Size > 3) and (x.Size <= 15))</pre> 
     222The <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, 
     226what happens? The Expression class doesn't really care what the argument  
     227names are, just their order, so the names might not come out as you might 
     228expect; however, the logic is preserved:</p> 
     229 
     230<pre>>>> f = logic.filter(Name='Bruce') 
     231>>> f 
     232logic.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 
     236logic.Expression(lambda x, b, **kw: (x.Name == 'Bruce') 
     237                 and (x.Name + b.Surname == kw['Full Name'])) 
     238>>> g + f 
     239logic.Expression(lambda a, b, **kw: (a.Name + b.Surname == kw['Full Name']) 
     240                 and (a.Name == 'Bruce')) 
     241</pre> 
     242 
    212243<h4>Exporting the <tt>logic</tt> module</h4> 
    213244<p>The <tt>logic</tt> module (and <tt>codewalk</tt>, on which it is built) 
     
    218249 
    219250<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 to 
     251Python objects, not just dejavu <tt>Unit</tt> instances. If you wish to 
    221252provide additional logic functions (as dejavu does), simply inject them 
    222253into <tt>logic</tt>'s globals.</p> 
  • trunk/doc/modeling.html

    r109 r110  
    7575the classmethod <tt class='def'>Unit.set_property()</tt>. For example, 
    7676the following two classes are equivalent: 
    77 <pre>class Publication(Unit): 
     77<pre>class Book(Unit): 
    7878    Content = UnitProperty(unicode) 
    7979 
    80 class Publication(Unit): pass 
    81 Publication.set_property('Content', unicode)</pre> 
     80class Book(Unit): pass 
     81Book.set_property('Content', unicode)</pre> 
    8282 
    8383Declarations outside of the class body allow more dynamic setting of 
     
    8585the <tt class='def'>Unit.set_properties()</tt> classmethod: 
    8686 
    87 <pre>class Publication(Unit): pass 
    88 Publication.set_properties({'Content': unicode, 
    89                             'Publisher': unicode, 
    90                             'Year': int, 
    91                             })</pre> 
     87<pre>class Book(Unit): pass 
     88Book.set_properties({'Content': unicode, 
     89                     'Publisher': unicode, 
     90                     'Year': int, 
     91                     })</pre> 
    9292</p> 
    9393 
     
    100100snippets are equivalent: 
    101101 
    102 <pre>pub = Publication() 
     102<pre>pub = Book() 
    103103pub.Publisher = 'Walter J. Black' 
    104104pub.Year = 1928 
    105105 
    106 pub = Publication() 
     106pub = Book() 
    107107pub.adjust(Publisher='Walter J. Black', Year=1928) 
    108108 
    109 pub = Publication(Publisher='Walter J. Black', Year=1928)</pre> 
     109pub = Book(Publisher='Walter J. Black', Year=1928)</pre> 
    110110</p> 
    111111 
     
    292292An example recall operation: 
    293293<pre>>>> e = logic.Expression(lambda x: x.Year == 1928) 
    294 >>> units = box.recall(Publication, e) 
     294>>> units = box.recall(Book, e) 
    295295>>> [x.Title for x in units] 
    296296[u'The Giant Horse of Oz', u'Kai Lung Unrolls His Mat', 
     
    319319"property_name=value". The method will form an equivalent Expression 
    320320for you from the keyword args. For example: 
    321 <pre>>>> book = box.unit(Publication, ID=1) 
     321<pre>>>> book = box.unit(Book, ID=1) 
    322322>>> if book: 
    323323...     print book.Title 
     
    328328 
    329329<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 
     331a series of unitsets. Each unitset will be a list of units, one per 
     332class 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 
     334any given unitset at once. 
     335<pre>pubs = box.multirecall((Publisher, Book), 
     336                       logic.Expression(lambda p, b: p.ID == 4))</pre> 
     337This example will retrieve a series of (Publisher, Book) pairs.</p> 
    336338 
    337339<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 guaranteed 
     340full inner joins between each class and its neighbor. That is, class 1 
     341is joined to class 2, then class 2 is joined to class 3, etcetera. 
     342Since each join is an inner join, the result set is guaranteed 
    341343to contain a complete set of units for each iteration; however, repeated 
    342344units will reference the same object; in the example above, each Publisher 
     
    344346Publisher. So we might iterate over "pubs" multiple times, but each time, 
    345347the first unit in the set will be the same unit instance.</p> 
     348 
     349<p>The relationships (joins) between each class are specified by 
     350Unit Associations (see <a href='#associations'>below</a>).</p> 
    346351 
    347352<h4>Forgetting and Repressing</h4> 
     
    428433 
    429434 
    430 <h3>Associations between Unit Classes</h3
     435<a name='associations'><h3>Associations between Unit Classes</h3></a
    431436<p>Once you've put together some Unit classes, chances are you're going to 
    432437want to associate them. Generally, this is accomplished by creating a 
     
    496501</p> 
    497502 
    498 <h4><tt>related_units</tt> methods</h4> 
     503<h4>"Related units" methods</h4> 
    499504<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 instances 
     505"related units" method which simplifies looking up related instances 
    501506of the other class. The new method for Unit_B will have the name of Unit_A, 
    502507and vice-versa. In our example: 
     
    517522We've only created three Biographies at this point, so we can print the list 
    518523easily. 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_units method. 
     524you can pass an optional <tt>Expression</tt> object to the "related units" method. 
    520525When you do, the list of associated Units will be filtered accordingly.</p> 
    521526 
    522527<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" 
    524529(Archaeologist) which is retrieving the "many" (Biography) retrieves 
    525530a list. The "many" retrieving the "one" retrieves a single Unit. 
     
    530535would have returned lists).</p> 
    531536 
    532 <p>Because the related_units method names are formed automatically, you need 
     537<p>Because the "related units" method names are formed automatically, you need 
    533538to take care not to use the names of Unit classes for your Unit properties. 
    534539In our example, we used "ArchID" for the name of our "foreign key". If we 
    535540had used "Archaeologist" instead, we would have had problems; when we 
    536541associated the classes, the <i>property</i> named "Archaeologist" would 
    537 have collided with the <i>related_units method</i> named "Archaeologist". 
     542have collided with the <i>"related units" method</i> named "Archaeologist". 
    538543Be careful when naming your properties, and plan for the future. The best 
    539544approach is probably to end your property name with "ID" every time.</p> 
    540545 
    541546<p>Unlike some other ORM's, Dejavu doesn't cache far Units within the near 
    542 Unit. Each time you call the related_units method, the data is recalled 
     547Unit. Each time you call the "related units" method, the data is recalled 
    543548from your Sandbox. It is quite probable that those far Units are still 
    544549sitting in memory in the Sandbox, but they're not going to persist in 
    545550the near Unit itself in any way.</p> 
    546551 
    547 <p>Finally, some of you may want to override the default related_units 
     552<p>Finally, some of you may want to override the default "related units" 
    548553methods. Feel free; <tt>Unit.associate</tt> takes two optional arguments, 
    549554which should be subclasses of the UnitAssociation descriptor. See the 
  • trunk/doc/storage.html

    r109 r110  
    249249<h5>Caching Proxy</h5> 
    250250<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> 
     251It usually proxies another Storage Manager. Configuration entries:</p> 
    253252<ul> 
    254253    <li><b>Class:</b> <tt>dejavu.storage.CachingProxy</tt></li> 
     
    281280this Storage Manager recalls all Units at once upon the first request, 
    282281and 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> 
     282for the lifetime of the application. Configuration entries:</p> 
    285283<ul> 
    286284    <li><b>Class:</b> <tt>dejavu.storage.BurnedProxy</tt></li> 
  • trunk/storage/__init__.py

    r109 r110  
    5858        raise NotImplementedError 
    5959     
    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.""" 
    6262        raise NotImplementedError 
    6363 
     
    109109            return self.nextstore.distinct(cls, attrs, expr) 
    110110     
    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
    115115 
    116116 
  • trunk/storage/db.py

    r109 r110  
    392392 
    393393 
     394class TableRef: 
     395    def __init__(self, tablename): 
     396        self.tablename = tablename 
     397 
    394398# Stack sentinels 
    395399class Sentinel(object): 
     
    401405        return 'Stack Sentinel: %s' % self.name 
    402406 
    403 table_arg = Sentinel('Table Arg') 
    404407kw_arg = Sentinel('Keyword Arg') 
    405408# cannot_represent exists so that a portion of an Expression can be 
     
    430433    sql_cmp_op = ('<', '<=', '=', '!=', '>', '>=', 'in', 'not in') 
    431434     
    432     def __init__(self, tablename, expr, adapter=AdapterToSQL()): 
    433         self.tablename = tablename 
     435    def __init__(self, tablenames, expr, adapter=AdapterToSQL()): 
     436        self.tablenames = tablenames 
    434437        self.expr = expr 
    435438        self.adapter = adapter 
     
    506509     
    507510    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])) 
    510514        else: 
    511515            self.stack.append(kw_arg) 
     
    514518        name = self.co_names[lo + (hi << 8)] 
    515519        tos = self.stack.pop() 
    516         if tos is table_arg
     520        if isinstance(tos, TableRef)
    517521            # 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)) 
    519523        else: 
    520524            # tos.name will reference an attribute of the tos object. 
     
    628632            self.stack.append("NOT (" + op + ")") 
    629633     
    630     def column_name(self, name): 
     634    def column_name(self, tablename, name): 
    631635        # This is valid SQL for PostgreSQL only and should be overridden. 
    632636        # If you want to use a map from UnitProperty names to legacy DB 
     
    634638        # want to override StorageManager.identifier and perform the 
    635639        # same map lookup there. 
    636         return '%s."%s"' % (self.tablename, name) 
     640        return '%s."%s"' % (tablename, name) 
    637641     
    638642    # --------------------------- Dispatchees --------------------------- # 
     
    884888        else: 
    885889            sql = u'SELECT * FROM %s' % tablename 
    886         w, i = self.where(unitClass, expr) 
     890        w, i = self.where((self.tablename(unitClass),), expr) 
    887891        if len(w) > 0: 
    888892            w = u" WHERE " + w 
     
    892896        return sql, i 
    893897     
    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) 
    896900        return decom.code(), decom.imperfect 
    897901     
     
    11311135                     for row in data] 
    11321136     
    1133     def multiselect(self, pairs): 
     1137    def multiselect(self, classes, expr): 
    11341138        t = self.tablename 
    11351139        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         
    11381146        # Because various databases may mangle column names, we explicitly 
    11391147        # order the requested columns (instead of using *). 
    11401148        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: 
    11461152            # Place the ID property first in case others depend upon it. 
    11471153            keys = ['ID'] + [k for k in cls.properties() if k != 'ID'] 
    11481154            columns.extend([(cls, k) for k in keys]) 
    11491155             
    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              
    11561156            if cls is not firstcls: 
    1157                 spath = self.arena.associations.shortest_path(firstcls, cls) 
    1158                 # This should be firstcls in every case. 
     1157                spath = self.arena.associations.shortest_path(basecls, cls) 
     1158                # cls1 should be firstcls in every case. 
    11591159                cls1 = spath.pop(0) 
    11601160                for cls2 in spath: 
    11611161                    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)) 
    11651165                    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) 
    11801183        data, recvd_cols = self.fetch(sql) 
    11811184         
     
    12111214                index += 1 
    12121215             
     1216            unitset = [] 
     1217            for cls in classes: 
     1218                unit = units[cls] 
     1219                unit.cleanse() 
     1220                unitset.append(unit) 
     1221             
    12131222            # If our SQL is imperfect, don't yield units to the 
    12141223            # caller unless they pass expr(unit). 
    12151224            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) 
    12261227            if acceptable: 
    12271228                yield unitset 
  • trunk/storage/storeado.py

    r109 r110  
    250250            self.stack.append(op1 + " " + self.sql_cmp_op[op] + " " + op2) 
    251251     
    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) 
    254254     
    255255    # --------------------------- Dispatchees --------------------------- # 
  • trunk/storage/storemysql.py

    r109 r110  
    5353class MySQLDecompiler(db.SQLDecompiler): 
    5454     
    55     def column_name(self, name): 
     55    def column_name(self, tablename, name): 
    5656        # MySQL forces lowercase column names. 
    57         return '%s.`%s`' % (self.tablename, name.lower()) 
     57        return '%s.`%s`' % (tablename, name.lower()) 
    5858     
    5959    # --------------------------- Dispatchees --------------------------- # 
  • trunk/storage/storeshelve.py

    r109 r110  
    161161            lock.release() 
    162162     
    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) 
    178176             
    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) 
    189197                 
    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  
    4343class SQLiteDecompiler(db.SQLDecompiler): 
    4444     
    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) 
    4747     
    4848    # --------------------------- Dispatchees --------------------------- # 
  • trunk/test/zoo_fixture.py

    r109 r110  
    401401        self.assertEqual(escapees, [4]) 
    402402     
    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') 
    406413        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         
    408417        SDZ = box.unit(Zoo, Name='San Diego Zoo') 
    409         self.assertEqual(len(zooed_animals), 2) 
    410418        aid = 0 
    411419        for z, a in zooed_animals: 
     
    414422            aid = id(a) 
    415423         
    416         # Assert that multirecalls with no matching secondary units returns 
    417         # 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') 
    419427        zooed_animals = [(z, a) for z, a in 
    420                          box.multirecall((Zoo, f), (Animal, leo))] 
     428                         box.multirecall([Zoo, Animal], f + leo)] 
    421429        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) 
    422444     
    423445    def test_7_Multithreading(self):