From: Mike Bayer Date: Mon, 27 Feb 2006 01:05:01 +0000 (+0000) Subject: more work on cycles, fleshed out tests for post_update, fix to the delete phase of... X-Git-Tag: rel_0_1_3~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e5415ee9e10da5db55d1ebeac8a486cd4ac88984;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git more work on cycles, fleshed out tests for post_update, fix to the delete phase of a one-to-many post update closes [ticket:67] --- diff --git a/lib/sqlalchemy/mapping/properties.py b/lib/sqlalchemy/mapping/properties.py index 85fd7418b2..023e44bf7b 100644 --- a/lib/sqlalchemy/mapping/properties.py +++ b/lib/sqlalchemy/mapping/properties.py @@ -458,7 +458,7 @@ class PropertyLoader(MapperProperty): elif self.direction == PropertyLoader.ONETOMANY and delete: # head object is being deleted, and we manage its list of child objects # the child objects have to have their foreign key to the parent set to NULL - if self.private: + if self.private and not self.post_update: # if we are privately managed, then all our objects should # have been marked as "todelete" already and no attribute adjustment is needed return diff --git a/test/cycles.py b/test/cycles.py index f30ac0297b..9114eb4fe0 100644 --- a/test/cycles.py +++ b/test/cycles.py @@ -19,7 +19,7 @@ class Tester(object): def __repr__(self): return "%s(%s)" % (self.__class__.__name__, repr(self.data)) -class SelfCycleTest(AssertMixin): +class SelfReferentialTest(AssertMixin): """tests a self-referential mapper, with an additional list of child objects.""" def setUpAll(self): testbase.db.tables.clear() @@ -67,8 +67,8 @@ class SelfCycleTest(AssertMixin): objectstore.delete(a) objectstore.commit() -class CycleTest(AssertMixin): - """tests two mappers with a bi-directional dependency""" +class BiDirectionalOneToManyTest(AssertMixin): + """tests two mappers with a one-to-many relation to each other.""" def setUpAll(self): testbase.db.tables.clear() global t1 @@ -112,8 +112,8 @@ class CycleTest(AssertMixin): b.c1s.append(c) objectstore.commit() -class CycleWDepsTest(AssertMixin): - """tests two mappers with a bi-directional dependency, and child objects on one of them""" +class BiDirectionalOneToManyTest2(AssertMixin): + """tests two mappers with a one-to-many relation to each other, with a second one-to-many on one of the mappers""" def setUpAll(self): testbase.db.tables.clear() global t1 @@ -178,25 +178,35 @@ class CycleWDepsTest(AssertMixin): objectstore.delete(c) objectstore.commit() -class CycleTest2(AssertMixin): +class OneToManyManyToOneTest(AssertMixin): + """tests two mappers, one has a one-to-many on the other mapper, the other has a separate many-to-one relationship to the first. + two tests will have a row for each item that is dependent on the other. without the "post_update" flag, such relationships + raise an exception when dependencies are sorted.""" def setUpAll(self): testbase.db.tables.clear() global person global ball - person = Table('person', db, - Column('id', Integer, Sequence('person_id_seq', optional=True), primary_key=True), - Column('favoriteBall_id', Integer, ForeignKey('ball.id')), - ) ball = Table('ball', db, Column('id', Integer, Sequence('ball_id_seq', optional=True), primary_key=True), Column('person_id', Integer), ) + person = Table('person', db, + Column('id', Integer, Sequence('person_id_seq', optional=True), primary_key=True), + Column('favoriteBall_id', Integer, ForeignKey('ball.id')), +# Column('favoriteBall_id', Integer), + ) ball.create() person.create() +# person.c.favoriteBall_id.append_item(ForeignKey('ball.id')) ball.c.person_id.append_item(ForeignKey('person.id')) + # make the test more complete for postgres + if db.engine.__module__.endswith('postgres'): + db.execute("alter table ball add constraint fk_ball_person foreign key (person_id) references person(id)", {}) def tearDownAll(self): + if db.engine.__module__.endswith('postgres'): + db.execute("alter table ball drop constraint fk_ball_person", {}) person.drop() ball.drop() @@ -228,8 +238,8 @@ class CycleTest2(AssertMixin): p.balls.append(b) objectstore.commit() - def testrowcycle(self): - """tests a cycle between two rows""" + def testpostupdate_m2o(self): + """tests a cycle between two rows, with a post_update on the many-to-one""" class Person(object): pass @@ -252,11 +262,216 @@ class CycleTest2(AssertMixin): p.balls.append(Ball()) p.balls.append(Ball()) p.favorateBall = b - objectstore.commit() - + + self.assert_sql(db, lambda: objectstore.uow().commit(), [ + ( + "INSERT INTO person (favoriteBall_id) VALUES (:favoriteBall_id)", + {'favoriteBall_id': None} + ), + ( + "INSERT INTO ball (person_id) VALUES (:person_id)", + lambda:{'person_id':p.id} + ), + ( + "INSERT INTO ball (person_id) VALUES (:person_id)", + lambda:{'person_id':p.id} + ), + ( + "INSERT INTO ball (person_id) VALUES (:person_id)", + lambda:{'person_id':p.id} + ), + ( + "INSERT INTO ball (person_id) VALUES (:person_id)", + lambda:{'person_id':p.id} + ), + ( + "UPDATE person SET favoriteBall_id=:favoriteBall_id WHERE person.id = :person_id", + lambda:[{'favoriteBall_id':p.favorateBall.id,'person_id':p.id}] + ) + ], + with_sequences= [ + ( + "INSERT INTO person (id, favoriteBall_id) VALUES (:id, :favoriteBall_id)", + lambda:{'id':db.last_inserted_ids()[0], 'favoriteBall_id': None} + ), + ( + "INSERT INTO ball (id, person_id) VALUES (:id, :person_id)", + lambda:{'id':db.last_inserted_ids()[0],'person_id':p.id} + ), + ( + "INSERT INTO ball (id, person_id) VALUES (:id, :person_id)", + lambda:{'id':db.last_inserted_ids()[0],'person_id':p.id} + ), + ( + "INSERT INTO ball (id, person_id) VALUES (:id, :person_id)", + lambda:{'id':db.last_inserted_ids()[0],'person_id':p.id} + ), + ( + "INSERT INTO ball (id, person_id) VALUES (:id, :person_id)", + lambda:{'id':db.last_inserted_ids()[0],'person_id':p.id} + ), + # heres the post update + ( + "UPDATE person SET favoriteBall_id=:favoriteBall_id WHERE person.id = :person_id", + lambda:[{'favoriteBall_id':p.favorateBall.id,'person_id':p.id}] + ) + ]) objectstore.delete(p) - objectstore.commit() + self.assert_sql(db, lambda: objectstore.uow().commit(), [ + # heres the post update (which is a pre-update with deletes) + ( + "UPDATE person SET favoriteBall_id=:favoriteBall_id WHERE person.id = :person_id", + lambda:[{'person_id': p.id, 'favoriteBall_id': None}] + ), + ( + "DELETE FROM ball WHERE ball.id = :id", + None + # order cant be predicted, but something like: + #lambda:[{'id': 1L}, {'id': 4L}, {'id': 3L}, {'id': 2L}] + ), + ( + "DELETE FROM person WHERE person.id = :id", + lambda:[{'id': p.id}] + ) + + + ]) + + def testpostupdate_o2m(self): + """tests a cycle between two rows, with a post_update on the one-to-many""" + class Person(object): + pass + class Ball(object): + pass + + Ball.mapper = mapper(Ball, ball) + Person.mapper = mapper(Person, person, properties= dict( + balls = relation(Ball.mapper, primaryjoin=ball.c.person_id==person.c.id, foreignkey=ball.c.person_id, private=True, post_update=True), + favorateBall = relation(Ball.mapper, primaryjoin=person.c.favoriteBall_id==ball.c.id, foreignkey=person.c.favoriteBall_id), + ) + ) + + print str(Person.mapper.props['balls'].primaryjoin) + + b = Ball() + p = Person() + p.balls.append(b) + b2 = Ball() + p.balls.append(b2) + b3 = Ball() + p.balls.append(b3) + b4 = Ball() + p.balls.append(b4) + p.favorateBall = b +# objectstore.commit() + self.assert_sql(db, lambda: objectstore.uow().commit(), [ + ( + "INSERT INTO ball (person_id) VALUES (:person_id)", + {'person_id':None} + ), + ( + "INSERT INTO ball (person_id) VALUES (:person_id)", + {'person_id':None} + ), + ( + "INSERT INTO ball (person_id) VALUES (:person_id)", + {'person_id':None} + ), + ( + "INSERT INTO ball (person_id) VALUES (:person_id)", + {'person_id':None} + ), + ( + "INSERT INTO person (favoriteBall_id) VALUES (:favoriteBall_id)", + lambda:{'favoriteBall_id':b.id} + ), + # heres the post update on each one-to-many item + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id':p.id,'ball_id':b.id}] + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id':p.id,'ball_id':b2.id}] + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id':p.id,'ball_id':b3.id}] + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id':p.id,'ball_id':b4.id}] + ), + ], + with_sequences=[ + ( + "INSERT INTO ball (id, person_id) VALUES (:id, :person_id)", + lambda:{'id':db.last_inserted_ids()[0], 'person_id':None} + ), + ( + "INSERT INTO ball (id, person_id) VALUES (:id, :person_id)", + lambda:{'id':db.last_inserted_ids()[0], 'person_id':None} + ), + ( + "INSERT INTO ball (id, person_id) VALUES (:id, :person_id)", + lambda:{'id':db.last_inserted_ids()[0], 'person_id':None} + ), + ( + "INSERT INTO ball (id, person_id) VALUES (:id, :person_id)", + lambda:{'id':db.last_inserted_ids()[0], 'person_id':None} + ), + ( + "INSERT INTO person (id, favoriteBall_id) VALUES (:id, :favoriteBall_id)", + lambda:{'id':db.last_inserted_ids()[0], 'favoriteBall_id':b.id} + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id':p.id,'ball_id':b.id}] + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id':p.id,'ball_id':b2.id}] + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id':p.id,'ball_id':b3.id}] + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id':p.id,'ball_id':b4.id}] + ), + ]) + + objectstore.delete(p) + self.assert_sql(db, lambda: objectstore.uow().commit(), [ + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id': None, 'ball_id': b.id}] + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id': None, 'ball_id': b2.id}] + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id': None, 'ball_id': b3.id}] + ), + ( + "UPDATE ball SET person_id=:person_id WHERE ball.id = :ball_id", + lambda:[{'person_id': None, 'ball_id': b4.id}] + ), + ( + "DELETE FROM person WHERE person.id = :id", + lambda:[{'id':p.id}] + ), + ( + "DELETE FROM ball WHERE ball.id = :id", + None + # the order of deletion is not predictable, but its roughly: + # lambda:[{'id': b.id}, {'id': b2.id}, {'id': b3.id}, {'id': b4.id}] + ) + ]) if __name__ == "__main__": testbase.main() diff --git a/test/testbase.py b/test/testbase.py index afdca47382..cdd0d6a33e 100644 --- a/test/testbase.py +++ b/test/testbase.py @@ -143,7 +143,7 @@ class EngineAssert(object): query = self.convert_statement(query) - self.unittest.assert_(statement == query and params == parameters, "Testing for query '%s' params %s, received '%s' with params %s" % (query, repr(params), statement, repr(parameters))) + self.unittest.assert_(statement == query and (params is None or params == parameters), "Testing for query '%s' params %s, received '%s' with params %s" % (query, repr(params), statement, repr(parameters))) self.sql_count += 1 return self.realexec(proxy, compiled, parameters, **kwargs)