Kent wrote:
> Maybe you're still looking into that, but I still don't understand why
> this:
>
> merged.orderdetails[0].saleprice
>
> causes a new issue to the database.

as I mentioned earlier, if the value isn't in __dict__ on a persistent
instance, it will be loaded when accessed.   Your example doesn't set this
field on the object to be merged - so the merge operation actually expires
the value on the loaded object.   At the moment that's because the
attribute missing from __dict__ is assumed to have been expired, so it
expires it on the to-be-merged side as well (otherwise, what would it
merge it to?  particularly if the load=False flag is set).


>
> (Also, wondering if some databases allow a primary key to be null...)

I've researched this in the past and they don't.   I will look into
re-introducing "allow_null_pks" as a new flag "allow_partial_pks",
defaults to True, will be honored by merge(), you set yours to False. 
this is 0.6 only.



>
> Thanks again.
>
>
> On Feb 9, 2:50 pm, Kent <k...@retailarchitects.com> wrote:
>> Ah ha.  Thanks for tracking that down, makes sense.
>>
>> On Feb 9, 2:25 pm, "Michael Bayer" <mike...@zzzcomputing.com> wrote:
>>
>> > Kent wrote:
>> > > I am on version 0.5.8.
>>
>> > part of your issue is this:
>>
>> > line1.lineid = '15'
>>
>> > should be this:
>>
>> > line1.lineid = 15
>>
>> > This because the value comes back from the DB as a numeric, not a
>> string,
>> > producing the wrong identity key ( (<class '__main__.OrderDetail'>,
>> > ('0206001A134', '15')) vs (<class '__main__.OrderDetail'>,
>> > (u'0206001A134', 15)) ).
>>
>> > The merge then issues the eager load for the lead order + 1 detail,
>> and a
>> > second select for the additional detail.
>>
>> > > As far as how upset it is making me: well, I certainly have no right
>> > > to demand this very nice, free software be enhanced or changed: I'm
>> > > just grateful for it.
>>
>> > > We will be supporting clients on webservers that are removed by a
>> long
>> > > distance from the database server, so I would like to limit the
>> round
>> > > trips as much as is feasible...
>>
>> > > I've taken out most everything and left the logic in a simple case
>> to
>> > > create the behavior.  Here is the script that will demonstrate:
>>
>> > > =============================================================================
>>
>> > > from sqlalchemy import *
>> > > from sqlalchemy.orm import *
>>
>> > > engine = create_engine('postgres://dbuser:dbu...@localhost:5444/
>> > > dbuser',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)
>>
>> > > metadata.create_all(engine)
>>
>> > > o=Order()
>> > > o.orderid = '0206001A134'  #this order exists in the database -
>> You'll
>> > > need to set add it to the DB
>>
>> > > line1=OrderDetail()         #line exists in database - You'll need
>> to
>> > > set add it to the DB
>> > > line1.orderid = '0206001A134'
>> > > line1.lineid = '15'
>>
>> > > line2=OrderDetail()         #new line does not exist in database
>> > > line2.orderid = '0206001A134'
>>
>> > > o.orderdetails = [line1, line2]
>>
>> > > #
>> > > #
>> > > # Question a above - the following merge results in 3 SELECT
>> > > statements, but the first
>> > > # is an eagerly loaded query joined with orderdetails.  So, unless
>> the
>> > > JOIN returned fewer rows
>> > > # (for example, an inner join instead of outer was used), all the
>> > > orderdetails should
>> > > # already be in existence as persistent objects:
>> > > merged=session.merge(o)
>>
>> > > merged in session.new    #this order exists in the database
>>
>> > > merged.orderdetails[0]
>> > > merged.orderdetails[0] in session.new  # already in database (in new
>> =
>> > > False)
>>
>> > > merged.orderdetails[1]
>> > > merged.orderdetails[1] in session.new  # not yet in database (in new
>> =
>> > > True)
>>
>> > > #
>> > > # Question b:
>> > > # Why does this issue another select?  The object should have been
>> > > eagerly loaded,
>> > > # but even if not that, it was later reSELECTED during the merge()
>> > > merged.orderdetails[0].saleprice
>>
>> > > #
>> > > # Question c:
>> > > # Are there databases that allow part of a primary key to be
>> undefined
>> > > (None)?
>> > > # That is a foreign concept to me, so I expected this object would
>> > > realize it
>> > > # needn't query the database.
>> > > merged.orderdetails[1].saleprice
>>
>> > > =============================================================================
>>
>> > > Thanks in advance,
>>
>> > > Kent
>>
>> > > On Feb 8, 9:57 pm, Michael Bayer <mike...@zzzcomputing.com> wrote:
>> > >> On Feb 8, 2010, at 4:12 PM, Kent wrote:
>>
>> > >> > Ok, here are my questions:
>>
>> > >> > a) The merge eagerloads the order, along with its lines, but
>> then,
>> > >> > directly afterward, issues two additional SELECT statements for
>> the
>> > >> > two lines, even though these were already eagerly-loaded. That
>> > >> > surprised me. Why is that occurring?
>>
>> > >> I dont know. I would need more than code fragments to reproduce
>> your
>> > >> behavior. (nor do I know what version you're on). It doesn't
>> > >> reproduce with a simple test.
>>
>> > >> > b) When I ask for the property .saleprice on the order line,
>> another
>> > >> > SELECT statement is issued. Why does that occur when it was
>> eagerly
>> > >> > loaded already?
>>
>> > >> Same. If the value is in __dict__ it would not issue another load.
>>
>> > >> > c) In the case of line2, can SQLAlchemy be made to realize that
>> part
>> > >> > of the primary key is not set and therefore there is no reason to
>> > >> > attempt a fetch from the database? It already detected this was a
>> new
>> > >> > record during the merge.
>>
>> > >> the "fetch for None", meaning issuing a fetch when the primary key
>> was
>> > >> completely None, was resolved in a recent 0.5 version, probably
>> 0.5.8.
>> > >> However, a partial primary key is considered to be valid. There is
>> a
>> > >> flag on the mapper() called allow_null_pks=True which in 0.5 is set
>> to
>> > >> False by default - it means that partial primary key is not valid.
>> > >> That flag is not in fact checked by merge() in this case, which is
>> > >> because the flag was already being removed in 0.6 by the time this
>> fix
>> > >> went into place. The flag only led to confusion over and over again
>> > >> when users mapped to outerjoins, and didn't receive rows. Whereas
>> > >> nobody ever complained about merge issuing a load for None as a key
>> -
>> > >> the issue was fixed because I noticed it myself. So you're the
>> first
>> > >> person to ever complain about it, which is unfortunate since it may
>> have
>> > >> led to a different path for the allow_null_pks flag. I dont like
>> the
>> > >> flag very much since its obscure and nobody really ever used it
>> unless I
>> > >> told them to directly. The only other way would be for the mapper
>> to
>> > >> "guess" if nulls are allowed in the primary key based on the type
>> of
>> > >> table/join structure its mapped to. So it depends on how upset this
>> > >> behavior is making you if I really need to find some way for it to
>> > >> differentiate between None-capable primary keys or not.
>>
>> > >> > Thanks in advance.
>>
>> > >> > Kent
>>
>> > >> > --
>> > >> > You received this message because you are subscribed to the
>> Google
>> > >> Groups "sqlalchemy" group.
>> > >> > To post to this group, send email to sqlalch...@googlegroups.com.
>> > >> > To unsubscribe from this group, send email to
>> > >> sqlalchemy+unsubscr...@googlegroups.com.
>> > >> > For more options, visit this group
>> > >> athttp://groups.google.com/group/sqlalchemy?hl=en.
>>
>> > > --
>> > > You received this message because you are subscribed to the Google
>> Groups
>> > > "sqlalchemy" group.
>> > > To post to this group, send email to sqlalch...@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.
>>
>>
>
> --
> You received this message because you are subscribed to the Google Groups
> "sqlalchemy" group.
> To post to this group, send email to sqlalch...@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.
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalch...@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