recursion overflow "expire" cascade on pending related object

Issue #1754 resolved
Mike Bayer repo owner created an issue

this is mine. need to create a small test case. general steps:

x = Foo()
x = session.merge(x)

# x exists, and is loaded.
session.expire(x)

session.flush() # or autoflush inside 
# another merge, not 
# sure if that's significant





 player_entity = Session.merge(player_entity)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/scoping.py", line 129, in do
  return getattr(self.registry(), name)(*args, **kwargs)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/session.py", line 1117, in merge
  self._autoflush()
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/session.py", line 879, in _autoflush
  self.flush()
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/session.py", line 1332, in flush
  self._flush(objects)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/session.py", line 1410, in _flush
  flush_context.execute()
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/unitofwork.py", line 255, in execute
  UOWExecutor().execute(self, tasks)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/unitofwork.py", line 745, in execute
  self.execute_save_steps(trans, task)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/unitofwork.py", line 760, in execute_save_steps
  self.save_objects(trans, task)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/unitofwork.py", line 751, in save_objects
  task.mapper._save_obj(task.polymorphic_tosave_objects, trans)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/mapper.py", line 1295, in _save_obj
  state.key or m._identity_key_from_state(state)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/mapper.py", line 1108, in _identity_key_from_state
  return self.identity_key_from_primary_key(self._primary_key_from_state(state))
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/mapper.py", line 1119, in _primary_key_from_state
  return [column) for column in self.primary_key](self._get_state_attr_by_column(state,)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/mapper.py", line 1133, in _get_state_attr_by_column
  return self._get_col_to_prop(column).getattr(state, column)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/properties.py", line 100, in getattr
  return state.get_impl(self.key).get(state, state.dict)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/attributes.py", line 377, in get
  value = callable_(passive=passive)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/state.py", line 268, in __call__
  self.manager.deferred_scalar_loader(self, toload)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/mapper.py", line 1949, in _load_scalar_attributes
  identity_key = mapper._identity_key_from_state(state)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/mapper.py", line 1108, in _identity_key_from_state
  return self.identity_key_from_primary_key(self._primary_key_from_state(state))
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/mapper.py", line 1119, in _primary_key_from_state
  return [column) for column in self.primary_key](self._get_state_attr_by_column(state,)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/mapper.py", line 1133, in _get_state_attr_by_column
  return self._get_col_to_prop(column).getattr(state, column)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/properties.py", line 100, in getattr
  return state.get_impl(self.key).get(state, state.dict)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/attributes.py", line 377, in get
  value = callable_(passive=passive)
File "/project/lib/python2.6/site-packages/SQLAlchemy-0.6beta3dev-py2.6.egg/sqlalchemy/orm/state.py", line 268, in __call__

Comments (5)

  1. Mike Bayer reporter

    here we are, its a cascade issue:

    from sqlalchemy import *
    from sqlalchemy.orm import *
    from sqlalchemy.test.entities import BasicEntity
    
    engine = create_engine('sqlite://', echo=True)
    
    metadata = MetaData()
    
    foo = Table('foo', metadata,
        Column('id', String, primary_key=True),
    )
    
    bar = Table('bar', metadata, 
         Column('id', Integer, primary_key=True),
         Column('foo_id', String, ForeignKey('foo.id'))
    )
    
    class Foo(BasicEntity):
        def __init__(self, **kw):
            BasicEntity.__init__(self, **kw)
            self.bar = Bar()
    
    class Bar(BasicEntity):
        pass
    
    mapper(Foo, foo, properties={
        'bar':relation(Bar, uselist=False, cascade="save-update, merge, refresh-expire")
    })
    
    mapper(Bar, bar)
    
    metadata.create_all(engine)
    
    session = sessionmaker(engine)()
    
    p1 = Foo(id='p1')
    
    session.add(p1)
    session.flush()
    
    p1 = Foo(id='p1')
    p1 = session.merge(p1)
    session.expire(p1)
    session.flush()
    
  2. Mike Bayer reporter

    it wasa little more involved than that as we needed to figure out the correct approach to expiring a pending related entity. if delete-orphan is true its expunged otherwise not. similarly, refresh() wasn't honoring the cascade at all..nobody's ever noticed. since we're at 0.6 time it was also time to make this change. this is all in 75e14f855ee64a01bb79e66f8a868911f6c9e583.

  3. Log in to comment