Dear list,

In our application we've run across what seems to be at least a serious 
gotcha, if not an actual bug, in SQLAlchemy. Please let me know if you 
agree, or if there is something I'm doing wrong.

Attached is a minimal reproducer (as minimal as I could manage anyway). 
The assertion at the end of the script fails with SQLAlchemy 0.6.8 and 
0.7.6. I'm using MySQL, although I don't think the database matters. 
(SQLite's nested transaction support is broken, so it won't work.)

The most important part is the lazy_create method, which is how we are 
avoiding race conditions when inserting into tables with a unique 
constraint. It will either insert a new row with the given unique column 
value(s), or select the existing row. (The IntegrityError it catches 
will be a unique constraint violation.)

    @classmethod
    def lazy_create(cls, **kwargs):
        session.begin_nested()
        try:
            item = cls(**kwargs)
            session.add(item)
            session.commit()
        except IntegrityError:
            session.rollback()
            item = session.query(cls).filter_by(**kwargs).one()
        return item

The problem comes when we later do something like this:

    task.packages.append(Package.lazy_create(name=u'asdf'))

If the Package with name 'asdf' already exists, this .append() call will 
have no effect. The Package instance is silently discarded from the 
task.packages list.

As we eventually discovered, the reason is that the nested rollback 
inside lazy_create causes task.packages to be expired. But the .append() 
method is still called on the expired list because of the way our 
statement is written. The expired list is discarded, and so the 
.append() has no effect.

In this particular case, we can rewrite the statement as:

    package = Package.lazy_create(name=u'asdf')
    task.packages.append(package)

which fixes the problem, but it worries me.

Shouldn't calling .append() or any other method on an expired 
relationship list at least raise an exception, given that the list has 
been discarded?

Could SQLAlchemy expire the relationship list without actually replacing 
the list instance? Then we wouldn't need to worry about whether we are 
accidentally holding onto references to expired lists which have been 
replaced out from underneath us.

Is there some other way we could avoid this kind of problem?

-- 
Dan Callaghan <d...@djc.id.au>

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Attachment: bz816879.py
Description: application/python

Reply via email to