Hello,
* Apologies if this is a duplicate -- I attempted to post a few hours
earlier but the result vanished. Could be that the earlier effort went off
as a direct email to MB, instead of a post to this group as intended.
"chris e" posted about this topic in April and I was trying to respond on
that thread, but Google Groups won't take a response this long after.
Below, i've included a working test script that causes a
ConcurrentModificationError. I'm wondering if that is the right result. I
was working with this section of my code specifically to work out what
cascade relationships I wanted to use in my tables and mappers, and while
experimenting with various cascade options I fully expected to run into some
exceptions where I couldn't delete some object because of its
dependencies. But seeing a ConcurrentModificationError instead was a
surprise. So I'm wondering if I've just got something set up wrong.
I've done my best to trim this to a bare-bones minimal reproduction of the
error, though it's still not tiny. Can't really cut out any more
tables/classes/mappers without rendering the test script alien to what I'm
actually working on.
I'm modeling a rental reservation system. rentable Items are (multiple
table) polymorphic. In this example the subclasses of Item have all been
trimmed down to just one for clarity: Ski. Events are polymorphic, too;
here I've included only most obvious subclass of event: Reservation. Each
reservation has one customer associated with it. A reservation is
associated to one or more Gearsets; each gearset has one or more
Items. Items to Gearsets is actually many-to-many, and this is accomplished
via three secondary tables: historyfile, currentfile, and futurefile. In
this way an item has a distinct history of what sets (and thereby what
rentals) it has been a part of in the past; a record of what it's doing
right now, and what sets (and thereby rentals) it is reserved to be a part
of in the future.
If I had to guess, maybe this 3-way split of secondary/association tables is
the thing that might be causing problems. I set it up that way to keep a
strong, up-front distinction between events being in an item's future vs.
its present case vs. its past. But I suppose I could actually achieve the
same thing with all gearset<>item associations being in a single table, and
just map the item's future/current/past to separate selects on that
table. Right?
But that's semi-off-topic. Again, I thought maybe I'd encounter some
cascade problems but I didn't expect a ConcurrentModificationError to happen
where it's happening here.
Here's the test script and stack trace:
# semi-minimal test example for ConcurrentModificationError:
# Deleted rowcount 0 does not match number of objects deleted 1
import sqlalchemy
from sqlalchemy import Table, BoundMetaData, Column
from sqlalchemy import Integer, String, ForeignKey, PrimaryKeyConstraint
from sqlalchemy import ForeignKeyConstraint, polymorphic_union
from sqlalchemy import mapper, relation, backref
from sqlalchemy import create_engine, create_session
db = create_engine('postgres://postgres:[EMAIL PROTECTED]:5432/test2',
encoding='utf-8')
metadata = BoundMetaData(db)
items = Table('items', metadata,
Column('id', Integer, primary_key=True, autoincrement=True),
Column('type', String(20)))
skis = Table('skis', metadata,
Column('id', Integer, primary_key=True),
Column('data', Integer),
ForeignKeyConstraint(['id'], ['items.id']))
events = Table('events', metadata,
Column('id', Integer, primary_key=True),
Column('etype', String(20)),
Column('data', Integer))
reservations = Table('reservations', metadata,
Column('id', Integer, ForeignKey('events.id'), primary_key=True),
Column('customer_id', Integer, ForeignKey('customers.id')),
Column('data', Integer))
gearsets = Table('gearsets', metadata,
Column('id', Integer, primary_key=True),
Column('event_id', Integer, ForeignKey('events.id')),
Column('data', Integer))
historyfile = Table('historyfile', metadata,
Column('item_id', Integer, ForeignKey('items.id'), primary_key=True),
Column('gearset_id', Integer, ForeignKey('gearsets.id'),
primary_key=True))
currentfile = Table('currentfile', metadata,
Column('item_id', Integer, ForeignKey('items.id'), primary_key=True),
Column('gearset_id', Integer, ForeignKey('gearsets.id'),
primary_key=True))
futurefile = Table('futurefile', metadata,
Column('item_id', Integer, ForeignKey('items.id'), primary_key=True),
Column('gearset_id', Integer, ForeignKey('gearsets.id'),
primary_key=True))
customers = Table('customers', metadata,
Column('id', Integer, primary_key = True, autoincrement=True),
Column('data', Integer))
class Item(object): pass
class Ski(Item): pass
class GearSet(object): pass
class Event(object): pass
class Reservation(Event): pass
class Customer(object): pass
item_join = polymorphic_union(
{
'ski':items.join(skis),
'item':items.select(items.c.type=='item'),
}, None, 'ijoin')
item_mapper = mapper(Item, items,
select_table=item_join,
polymorphic_on=item_join.c.type,
polymorphic_identity='item',
properties = {
'history': relation(GearSet,
secondary=historyfile,
backref=backref('items_history', uselist=True),
uselist=True),
'current': relation(GearSet,
secondary=currentfile,
backref=backref('items_current', uselist=True),
uselist=False),
'future': relation(GearSet,
secondary=futurefile,
backref=backref('items_future', uselist=True),
uselist=True)})
customer_mapper = mapper(Customer, customers)
gearset_mapper = mapper(GearSet, gearsets,
properties = {
'event': relation(Event,
backref=backref('gearsets', uselist=True),
uselist=False)
})
event_join = polymorphic_union(
{
'reservation':events.join(reservations),
'event':events.select(events.c.etype=='event'),
}, None, 'ejoin')
event_mapper = mapper(Event, events,
select_table=event_join,
polymorphic_on=event_join.c.etype,
polymorphic_identity='event')
reservation_mapper = mapper(Reservation, reservations,
inherits=event_mapper,
polymorphic_identity='reservation',
properties = {
'customer': relation(Customer,
backref=backref('reservations', uselist=True),
uselist=False)
})
ski_mapper = mapper(Ski, skis,
inherits=item_mapper,
polymorphic_identity='ski')
if __name__ == '__main__':
metadata.create_all()
session = create_session()
ski = Ski()
gearset = GearSet()
customer = Customer()
reservation = Reservation()
reservation.customer = customer
gearset.items_future.append(ski)
gearset.event = reservation
session.save(reservation)
session.flush()
item = session.query(Item).selectfirst()
item.future = []
session.delete(item)
session.flush()
session.close()
metadata.drop_all()
ConcurrentModificationError: Deleted rowcount 0 does not match number of
objects deleted 1
Traceback (innermost last):
File "h:\Documents and Settings\Eric\My
Documents\rentalproject\deletetest.py", line 1, in <module>
# semi-minimal test example for ConcurrentModificationError:
File "h:\Documents and Settings\Eric\My
Documents\rentalproject\deletetest.py", line 138, in <module>
session.flush()
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\session.py", line 302, in flush
self.uow.flush(self, objects)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 213, in flush
flush_context.execute()
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 400, in execute
UOWExecutor().execute(self, head)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1018, in execute
self.execute_save_steps(trans, task)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1037, in
execute_save_steps
self.execute_childtasks(trans, task, False)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1055, in
execute_childtasks
self.execute(trans, child, isdelete)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1018, in execute
self.execute_save_steps(trans, task)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1037, in
execute_save_steps
self.execute_childtasks(trans, task, False)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1055, in
execute_childtasks
self.execute(trans, child, isdelete)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1018, in execute
self.execute_save_steps(trans, task)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1037, in
execute_save_steps
self.execute_childtasks(trans, task, False)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1055, in
execute_childtasks
self.execute(trans, child, isdelete)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1018, in execute
self.execute_save_steps(trans, task)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1036, in
execute_save_steps
self.execute_dependencies(trans, task, True)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1051, in
execute_dependencies
self.execute_dependency(trans, dep, True)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 1029, in
execute_dependency
dep.execute(trans, isdelete)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\unitofwork.py", line 986, in execute
self.processor.process_dependencies(self.targettask, [elem.obj for elem in
self.targettask.polymorphic_todelete_elements if elem.obj is not None],
trans, delete=True)
File "h:\Python25\Lib\site-packages\sqlalchemy-
0.3.8-py2.5.egg\sqlalchemy\orm\dependency.py", line 372, in
process_dependencies
raise exceptions.ConcurrentModificationError("Deleted rowcount %d does not
match number of objects deleted %d" % (result.rowcount,
len(secondary_delete)))
Any observations/suggestions appreciated.
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/sqlalchemy?hl=en
-~----------~----~----~----~------~----~------~--~---