Hi,

I think I have detected two bugs for PostgreSQL databases. I don't think is 
a different behavior between 0.6 and 0.7 as there is no problem with sqlite.

In the following code I have a very simple model, which contains a helper 
"pre-process" list class, which just transforms strings into the correct 
model object. Very similar to @collection.converter, however it works for 
append and extend. It works without errors (obviously w/o MutableComposite) 
in 0.6 and sqlite in 0.7. But the strange part is that it follows different 
behavior depending on when you add the parent object to the session: if you 
add it after creating the "company" (just like I put it here), then it will 
not insert the "data" object (see the produced log, if you change it to the 
sqlite engine it will work); but if you add and commit it, before and after 
the append operation it will fail with an IntegrityError in both sqlite and 
postgresql (this is why I doubt if it is a different behavior or not).

Either way I think that adding the object, committing it, then adding it 
again, and committing it, although very wasteful, should behave without 
error, specially for occasions when you don't know how your object has been 
handled in relation with your database.

The other bug is just for PostgreSQL and can be reproduced by uncommenting 
the "metadata.create_all()" line of code. It appears that is trying to 
create the data table before company. It doesn't matter in which order are 
defined the same error is raised.

Thanks

from sqlalchemy import create_engine, MetaData, Table, Column, Integer, \
                       String, ForeignKey
from sqlalchemy.orm.collections import collection
from sqlalchemy.orm import mapper, relationship, sessionmaker, composite
from sqlalchemy.ext.mutable import MutableComposite

engine = create_engine('postgresql://user:password@localhost:5432/db', 
echo=True)
#engine = create_engine('sqlite:///:memory:', echo=True)
metadata = MetaData()

company = Table(
    'company',
    metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String, nullable=False),
    Column('x', Integer, nullable=False),
    Column('y', Integer, nullable=False),
    sqlite_autoincrement=True
)

data = Table(
    'data',
    metadata,
    Column('id', Integer, primary_key=True),
    Column('data', String, nullable=False),
    Column('company', Integer, ForeignKey('company.id'), nullable=False),
    sqlite_autoincrement=True
)

metadata.bind = engine
#metadata.drop_all()
company.create()
data.create()
#metadata.create_all()


class PreprocessList(list):
    def __init__(self, items=None):
        if items is None:
            super(PreprocessList, self).__init__()
        else:
            super(PreprocessList, self).__init__(
                [self.preprocess(item) for item in items]
            )

    def preprocess(self, value):
        return value

    def __setitem__(self, key, value):
        value = self.preprocess(value)
        super(PreprocessList, self).__setitem__(key, value)
    
    @collection.internally_instrumented
    def append(self, value):
        value = self.preprocess(value)
        super(PreprocessList, self).append(value)
    
    @collection.internally_instrumented
    def extend(self, items):
        for item in items:
            self.append(item)
            
class AppenderList(PreprocessList):
    def preprocess(self, value):
        v = Data()
        v.data = value + 'data'
        return v
    
    def __getitem__(self, key):
        value = super(AppenderList, self).__getitem__(key)
        return value.data

class Data(object):
    pass

class Company(object):
    pass

class Point(MutableComposite):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __setattr__(self, key, value):
        "Intercept set events"

        # set the attribute
        object.__setattr__(self, key, value)

        # alert all parents to the change
        self.changed()

    def __composite_values__(self):
        return self.x, self.y

    def __eq__(self, other):
        return isinstance(other, Point) and \
            other.x == self.x and \
            other.y == self.y

    def __ne__(self, other):
        return not self.__eq__(other)

mapper(Company, company, properties={
    'id': company.c.id,
    'name': company.c.name,
    'data': relationship(
        Data,
        collection_class=AppenderList
    ),
    'point': composite(Point, company.c.x, company.c.y)
})
mapper(Data, data)
session = sessionmaker(bind=engine)()

# CHANGE BELOW

c = Company()
c.name = 'company 1'
c.data = AppenderList()
c.point = Point(3, 5)
session.add(c)
session.commit()

c.data.append('a')

#session.add(c)
session.commit()

metadata.drop_all()

print 'ok'


-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/sqlalchemy/-/9vkKkIpe9aEJ.
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.

Reply via email to