- changed status to resolved
why inconsistent orphan behavior for persistent vs. pending?
Issue #2655
resolved
Figure out the rationale behind the DoubleParentO2MOrphanTest regarding the auto-expunge. This patch reverses it:
diff -r b1df6fab53a0d740fe60f04e5c9ad01027ba59af lib/sqlalchemy/orm/unitofwork.py
--- a/lib/sqlalchemy/orm/unitofwork.py Mon Jan 21 18:17:10 2013 -0500
+++ b/lib/sqlalchemy/orm/unitofwork.py Tue Jan 22 20:55:19 2013 -0500
@@ -63,9 +63,11 @@
# expunge pending orphans
item_state = attributes.instance_state(item)
+
if prop.cascade.delete_orphan and \
item_state in sess._new and \
- prop.mapper._is_orphan(item_state):
+ not state.get_impl(key).hasparent(item_state):
+ #prop.mapper._is_orphan(item_state):
sess.expunge(item)
def set_(state, newvalue, oldvalue, initiator):
@@ -95,7 +97,8 @@
oldvalue_state = attributes.instance_state(oldvalue)
if oldvalue_state in sess._new and \
- prop.mapper._is_orphan(oldvalue_state):
+ not state.get_impl(key).hasparent(oldvalue_state):
+ #prop.mapper._is_orphan(oldvalue_state):
sess.expunge(oldvalue)
return newvalue
here's a test based on the association proxy example that seems quite reasonable:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
keyword = relationship("Keyword",
backref=backref("user_keywords", cascade="all, delete-orphan"))
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
users = association_proxy('user_keywords', 'user')
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
Base.metadata.drop_all(e)
Base.metadata.create_all(e)
session = Session(e)
rory = User("rory")
session.add(rory)
chicken = Keyword('chicken')
session.add(chicken)
rory.keywords.append(chicken)
# add this in, test passes, as the remove below
# triggers a single-parent orphan event.
# take it out, object is not expunged, get an integrity error.
#session.flush()
rory.keywords.remove(chicken)
session.flush()
clearly we did this expunge behavior for a reason. But I'm not seeing the wisdom of it at the moment, but this might be hard to change now. So not sure if we can do anything, perhaps document it, provide some backwards compatible workaround, not sure.
Comments (2)
-
reporter -
reporter - removed milestone
Removing milestone: 0.8.0final (automated comment)
- Log in to comment
big one, documentation-wise, a477f8a61ec60b2fc343d87aa30ef6595c77727d