[Sqlalchemy-commits] [1160] sqlalchemy/trunk/test: identified more issues with inheritance.
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-03-17 21:12:17
|
<!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>[1160] sqlalchemy/trunk/test: identified more issues with inheritance.</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1160</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-03-17 15:11:59 -0600 (Fri, 17 Mar 2006)</dd> </dl> <h3>Log Message</h3> <pre>identified more issues with inheritance. mapper inheritance is more closed-minded about how it creates the join crit erion as well as the sync rules in inheritance. syncrules have been tightened up to be smarter about creating a new SyncRule given lists of tables and a join clause. properties also checks for relation direction against the "noninherited table" which for the moment makes it a stronger requirement that a relation to a mapper must relate to that mapper's main table, not any tables that it inherits from.</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemymappingmapperpy">sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingpropertiespy">sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingsyncpy">sqlalchemy/trunk/lib/sqlalchemy/mapping/sync.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemyutilpy">sqlalchemy/trunk/lib/sqlalchemy/util.py</a></li> <li><a href="#sqlalchemytrunktestinheritancepy">sqlalchemy/trunk/test/inheritance.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemymappingmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py (1159 => 1160)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-03-17 20:22:55 UTC (rev 1159) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-03-17 21:11:59 UTC (rev 1160) </span><span class="lines">@@ -63,10 +63,23 @@ </span><span class="cx"> </span><span class="cx"> if inherits is not None: </span><span class="cx"> self.primarytable = inherits.primarytable </span><del>- # inherit_condition is optional since the join can figure it out </del><ins>+ # inherit_condition is optional. + if inherit_condition is None: + # figure out inherit condition from our table to the immediate table + # of the inherited mapper, not its full table which could pull in other + # stuff we dont want (allows test/inheritance.InheritTest4 to pass) + inherit_condition = sql.join(inherits.noninherited_table, table).onclause </ins><span class="cx"> self.table = sql.join(inherits.table, table, inherit_condition) </span><ins>+ #print "inherit condition", str(self.table.onclause) + + # generate sync rules. similarly to creating the on clause, specify a + # stricter set of tables to create "sync rules" by,based on the immediate + # inherited table, rather than all inherited tables </ins><span class="cx"> self._synchronizer = sync.ClauseSynchronizer(self, self, sync.ONETOMANY) </span><del>- self._synchronizer.compile(self.table.onclause, inherits.tables, TableFinder(table)) </del><ins>+ self._synchronizer.compile(self.table.onclause, util.HashSet([inherits.noninherited_table]), TableFinder(table)) + # the old rule + #self._synchronizer.compile(self.table.onclause, inherits.tables, TableFinder(table)) + </ins><span class="cx"> self.inherits = inherits </span><span class="cx"> self.noninherited_table = table </span><span class="cx"> else: </span><span class="lines">@@ -965,7 +978,8 @@ </span><span class="cx"> def __init__(self, table, check_columns=False): </span><span class="cx"> self.tables = [] </span><span class="cx"> self.check_columns = check_columns </span><del>- table.accept_visitor(self) </del><ins>+ if table is not None: + table.accept_visitor(self) </ins><span class="cx"> def visit_table(self, table): </span><span class="cx"> self.tables.append(table) </span><span class="cx"> def __len__(self): </span><span class="lines">@@ -977,7 +991,7 @@ </span><span class="cx"> def __contains__(self, obj): </span><span class="cx"> return obj in self.tables </span><span class="cx"> def __add__(self, obj): </span><del>- return self.tables + obj </del><ins>+ return self.tables + list(obj) </ins><span class="cx"> def visit_column(self, column): </span><span class="cx"> if self.check_columns: </span><span class="cx"> column.table.accept_visitor(self) </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py (1159 => 1160)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-03-17 20:22:55 UTC (rev 1159) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-03-17 21:11:59 UTC (rev 1160) </span><span class="lines">@@ -228,11 +228,9 @@ </span><span class="cx"> return PropertyLoader.ONETOMANY </span><span class="cx"> elif self.secondaryjoin is not None: </span><span class="cx"> return PropertyLoader.MANYTOMANY </span><del>- elif self.foreigntable == self.target: - #elif self.foreigntable is self.target or self.foreigntable in self.mapper.tables: </del><ins>+ elif self.foreigntable == self.mapper.noninherited_table: </ins><span class="cx"> return PropertyLoader.ONETOMANY </span><del>- elif self.foreigntable == self.parent.table: - #elif self.foreigntable is self.parent.table or self.foreigntable in self.parent.tables: </del><ins>+ elif self.foreigntable == self.parent.noninherited_table: </ins><span class="cx"> return PropertyLoader.MANYTOONE </span><span class="cx"> else: </span><span class="cx"> raise ArgumentError("Cant determine relation direction") </span><span class="lines">@@ -529,6 +527,7 @@ </span><span class="cx"> The list of rules is used within commits by the _synchronize() method when dependent </span><span class="cx"> objects are processed.""" </span><span class="cx"> </span><ins>+ </ins><span class="cx"> parent_tables = util.HashSet(self.parent.tables + [self.parent.primarytable]) </span><span class="cx"> target_tables = util.HashSet(self.mapper.tables + [self.mapper.primarytable]) </span><span class="cx"> </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingsyncpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/sync.py (1159 => 1160)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/sync.py 2006-03-17 20:22:55 UTC (rev 1159) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/sync.py 2006-03-17 21:11:59 UTC (rev 1160) </span><span class="lines">@@ -24,13 +24,15 @@ </span><span class="cx"> self.syncrules = [] </span><span class="cx"> </span><span class="cx"> def compile(self, sqlclause, source_tables, target_tables, issecondary=None): </span><del>- def check_for_table(binary, l): - for col in [binary.left, binary.right]: - if col.table in l: - return col </del><ins>+ def check_for_table(binary, list1, list2): + #print "check for table", str(binary), [str(c) for c in l] + if binary.left.table in list1 and binary.right.table in list2: + return (binary.left, binary.right) + elif binary.right.table in list1 and binary.left.table in list2: + return (binary.right, binary.left) </ins><span class="cx"> else: </span><del>- return None - </del><ins>+ return (None, None) + </ins><span class="cx"> def compile_binary(binary): </span><span class="cx"> """assembles a SyncRule given a single binary condition""" </span><span class="cx"> if binary.operator != '=' or not isinstance(binary.left, schema.Column) or not isinstance(binary.right, schema.Column): </span><span class="lines">@@ -53,8 +55,7 @@ </span><span class="cx"> else: </span><span class="cx"> raise AssertionError("assert failed") </span><span class="cx"> else: </span><del>- pt = check_for_table(binary, source_tables) - tt = check_for_table(binary, target_tables) </del><ins>+ (pt, tt) = check_for_table(binary, source_tables, target_tables) </ins><span class="cx"> #print "OK", binary, [t.name for t in source_tables], [t.name for t in target_tables] </span><span class="cx"> if pt and tt: </span><span class="cx"> if self.direction == ONETOMANY: </span><span class="lines">@@ -94,7 +95,7 @@ </span><span class="cx"> self.issecondary = issecondary </span><span class="cx"> self.dest_mapper = dest_mapper </span><span class="cx"> self.dest_column = dest_column </span><del>- #print "SyncRule", source_mapper, source_column, dest_column, dest_mapper, direction </del><ins>+ #print "SyncRule", source_mapper, source_column, dest_column, dest_mapper </ins><span class="cx"> </span><span class="cx"> def execute(self, source, dest, obj, child, clearkeys): </span><span class="cx"> if source is None: </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemyutilpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/util.py (1159 => 1160)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/util.py 2006-03-17 20:22:55 UTC (rev 1159) +++ sqlalchemy/trunk/lib/sqlalchemy/util.py 2006-03-17 21:11:59 UTC (rev 1160) </span><span class="lines">@@ -255,6 +255,8 @@ </span><span class="cx"> return self.map.has_key(item) </span><span class="cx"> def clear(self): </span><span class="cx"> self.map.clear() </span><ins>+ def intersection(self, l): + return HashSet([x for x in l if self.contains(x)]) </ins><span class="cx"> def empty(self): </span><span class="cx"> return len(self.map) == 0 </span><span class="cx"> def append(self, item): </span></span></pre></div> <a id="sqlalchemytrunktestinheritancepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/inheritance.py (1159 => 1160)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/inheritance.py 2006-03-17 20:22:55 UTC (rev 1159) +++ sqlalchemy/trunk/test/inheritance.py 2006-03-17 21:11:59 UTC (rev 1160) </span><span class="lines">@@ -14,80 +14,83 @@ </span><span class="cx"> pass </span><span class="cx"> </span><span class="cx"> class InheritTest(testbase.AssertMixin): </span><del>- def setUpAll(self): - global principals - global users - global groups - global user_group_map - principals = Table( - 'principals', - testbase.db, - Column('principal_id', Integer, Sequence('principal_id_seq', optional=False), primary_key=True), - Column('name', String(50), nullable=False), - ) </del><ins>+ """deals with inheritance and many-to-many relationships""" + def setUpAll(self): + global principals + global users + global groups + global user_group_map + principals = Table( + 'principals', + testbase.db, + Column('principal_id', Integer, Sequence('principal_id_seq', optional=False), primary_key=True), + Column('name', String(50), nullable=False), + ) </ins><span class="cx"> </span><del>- users = Table( - 'prin_users', - testbase.db, - Column('principal_id', Integer, ForeignKey('principals.principal_id'), primary_key=True), - Column('password', String(50), nullable=False), - Column('email', String(50), nullable=False), - Column('login_id', String(50), nullable=False), </del><ins>+ users = Table( + 'prin_users', + testbase.db, + Column('principal_id', Integer, ForeignKey('principals.principal_id'), primary_key=True), + Column('password', String(50), nullable=False), + Column('email', String(50), nullable=False), + Column('login_id', String(50), nullable=False), </ins><span class="cx"> </span><del>- ) </del><ins>+ ) </ins><span class="cx"> </span><del>- groups = Table( - 'prin_groups', - testbase.db, - Column( 'principal_id', Integer, ForeignKey('principals.principal_id'), primary_key=True), </del><ins>+ groups = Table( + 'prin_groups', + testbase.db, + Column( 'principal_id', Integer, ForeignKey('principals.principal_id'), primary_key=True), </ins><span class="cx"> </span><del>- ) </del><ins>+ ) </ins><span class="cx"> </span><del>- user_group_map = Table( - 'prin_user_group_map', - testbase.db, - Column('user_id', Integer, ForeignKey( "prin_users.principal_id"), primary_key=True ), - Column('group_id', Integer, ForeignKey( "prin_groups.principal_id"), primary_key=True ), - #Column('user_id', Integer, ForeignKey( "prin_users.principal_id"), ), - #Column('group_id', Integer, ForeignKey( "prin_groups.principal_id"), ), </del><ins>+ user_group_map = Table( + 'prin_user_group_map', + testbase.db, + Column('user_id', Integer, ForeignKey( "prin_users.principal_id"), primary_key=True ), + Column('group_id', Integer, ForeignKey( "prin_groups.principal_id"), primary_key=True ), + #Column('user_id', Integer, ForeignKey( "prin_users.principal_id"), ), + #Column('group_id', Integer, ForeignKey( "prin_groups.principal_id"), ), </ins><span class="cx"> </span><del>- ) </del><ins>+ ) </ins><span class="cx"> </span><del>- principals.create() - users.create() - groups.create() - user_group_map.create() - def tearDownAll(self): - user_group_map.drop() - groups.drop() - users.drop() - principals.drop() - testbase.db.tables.clear() - def setUp(self): - objectstore.clear() - clear_mappers() - - def testbasic(self): - assign_mapper( Principal, principals ) - assign_mapper( - User, - users, - inherits=Principal.mapper - ) </del><ins>+ principals.create() + users.create() + groups.create() + user_group_map.create() + def tearDownAll(self): + user_group_map.drop() + groups.drop() + users.drop() + principals.drop() + testbase.db.tables.clear() + def setUp(self): + objectstore.clear() + clear_mappers() + + def testbasic(self): + assign_mapper( Principal, principals ) + assign_mapper( + User, + users, + inherits=Principal.mapper + ) </ins><span class="cx"> </span><del>- assign_mapper( - Group, - groups, - inherits=Principal.mapper, - properties=dict( users = relation(User.mapper, user_group_map, lazy=True, backref="groups") ) - ) </del><ins>+ assign_mapper( + Group, + groups, + inherits=Principal.mapper, + properties=dict( users = relation(User.mapper, user_group_map, lazy=True, backref="groups") ) + ) </ins><span class="cx"> </span><del>- g = Group(name="group1") - g.users.append(User(name="user1", password="pw", email="fo...@ba...", login_id="lg1")) - - objectstore.commit() - </del><ins>+ g = Group(name="group1") + g.users.append(User(name="user1", password="pw", email="fo...@ba...", login_id="lg1")) + + objectstore.commit() + # TODO: put an assertion + </ins><span class="cx"> class InheritTest2(testbase.AssertMixin): </span><ins>+ """deals with inheritance and many-to-many relationships""" </ins><span class="cx"> def setUpAll(self): </span><span class="cx"> engine = testbase.db </span><span class="cx"> global foo, bar, foo_bar </span><span class="lines">@@ -155,6 +158,7 @@ </span><span class="cx"> ) </span><span class="cx"> </span><span class="cx"> class InheritTest3(testbase.AssertMixin): </span><ins>+ """deals with inheritance and many-to-many relationships""" </ins><span class="cx"> def setUpAll(self): </span><span class="cx"> engine = testbase.db </span><span class="cx"> global foo, bar, blub, bar_foo, blub_bar, blub_foo,tables </span><span class="lines">@@ -217,9 +221,11 @@ </span><span class="cx"> b.foos.append(Foo("foo #1")) </span><span class="cx"> b.foos.append(Foo("foo #2")) </span><span class="cx"> objectstore.commit() </span><ins>+ compare = repr(b) + repr(b.foos) </ins><span class="cx"> objectstore.clear() </span><span class="cx"> l = Bar.mapper.select() </span><del>- print l[0], l[0].foos </del><ins>+ self.echo(repr(l[0]) + repr(l[0].foos)) + self.assert_(repr(l[0]) + repr(l[0].foos) == compare) </ins><span class="cx"> </span><span class="cx"> def testadvanced(self): </span><span class="cx"> class Foo(object): </span><span class="lines">@@ -274,7 +280,76 @@ </span><span class="cx"> self.echo(x) </span><span class="cx"> self.assert_(repr(x) == compare) </span><span class="cx"> </span><ins>+class InheritTest4(testbase.AssertMixin): + """deals with inheritance and one-to-many relationships""" + def setUpAll(self): + engine = testbase.db + global foo, bar, blub, tables + engine.engine.echo = 'debug' + # the 'data' columns are to appease SQLite which cant handle a blank INSERT + foo = Table('foo', engine, + Column('id', Integer, Sequence('foo_seq'), primary_key=True), + Column('data', String(20))) </ins><span class="cx"> </span><ins>+ bar = Table('bar', engine, + Column('id', Integer, ForeignKey('foo.id'), primary_key=True), + Column('data', String(20))) </ins><span class="cx"> </span><ins>+ blub = Table('blub', engine, + Column('id', Integer, ForeignKey('bar.id'), primary_key=True), + Column('foo_id', Integer, ForeignKey('foo.id'), nullable=False), + Column('data', String(20))) + + tables = [foo, bar, blub] + for table in tables: + table.create() + def tearDownAll(self): + for table in reversed(tables): + table.drop() + testbase.db.tables.clear() + + def tearDown(self): + for table in reversed(tables): + table.delete().execute() + + def testbasic(self): + class Foo(object): + def __init__(self, data=None): + self.data = data + def __repr__(self): + return "Foo id %d, data %s" % (self.id, self.data) + Foo.mapper = mapper(Foo, foo) + + class Bar(Foo): + def __repr__(self): + return "Bar id %d, data %s" % (self.id, self.data) + + Bar.mapper = mapper(Bar, bar, inherits=Foo.mapper) + + class Blub(Bar): + def __repr__(self): + return "Blub id %d, data %s" % (self.id, self.data) + + Blub.mapper = mapper(Blub, blub, inherits=Bar.mapper, properties={ + # bug was raised specifically based on the order of cols in the join.... +# 'parent_foo':relation(Foo.mapper, primaryjoin=blub.c.foo_id==foo.c.id) +# 'parent_foo':relation(Foo.mapper, primaryjoin=foo.c.id==blub.c.foo_id) + 'parent_foo':relation(Foo.mapper) + }) + + b1 = Blub("blub #1") + b2 = Blub("blub #2") + f = Foo("foo #1") + b1.parent_foo = f + b2.parent_foo = f + objectstore.commit() + compare = repr(b1) + repr(b2) + repr(b1.parent_foo) + repr(b2.parent_foo) + objectstore.clear() + l = Blub.mapper.select() + result = repr(l[0]) + repr(l[1]) + repr(l[0].parent_foo) + repr(l[1].parent_foo) + self.echo(result) + self.assert_(compare == result) + self.assert_(l[0].parent_foo.data == 'foo #1' and l[1].parent_foo.data == 'foo #1') + </ins><span class="cx"> if __name__ == "__main__": </span><span class="cx"> testbase.main() </span></span></pre> </div> </div> </body> </html> |