[Sqlalchemy-commits] [1295] sqlalchemy/trunk/test: got circular many-to-many relationships to work
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-04-19 18:25:00
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1295] sqlalchemy/trunk/test: got circular many-to-many relationships to work</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1295</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-19 13:24:45 -0500 (Wed, 19 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>got circular many-to-many relationships to work</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemymappingpropertiespy">sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py</a></li> <li><a href="#sqlalchemytrunktestalltestspy">sqlalchemy/trunk/test/alltests.py</a></li> <li><a href="#sqlalchemytrunktestmanytomanypy">sqlalchemy/trunk/test/manytomany.py</a></li> <li><a href="#sqlalchemytrunktestrelationshipspy">sqlalchemy/trunk/test/relationships.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemymappingpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py (1294 => 1295)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-04-19 03:58:05 UTC (rev 1294) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-04-19 18:24:45 UTC (rev 1295) </span><span class="lines">@@ -225,13 +225,13 @@ </span><span class="cx"> """determines our 'direction', i.e. do we represent one to many, many to many, etc.""" </span><span class="cx"> #print self.key, repr(self.parent.table.name), repr(self.parent.primarytable.name), repr(self.foreignkey.table.name), repr(self.target), repr(self.foreigntable.name) </span><span class="cx"> </span><del>- if self.parent.table is self.target: </del><ins>+ if self.secondaryjoin is not None: + return PropertyLoader.MANYTOMANY + elif self.parent.table is self.target: </ins><span class="cx"> if self.foreignkey.primary_key: </span><span class="cx"> return PropertyLoader.MANYTOONE </span><span class="cx"> else: </span><span class="cx"> return PropertyLoader.ONETOMANY </span><del>- elif self.secondaryjoin is not None: - return PropertyLoader.MANYTOMANY </del><span class="cx"> elif self.foreigntable == self.mapper.noninherited_table: </span><span class="cx"> return PropertyLoader.ONETOMANY </span><span class="cx"> elif self.foreigntable == self.parent.noninherited_table: </span><span class="lines">@@ -657,13 +657,11 @@ </span><span class="cx"> binary.right = binds.setdefault(binary.right, </span><span class="cx"> sql.BindParamClause(binary.left._label, None, shortname = binary.right.name)) </span><span class="cx"> </span><del>- if secondaryjoin is not None: - lazywhere = sql.and_(primaryjoin, secondaryjoin) - else: - lazywhere = primaryjoin - lazywhere = lazywhere.copy_container() </del><ins>+ lazywhere = primaryjoin.copy_container() </ins><span class="cx"> li = BinaryVisitor(visit_binary) </span><span class="cx"> lazywhere.accept_visitor(li) </span><ins>+ if secondaryjoin is not None: + lazywhere = sql.and_(lazywhere, secondaryjoin) </ins><span class="cx"> return (lazywhere, binds) </span><span class="cx"> </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemytrunktestalltestspy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/alltests.py (1294 => 1295)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/alltests.py 2006-04-19 03:58:05 UTC (rev 1294) +++ sqlalchemy/trunk/test/alltests.py 2006-04-19 18:24:45 UTC (rev 1295) </span><span class="lines">@@ -39,6 +39,7 @@ </span><span class="cx"> </span><span class="cx"> # ORM persistence </span><span class="cx"> 'objectstore', </span><ins>+ 'relationships', </ins><span class="cx"> </span><span class="cx"> # cyclical ORM persistence </span><span class="cx"> 'cycles', </span></span></pre></div> <a id="sqlalchemytrunktestmanytomanypy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/manytomany.py (1294 => 1295)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/manytomany.py 2006-04-19 03:58:05 UTC (rev 1294) +++ sqlalchemy/trunk/test/manytomany.py 2006-04-19 18:24:45 UTC (rev 1295) </span><span class="lines">@@ -7,6 +7,10 @@ </span><span class="cx"> '''represents a place''' </span><span class="cx"> def __init__(self, name=None): </span><span class="cx"> self.name = name </span><ins>+ def __str__(self): + return "(Place '%s')" % self.name + def __repr__(self): + return str(self) </ins><span class="cx"> </span><span class="cx"> class PlaceThingy(object): </span><span class="cx"> '''represents a thingy attached to a Place''' </span><span class="lines">@@ -58,29 +62,87 @@ </span><span class="cx"> Column('transition_id', Integer, ForeignKey('transition.transition_id')), </span><span class="cx"> ) </span><span class="cx"> </span><ins>+ global place_place + place_place = Table('place_place', db, + Column('pl1_id', Integer, ForeignKey('place.place_id')), + Column('pl2_id', Integer, ForeignKey('place.place_id')), + ) + </ins><span class="cx"> place.create() </span><span class="cx"> transition.create() </span><span class="cx"> place_input.create() </span><span class="cx"> place_output.create() </span><span class="cx"> place_thingy.create() </span><ins>+ place_place.create() </ins><span class="cx"> </span><span class="cx"> def tearDownAll(self): </span><ins>+ place_place.drop() </ins><span class="cx"> place_input.drop() </span><span class="cx"> place_output.drop() </span><span class="cx"> place_thingy.drop() </span><span class="cx"> place.drop() </span><span class="cx"> transition.drop() </span><ins>+ #testbase.db.tables.clear() </ins><span class="cx"> </span><span class="cx"> def setUp(self): </span><span class="cx"> objectstore.clear() </span><span class="cx"> clear_mappers() </span><span class="cx"> </span><span class="cx"> def tearDown(self): </span><ins>+ place_place.delete().execute() </ins><span class="cx"> place_input.delete().execute() </span><span class="cx"> place_output.delete().execute() </span><span class="cx"> transition.delete().execute() </span><span class="cx"> place.delete().execute() </span><span class="cx"> </span><ins>+ def testcircular(self): + """tests a many-to-many relationship from a table to itself.""" + + Place.mapper = mapper(Place, place) + + Place.mapper.add_property('places', relation( + Place.mapper, secondary=place_place, primaryjoin=place.c.place_id==place_place.c.pl1_id, + secondaryjoin=place.c.place_id==place_place.c.pl2_id, + order_by=place_place.c.pl2_id, + lazy=True, + )) + + p1 = Place('place1') + p2 = Place('place2') + p3 = Place('place3') + p4 = Place('place4') + p5 = Place('place5') + p6 = Place('place6') + p7 = Place('place7') + + p1.places.append(p2) + p1.places.append(p3) + p5.places.append(p6) + p6.places.append(p1) + p7.places.append(p1) + p1.places.append(p5) + p4.places.append(p3) + p3.places.append(p4) + objectstore.flush() + + objectstore.clear() + l = Place.mapper.select(order_by=place.c.place_id) + (p1, p2, p3, p4, p5, p6, p7) = l + assert p1.places == [p2,p3,p5] + assert p5.places == [p6] + assert p7.places == [p1] + assert p6.places == [p1] + assert p4.places == [p3] + assert p3.places == [p4] + assert p2.places == [] + + for p in l: + pp = p.places + self.echo("Place " + str(p) +" places " + repr(pp)) + + objectstore.delete(p1,p2,p3,p4,p5,p6,p7) + objectstore.flush() + </ins><span class="cx"> def testdouble(self): </span><span class="cx"> """tests that a mapper can have two eager relations to the same table, via </span><span class="cx"> two different association tables. aliases are required.""" </span><span class="lines">@@ -110,17 +172,14 @@ </span><span class="cx"> } </span><span class="cx"> ) </span><span class="cx"> </span><del>- def testcircular(self): - """tests a circular many-to-many relationship. this requires that the mapper - "break off" a new "mapper stub" to indicate a third depedendent processor.""" </del><ins>+ def testbidirectional(self): + """tests a bi-directional many-to-many relationship.""" </ins><span class="cx"> Place.mapper = mapper(Place, place) </span><span class="cx"> Transition.mapper = mapper(Transition, transition, properties = dict( </span><span class="cx"> inputs = relation(Place.mapper, place_output, lazy=True, backref='inputs'), </span><span class="cx"> outputs = relation(Place.mapper, place_input, lazy=True, backref='outputs'), </span><span class="cx"> ) </span><span class="cx"> ) </span><del>- #Place.mapper.add_property('inputs', relation(Transition.mapper, place_output, lazy=True, attributeext=attr.ListBackrefExtension('inputs'))) - #Place.mapper.add_property('outputs', relation(Transition.mapper, place_input, lazy=True, attributeext=attr.ListBackrefExtension('outputs'))) </del><span class="cx"> </span><span class="cx"> Place.eagermapper = Place.mapper.options( </span><span class="cx"> eagerload('inputs', selectalias='ip_alias'), </span><span class="lines">@@ -167,6 +226,7 @@ </span><span class="cx"> enrolTbl.drop() </span><span class="cx"> studentTbl.drop() </span><span class="cx"> courseTbl.drop() </span><ins>+ #testbase.db.tables.clear() </ins><span class="cx"> </span><span class="cx"> def setUp(self): </span><span class="cx"> objectstore.clear() </span><span class="lines">@@ -242,6 +302,7 @@ </span><span class="cx"> c2a1.drop() </span><span class="cx"> a.drop() </span><span class="cx"> c.drop() </span><ins>+ #testbase.db.tables.clear() </ins><span class="cx"> </span><span class="cx"> def testbasic(self): </span><span class="cx"> class C(object):pass </span></span></pre></div> <a id="sqlalchemytrunktestrelationshipspy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/relationships.py (1294 => 1295)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/relationships.py 2006-04-19 03:58:05 UTC (rev 1294) +++ sqlalchemy/trunk/test/relationships.py 2006-04-19 18:24:45 UTC (rev 1295) </span><span class="lines">@@ -10,11 +10,14 @@ </span><span class="cx"> </span><span class="cx"> </span><span class="cx"> class RelationTest(testbase.PersistTest): </span><del>- """this is essentially an extension of the "dependency.py" topological sort test. this exposes - a particular issue that doesnt always occur with the straight dependency tests, due to the nature - of the sort being different based on random conditions""" </del><ins>+ """this is essentially an extension of the "dependency.py" topological sort test. + in this test, a table is dependent on two other tables that are otherwise unrelated to each other. + the dependency sort must insure that this childmost table is below both parent tables in the outcome + (a bug existed where this was not always the case). + while the straight topological sort tests should expose this, since the sorting can be different due + to subtle differences in program execution, this test case was exposing the bug whereas the simpler tests + were not.""" </ins><span class="cx"> def setUpAll(self): </span><del>- testbase.db.tables.clear() </del><span class="cx"> global tbl_a </span><span class="cx"> global tbl_b </span><span class="cx"> global tbl_c </span><span class="lines">@@ -81,6 +84,9 @@ </span><span class="cx"> tbl_c.drop() </span><span class="cx"> tbl_b.drop() </span><span class="cx"> tbl_a.drop() </span><ins>+ + def tearDownAll(self): + testbase.db.tables.clear() </ins><span class="cx"> </span><span class="cx"> def testDeleteRootTable(self): </span><span class="cx"> session = objectstore.get_session() </span></span></pre> </div> </div> </body> </html> |