ColumnProperty merge from nonexistent prop shouldn't expire loaded prop if load=True

Issue #1681 resolved
Mike Bayer repo owner created an issue

it should likely reset any history on the attribute, considering that the next load operation in the trans would get that value from an expiration.

however, need to understand fully whats going on in test_pickled.PickleTest.test_instance_deferred_cols before changing anything.

Comments (5)

  1. Mike Bayer reporter

    consider also:

    Index: lib/sqlalchemy/orm/properties.py
    ===================================================================
    --- lib/sqlalchemy/orm/properties.py    (revision 6791)
    +++ lib/sqlalchemy/orm/properties.py    (working copy)
    @@ -115,7 +115,8 @@
                     impl = dest_state.get_impl(self.key)
                     impl.set(dest_state, dest_dict, value, None)
             else:
    -            dest_state.expire_attributes(dest_dict, [self.key](self.key))
    +            if self.key not in dest_dict:
    +                dest_state.expire_attributes(dest_dict, [self.key](self.key))
    
         def get_col_value(self, column, value):
             return value
    
  2. Former user Account Deleted

    This seems fixed for merging objects where the entity already exists in the database.

    If the entity is not in the database, however, {{{merge()}}} will create a ''Pending'' instance from the passed ''Transient'' object. With {{{load=True}}}, this ''Pending'' instance should not have expired attributes, since it was just loaded from the database.

    The following script illustrates:

    from sqlalchemy import *
    from sqlalchemy.orm import *
    
    engine = create_engine('postgres://user:pass@localhost:5444/name',echo=True)
    metadata = MetaData()
    Session = sessionmaker(bind=engine)
    session = Session()
    
    order_table = Table("orders", metadata,
        Column("orderid", Unicode, primary_key=True)
    )
    
    orderdetail_table = Table("orderdetails",metadata,
        Column("orderid", Unicode, ForeignKey('orders.orderid'), primary_key=True),
        Column("lineid", Integer, primary_key=True),
        Column("saleprice", Numeric, nullable=False)
    )
    
    class Order(object):
        pass
    
    class OrderDetail(object):
        pass
    
    order_mapper = mapper(Order, order_table,
            properties=dict(orderdetails=relation(OrderDetail, 
                            cascade='all,delete-orphan', 
                            single_parent=True,
                            lazy=False,
                            backref=backref('parentorder',
                                    cascade='refresh-expire,expunge'))))
    
    orderdetail_mapper = mapper(OrderDetail, orderdetail_table,
            allow_partial_pks=False)
    
    o=Order()
    o.orderid = u'SALE35426'  # NOT IN THE DATABASE, WILL be added by merge()
    
    line=OrderDetail()      
    line.orderid = u'SALE35426' # NOT IN THE DATABASE, WILL be added by merge()
    line.lineid = 2
    
    o.orderdetails = [line](line)
    merged=session.merge(o)
    
    merged.orderdetails[0](0).saleprice
    

    During the {{{merge()}}} call, if {{{load=True}}} and the entity is not found, it seems to be all attributes should be fully loaded and not expired. However, the getattr on saleprice in the last line shows otherwise:

    >>> merged=session.merge(o)
    2010-05-03 10:43:54,016 INFO sqlalchemy.engine.base.Engine.0x...af10 select version()
    2010-05-03 10:43:54,021 INFO sqlalchemy.engine.base.Engine.0x...af10 {}
    2010-05-03 10:43:54,023 INFO sqlalchemy.engine.base.Engine.0x...af10 select current_schema()
    2010-05-03 10:43:54,023 INFO sqlalchemy.engine.base.Engine.0x...af10 {}
    ############ merge() looks up order... doesn't find it ##############
    2010-05-03 10:43:54,026 INFO sqlalchemy.engine.base.Engine.0x...af10 BEGIN
    2010-05-03 10:43:54,027 INFO sqlalchemy.engine.base.Engine.0x...af10 SELECT
    orders.orderid AS orders_orderid, orderdetails_1.orderid AS orderdetails_1_orderid,
    orderdetails_1.lineid AS orderdetails_1_lineid, orderdetails_1.saleprice AS 
    orderdetails_1_saleprice
    FROM orders LEFT OUTER JOIN orderdetails AS orderdetails_1 ON orders.orderid = orderdetails_1.orderid
    WHERE orders.orderid = %(param_1)s
    2010-05-03 10:43:54,027 INFO sqlalchemy.engine.base.Engine.0x...af10 {'param_1': u'SALE35426'}
    ############ merge() looks up orderdetail... doesn't find it ##############
    2010-05-03 10:43:54,033 INFO sqlalchemy.engine.base.Engine.0x...af10 SELECT 
    orderdetails.orderid AS orderdetails_orderid, orderdetails.lineid AS 
    orderdetails_lineid, orderdetails.saleprice AS orderdetails_saleprice
    FROM orderdetails
    WHERE orderdetails.orderid = %(param_1)s AND orderdetails.lineid = %(param_2)s
    2010-05-03 10:43:54,033 INFO sqlalchemy.engine.base.Engine.0x...af10 {'param_1': u'SALE35426', 'param_2': 2}
    >>>
    ############ since it was just 'loaded' from the database, (not found)
    ############ no attributes should be expired, should they?
    >>> merged.orderdetails[0](0).saleprice
    2010-05-03 10:43:54,035 INFO sqlalchemy.engine.base.Engine.0x...af10 SELECT orderdetails.saleprice AS orderdetails_saleprice
    FROM orderdetails
    WHERE orderdetails.orderid = %(param_1)s AND orderdetails.lineid = %(param_2)s
    2010-05-03 10:43:54,036 INFO sqlalchemy.engine.base.Engine.0x...af10 {'param_1': u'SALE35426', 'param_2': 2}
    >>>
    

    Functionally, nothing is wrong, but this would be a nice performance enhancement.

    P.S. Please add kb@retailarchitects.com to cc: if you do something with this or if you open another ticket. How do I go about getting a trac user id?

  3. Log in to comment