Description
Migrated issue, originally created by Anonymous
If we have a self-referential, many-to-many relation (e.g. network of nodes), when we define a validator on one side of the symmetrical relationship (namely children), append works or doesn't work correctly depending on whether the data is commited or not. Works fine if there's no validator or changes are commited at the very end of operation.
An obvious workaround is to change the append to append to the other side of the relationship (ie. parent.children.append instead of children.parents.append), so I've set the ticket priority to low. However, something is definitely wrong with the code and this can be a manifestation of some nastier bug, ergo the ticket.
Self-contained test case is attached. My setup: SQLAlchemy 0.5.5, Python 2.5 on win32
Attachments: validator_bug.py | backref_validator_test.py
Activity
sqlalchemy-bot commentedon Sep 4, 2009
Anonymous wrote:
Test case showing the bug
sqlalchemy-bot commentedon Sep 4, 2009
Changes by Anonymous:
sqlalchemy-bot commentedon Sep 4, 2009
Michael Bayer (@zzzeek) wrote:
backrefs don't fire off for unloaded collections, and it would be seriously inefficient if they did for many-to-many relations. every append() of an item would potentially result in a full load of a new collection of arbitrary length !
I think this is more of a documentation issue - validators only apply to in-python operations, not "de facto" collection modifications that occur due to database operations resulting from a related collection in the other direction.
the solution would be to validate on each side where you expect an append() operation in python to occur.
the only thing bugging me here is that yes, if both sides are loaded, then both event handlers fire off. so this may be a behavioral change I'd want to set up for 0.6.0 - that @validator doesn't fire off for a backref operation (or, better yet, lets just add a flag to @validator("foo", enable_backrefs=False))
sqlalchemy-bot commentedon Sep 4, 2009
Changes by Michael Bayer (@zzzeek):
sqlalchemy-bot commentedon Sep 5, 2009
Anonymous wrote:
Now I'm confused. You say that backrefs don't fire off for unloaded collections. However, a validator defined for 'children' will fire (and work correctly) when doing a node.parents.append(). The test case demonstrates this (eg. trying to add a duplicate node will raise an exception).
What doesn't work is that append() silently fails, and only when intermidiate commits() are made. In pseudocode:
This works:
But this doesn't:
But this works again:
sqlalchemy-bot commentedon Sep 5, 2009
Michael Bayer (@zzzeek) wrote:
sorry, I read "doesn't work", saw that you were expecting it to work primarily from the reverse end, and assumed it meant your validator wasn't working as expected. But in fact you're hitting "self.children" which is loading the collection unconditionally. That is actually the source of the issue here, changing the state of the collection within the enclosing operation that is trying to do the same thing is resetting the "dirty" flag on the item and the collection isn't being flushed.
While the use case pictured in this test case is a lot easier to achieve by just using a
set()
, it is a bug though I won't know if it's resolvable until I more closely pinpoint the mechanics of this condition.sqlalchemy-bot commentedon Sep 5, 2009
Changes by Michael Bayer (@zzzeek):
sqlalchemy-bot commentedon Sep 5, 2009
Anonymous wrote:
The actual use case is a bit more complicated. The validator is used to prevent circulairty in the graph and therefore must traverse parents/children in a non-trivial way.
However, as there is a workaround (in this test case, using
node1.children.append(node2)
instead ofnode2.parents.append(node1)
), the issue is not too critical. I just wanted to point out the bug, to make sure its not a manifestation of some deeper problem.sqlalchemy-bot commentedon Sep 7, 2009
Michael Bayer (@zzzeek) wrote:
Here's a patch that addresses at least trapping where the bookkeeping goes wrong:
I think this + the aforementioned enable_backrefs=False flag to avoid having the error be raised is at least the way to go for now. there are ways the bookkeeping can try to "adjust" inside the block there but the sequence of events gets a little more intricate.
sqlalchemy-bot commentedon Sep 11, 2009
Anonymous wrote:
A test that fails when backref validators are fired and passes when only the correct validator is fired
sqlalchemy-bot commentedon Sep 11, 2009
Changes by Anonymous:
sqlalchemy-bot commentedon Sep 11, 2009
Anonymous wrote:
I confirm that the changes throw an assert where append() used to silently fail.
I think that it's better for validators not to fire for the backref side of the relation, since it's redundant. Then the programmer would explicitly need to use
enable_backrefs=True
if he really knows what he's doing (and the funcionality has been fully implemented, which at the moment it isn't for self-referential relations).Since this is a lower-priority issue, maybe the following course of action would be the best:
Attached: a test case that fails when backref validators are fired and passes when only the correct validator is fired.
sqlalchemy-bot commentedon Sep 12, 2009
Michael Bayer (@zzzeek) wrote:
assertion is in f83c9a3 83eafb2. the validates flag is #1535.
sqlalchemy-bot commentedon Sep 12, 2009
Changes by Michael Bayer (@zzzeek):
5 remaining items