Here is a test case for the bug. The bug only manifests itself if the 
transaction that slices the list also modifies all the remaining objects in the 
list.

Barry

----- Original Message ----
From: Michael Bayer <[EMAIL PROTECTED]>
To: sqlalchemy@googlegroups.com
Sent: Wednesday, October 24, 2007 5:01:43 PM
Subject: [sqlalchemy] Re: Problem when slicing a relation list


hey Barry -

again, can you please attach a working test case for this one ?  attached is 
mine, which tests this exact operation for four diferent kinds of relation()s - 
one-to-many and many to many, with and without delete cascade on the relation.  
passes for 0.3 (including 0.3.10) and 0.4.





-----Inline Attachment Follows-----

from sqlalchemy import *

from sqlalchemy.orm import *



def test(m2m=False, cascade=False, useclear=False):

    engine = create_engine('sqlite://', echo=True)

    

    meta = MetaData(engine)



    a = Table('a', meta, Column('id', Integer, primary_key=True), Column('foo', 
String(30)))

    

    if m2m:

        b = Table('b', meta, Column('id', Integer, primary_key=True), 
Column('foo', String(30)))

    else:

        b = Table('b', meta, Column('id', Integer, primary_key=True), 
Column('foo', String(30)), Column('a_id', Integer, ForeignKey('a.id')))

    

    if m2m:

        atob = Table('atob', meta, Column('a_id', Integer, ForeignKey('a.id')), 
Column('b_id', Integer, ForeignKey('b.id')), )

    else:

        atob = None

        

    class A(object):

        def __init__(self, foo):

            self.foo = foo

    class B(object):

        def __init__(self, foo):

            self.foo = foo



    if cascade:

        use_cascade = "all, delete-orphan"

    else:

        use_cascade = "save-update"

        

    mapper(A, a, properties={

        'bs':relation(B, secondary=atob, cascade=use_cascade)

    })

    mapper(B, b)



    meta.create_all()



    a1 = A('a1')

    a1.bs.append(B('b1'))

    a1.bs.append(B('b2'))

    a1.bs.append(B('b3'))



    sess = create_session()

    sess.save(a1)

    sess.flush()



    if m2m:

        assert atob.count().scalar() == 3

    else:

        assert b.count(b.c.a_id == None).scalar() == 0



    assert b.count().scalar() == 3

        

    if useclear:

        sess.clear()

        

    a1 = sess.query(A).get(a1.id)

    assert len(a1.bs) == 3

    a1.bs = a1.bs[1:]

    sess.flush()

    

    if m2m:

        assert atob.count().scalar() == 2

    else:

        assert b.count(b.c.a_id != None).scalar() == 2



    if cascade:

        assert b.count().scalar() == 2

    else:

        assert b.count().scalar() == 3

    

    if useclear:

        sess.clear()

        

    a1 = sess.query(A).get(a1.id)

    assert len(a1.bs) == 2

        

for m2m in (True, False):

    for cascade in (True, False):

        for useclear in (True, False):

            test(m2m, cascade, useclear)




On Oct 24, 2007, at 4:18 PM, Barry Hart wrote:

I found a problem in SqlAlchemy 0.3.10 this week: slicing a list property 
caused the whole association list to be deleted. For example, suppose I want to 
remove the first 3 items from a list of related items:

my_obj.related = my_obj.related[3:]

When these changes were saved , my_obj.related was empty the next time it 
loaded. This code, however, worked correctly:

for i in range(0, 3):
    del my_obj.related[0]

Barry


__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around 
http://mail.yahoo.com 









__________________________________________________
Do You Yahoo!?
Tired of spam?  Yahoo! Mail has the best spam protection around 
http://mail.yahoo.com 
--~--~---------~--~----~------------~-------~--~----~
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 [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en
-~----------~----~----~----~------~----~------~--~---

from sqlalchemy import *
from sqlalchemy.ext.assignmapper import assign_mapper

import sqlalchemy
from sqlalchemy.ext import activemapper, sessioncontext

import datetime

engine = None

def create_engine():
    global engine
    engine = sqlalchemy.create_engine('sqlite://')
    #engine = sqlalchemy.create_engine('postgres://postgres:[EMAIL 
PROTECTED]:5432/testdb')
    metadata.connect(engine)

def create_session():
    return sqlalchemy.create_session(bind_to=engine)

def fuzzy_search(column, value):
    """Case insensitive search allowing partial string matches."""
    return func.lower(column).like('%%%s%%' % value.lower())

metadata = activemapper.metadata
create_engine()
session = activemapper.Objectstore(create_session)
activemapper.objectstore = session

g_count = 10
##########################################################################
# Classes
##########################################################################

class ObjX(object):

    def __init__(self):
        self.y = ObjY()
        
    def analyze(self):
        #del self.y.zs[0] # Using this line works.
        self.y.zs = self.y.zs[1:] # Using this line fails.
        for i in range(0, len(self.y.zs)):
            s = self.y.zs[i]
            
            # Modify the other objects in the list. Without this, the test 
still works.
            s.units = 10
#

class ObjY(object): pass
class ObjZ(object): pass

##########################################################################
# Tables
##########################################################################

obj_x_table = Table(
    'obj_x', metadata,
    Column('id', Integer, primary_key=True),
    Column('desc', Unicode(255), default="")
    )
    
obj_y_table = Table(
    'obj_y', metadata,
    Column('id', Integer, primary_key=True),
    Column('obj_x_id', Integer, ForeignKey('obj_x.id'))
    )
    
obj_z_table = Table(
    'predict_time_period', metadata,
    Column('id', Integer, primary_key=True),
    Column('obj_y_id', Integer, ForeignKey('obj_y.id')),
    Column('start_date', DateTime),
    Column('units', Float)
    )


##########################################################################
# Mappings
##########################################################################

assign_mapper(session.context, ObjX, obj_x_table, properties=dict(
    y=relation(ObjY, uselist=False)
    ))
    
assign_mapper(session.context, ObjY, obj_y_table, properties=dict(
    zs=relation(ObjZ, order_by=obj_z_table.c.start_date, 
cascade="all,delete-orphan")
    ))
    
assign_mapper(session.context, ObjZ, obj_z_table)


##########################################################################
# Functions
##########################################################################

def createX():
    x = ObjX()
    
    for i in range(0, g_count):
        z = ObjZ()
        z.units = i
        x.y.zs.append(z)
    
    return x

##########################################################################
# Main program
##########################################################################

obj_x_table.create(checkfirst=True)
obj_y_table.create(checkfirst=True)
obj_z_table.create(checkfirst=True)

x = createX()
assert len(x.y.zs) == g_count
session.flush()
session.clear()

x = ObjX.select()[0]
assert len(x.y.zs) == g_count # Starts with g_count objects
x.analyze() # Slices one object from the list
assert len(x.y.zs) == g_count - 1 # Check the count again
session.flush()
session.clear()

x = ObjX.select()[0]
assert len(x.y.zs) == g_count - 1, 'Expected %d, got %d' % (g_count - 1, 
len(x.y.zs))

Reply via email to