diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-07-22 01:46:41 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-07-22 01:46:41 -0400 |
| commit | ca9029ca9830573c4a5618e5dc01347576d50eb4 (patch) | |
| tree | 680c7622154346c21523c69a0d932a9a91146c24 | |
| parent | 5b91f627a4e5db44c45254f0cb32ac10d0dfabd0 (diff) | |
| download | sqlalchemy-ca9029ca9830573c4a5618e5dc01347576d50eb4.tar.gz | |
- Moving an o2m object from one collection to
another, or vice versa changing the referenced
object by an m2o, where the foreign key is also a
member of the primary key, will now be more
carefully checked during flush if the change in
value of the foreign key on the "many" side is the
result of a change in the primary key of the "one"
side, or if the "one" is just a different object.
In one case, a cascade-capable DB would have
cascaded the value already and we need to look at
the "new" PK value to do an UPDATE, in the other we
need to continue looking at the "old". We now look
at the "old", assuming passive_updates=True,
unless we know it was a PK switch that
triggered the change. [ticket:1856]
| -rw-r--r-- | CHANGES | 17 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/dependency.py | 24 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/sync.py | 4 | ||||
| -rw-r--r-- | test/orm/test_naturalpks.py | 88 |
4 files changed, 116 insertions, 17 deletions
@@ -5,6 +5,23 @@ CHANGES ======= 0.6.4 ===== +- orm + - Moving an o2m object from one collection to + another, or vice versa changing the referenced + object by an m2o, where the foreign key is also a + member of the primary key, will now be more + carefully checked during flush if the change in + value of the foreign key on the "many" side is the + result of a change in the primary key of the "one" + side, or if the "one" is just a different object. + In one case, a cascade-capable DB would have + cascaded the value already and we need to look at + the "new" PK value to do an UPDATE, in the other we + need to continue looking at the "old". We now look + at the "old", assuming passive_updates=True, + unless we know it was a PK switch that + triggered the change. [ticket:1856] + - sql - Changed the scheme used to generate truncated "auto" index names when using the "index=True" diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index baa7af645..e96eed28e 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -468,7 +468,8 @@ class OneToManyDP(DependencyProcessor): self._synchronize( state, child, - None, True, uowcommit) + None, True, + uowcommit, False) if self.post_update and child: self._post_update(child, uowcommit, [state]) @@ -479,7 +480,8 @@ class OneToManyDP(DependencyProcessor): self._synchronize( state, child, - None, True, uowcommit) + None, True, + uowcommit, False) if self.post_update and child: self._post_update(child, uowcommit, @@ -497,22 +499,25 @@ class OneToManyDP(DependencyProcessor): passive=True) if history: for child in history.added: - self._synchronize(state, child, None, False, uowcommit) + self._synchronize(state, child, None, + False, uowcommit, False) if child is not None and self.post_update: self._post_update(child, uowcommit, [state]) for child in history.deleted: if not self.cascade.delete_orphan and \ not self.hasparent(child): - self._synchronize(state, child, None, True, uowcommit) + self._synchronize(state, child, None, True, + uowcommit, False) if self._pks_changed(uowcommit, state): for child in history.unchanged: self._synchronize(state, child, None, - False, uowcommit) + False, uowcommit, True) - def _synchronize(self, state, child, associationrow, - clearkeys, uowcommit): + def _synchronize(self, state, child, + associationrow, clearkeys, uowcommit, + pks_changed): source = state dest = child if dest is None or \ @@ -524,7 +529,7 @@ class OneToManyDP(DependencyProcessor): else: sync.populate(source, self.parent, dest, self.mapper, self.prop.synchronize_pairs, uowcommit, - self.passive_updates) + self.passive_updates and pks_changed) def _pks_changed(self, uowcommit, state): return sync.source_modified( @@ -713,8 +718,7 @@ class ManyToOneDP(DependencyProcessor): self.parent, self.prop.synchronize_pairs, uowcommit, - self.passive_updates - ) + False) class DetectKeySwitch(DependencyProcessor): """For many-to-one relationships with no one-to-many backref, diff --git a/lib/sqlalchemy/orm/sync.py b/lib/sqlalchemy/orm/sync.py index 184ae8c84..3b2a291bd 100644 --- a/lib/sqlalchemy/orm/sync.py +++ b/lib/sqlalchemy/orm/sync.py @@ -11,7 +11,7 @@ between instances based on join conditions. from sqlalchemy.orm import exc, util as mapperutil def populate(source, source_mapper, dest, dest_mapper, - synchronize_pairs, uowcommit, passive_updates): + synchronize_pairs, uowcommit, flag_cascaded_pks): for l, r in synchronize_pairs: try: value = source_mapper._get_state_attr_by_column(source, source.dict, l) @@ -29,7 +29,7 @@ def populate(source, source_mapper, dest, dest_mapper, # reasons, since we only need this info for a primary key # destination. if l.primary_key and r.primary_key and \ - r.references(l) and passive_updates: + r.references(l) and flag_cascaded_pks: uowcommit.attributes[("pk_cascaded", dest, r)] = True def clear(dest, dest_mapper, synchronize_pairs): diff --git a/test/orm/test_naturalpks.py b/test/orm/test_naturalpks.py index 1a22e00d1..023ef1db2 100644 --- a/test/orm/test_naturalpks.py +++ b/test/orm/test_naturalpks.py @@ -702,14 +702,14 @@ class CascadeToFKPKTest(_base.MappedTest, testing.AssertsCompiledSQL): def test_onetomany_nonpassive(self): self._test_onetomany(False) - def test_move_passive(self): - self._test_move(True) + def test_o2m_change_passive(self): + self._test_o2m_change(True) - def test_move_nonpassive(self): - self._test_move(False) + def test_o2m_change_nonpassive(self): + self._test_o2m_change(False) @testing.resolve_artifact_names - def _test_move(self, passive_updates): + def _test_o2m_change(self, passive_updates): """Change the PK of a related entity to another. "on update cascade" is not involved here, so the mapper has @@ -731,7 +731,85 @@ class CascadeToFKPKTest(_base.MappedTest, testing.AssertsCompiledSQL): a1.username = 'jack' sess.flush() + + def test_o2m_move_passive(self): + self._test_o2m_move(True) + + def test_o2m_move_nonpassive(self): + self._test_o2m_move(False) + + @testing.resolve_artifact_names + def _test_o2m_move(self, passive_updates): + """Move the related entity to a different collection, + changing its PK. + + """ + mapper(User, users, properties={ + 'addresses':relationship(Address, + passive_updates=passive_updates)}) + mapper(Address, addresses) + + sess = create_session() + a1 = Address(username='ed', email='ed@host1') + u1 = User(username='ed', addresses=[a1]) + u2 = User(username='jack') + + sess.add_all([a1, u1, u2]) + sess.flush() + + u1.addresses.remove(a1) + u2.addresses.append(a1) + sess.flush() + + def test_change_m2o_passive(self): + self._test_change_m2o(True) + + @testing.fails_on_everything_except('sqlite', 'oracle', '+zxjdbc') + def test_change_m2o_nonpassive(self): + self._test_change_m2o(False) + + @testing.resolve_artifact_names + def _test_change_m2o(self, passive_updates): + mapper(User, users) + mapper(Address, addresses, properties={ + 'user':relationship(User, passive_updates=passive_updates) + }) + + sess = create_session() + u1 = User(username='jack') + a1 = Address(user=u1, email='foo@bar') + sess.add_all([u1, a1]) + sess.flush() + u1.username='edmodified' + sess.flush() + eq_(a1.username, 'edmodified') + + def test_move_m2o_passive(self): + self._test_move_m2o(True) + + def test_move_m2o_nonpassive(self): + self._test_move_m2o(False) + + @testing.resolve_artifact_names + def _test_move_m2o(self, passive_updates): + # tests [ticket:1856] + mapper(User, users) + mapper(Address, addresses, properties={ + 'user':relationship(User, passive_updates=passive_updates) + }) + + sess = create_session() + u1 = User(username='jack') + u2 = User(username='ed') + a1 = Address(user=u1, email='foo@bar') + sess.add_all([u1, u2, a1]) + sess.flush() + + a1.user = u2 + sess.flush() + + @testing.resolve_artifact_names def test_rowswitch_doesntfire(self): mapper(User, users) |
