I understand, I had a feeling it would be something like that. Don't worry,
I can work around it by using a subqueryload() instead (which I guess fixes
it by changing the order things are loaded?)

Thank you very much for taking the time to answer; still loving SQLAlchemy!


On Tue, Mar 25, 2014 at 1:06 PM, Michael Bayer <mike...@zzzcomputing.com>wrote:

>
> On Mar 25, 2014, at 7:48 AM, Philip Scott <safetyfirstp...@gmail.com>
> wrote:
>
> Ah, so, it turns out to be more subtle than I first thought. It took me
> quite a while to narrow it down to an easily reproducible case. To trigger
> the behavior you need to be: joinedloading() along a backref, and also I
> think it matters that I am joining back onto the same table and returning a
> bunch of objects. Quite a corner case I think.
>
> This code demonstrates the behavior - it issues a second query in the for
> loop for the 'A' which has no child.
>
>
>
> this is very difficult to resolve and it may have been something I've said
> was unfixable in the past.  The "a2" object here is loaded in two different
> contexts, one is as the joined loaded child of a1, the other as a first
> class result.   Because when the query orders by a1, a2, we hit a1 first,
> a2 is necessarily loaded as the child of a1.  The joined loading only goes
> one level deep, that is, it doesn't load the children of children, unless
> you told it to by saying joinedload("child").joinedload("child").  So
> a2.child's loader is declared as "not loaded".     then on the next row it
> comes in as a first class result, but the a2 object we get there is only an
> identity map lookup - this object is already loaded.
>
> Adjusting this behavior would require the loading logic figure out
> mid-results that the context for a particular object is changing.  pretty
> complicated.
>
>
>
>
> from sqlalchemy import *
> from sqlalchemy.orm import *
> from sqlalchemy.ext.declarative import declarative_base
>
> Base = declarative_base()
>
> class A(Base):
>     __tablename__ = 'a'
>
>     id = Column(Integer, primary_key=True)
>
>     join_table = Table('parent_child', Base.metadata,
>                         Column('id_a', ForeignKey('a.id')),
>                         Column('id_b', ForeignKey('a.id')))
>
>     parent = relationship("A",
>                       secondary=join_table,
>                       primaryjoin = (id == join_table.c.id_a),
>                       secondaryjoin = (id == join_table.c.id_b),
>                       uselist=False,
>                       backref=backref("child", uselist=False))
>
> e = create_engine("sqlite://", echo=True)
> Base.metadata.create_all(e)
>
> sess = Session(e)
> a1 = A()
> a2 = A(parent=a1)
> sess.add(a1)
> sess.add(a2)
> sess.commit()
> sess.close()
>
> results = sess.query(A).options(joinedload("child")).all()
> print "----"
> for a in results:
>     print a.child is None
>
>
>
>
>
> On Mon, Mar 24, 2014 at 8:34 PM, Michael Bayer 
> <mike...@zzzcomputing.com>wrote:
>
>>
>> On Mar 24, 2014, at 2:09 PM, Philip Scott <safetyfirstp...@gmail.com>
>> wrote:
>>
>> >
>> > Is this a bug, or perhaps some expected side effect of the joined load?
>>
>> seemed like something that might be possible but the scalar loader is
>> initializing the attribute to None to start with, here's a simple test that
>> doesn't show your behavior, so see if you can just modify this one to show
>> what you are seeing.  note we only need to see that 'bs' is in a1.__dict__
>> to prevent a lazyload.
>>
>> from sqlalchemy import *
>> from sqlalchemy.orm import *
>> from sqlalchemy.ext.declarative import declarative_base
>>
>> Base = declarative_base()
>>
>> class A(Base):
>>     __tablename__ = 'a'
>>
>>     id = Column(Integer, primary_key=True)
>>     bs = relationship("B", secondary=Table('atob', Base.metadata,
>>                         Column('aid', ForeignKey('a.id')),
>>                         Column('bid', ForeignKey('b.id'))
>>                     ),
>>             uselist=False)
>>
>> class B(Base):
>>     __tablename__ = 'b'
>>
>>     id = Column(Integer, primary_key=True)
>>
>> e = create_engine("sqlite://", echo=True)
>> Base.metadata.create_all(e)
>>
>> sess = Session(e)
>> sess.add(A())
>> sess.commit()
>> sess.close()
>>
>> a1 = sess.query(A).options(joinedload("bs")).first()
>> assert 'bs' in a1.__dict__
>> assert a1.__dict__['bs'] is None
>> assert a1.bs is None
>>
>>
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "sqlalchemy" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to sqlalchemy+unsubscr...@googlegroups.com.
>> To post to this group, send email to sqlalchemy@googlegroups.com.
>> Visit this group at http://groups.google.com/group/sqlalchemy.
>> For more options, visit https://groups.google.com/d/optout.
>>
>
>
> --
> You received this message because you are subscribed to the Google Groups
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to sqlalchemy+unsubscr...@googlegroups.com.
> To post to this group, send email to sqlalchemy@googlegroups.com.
> Visit this group at http://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/d/optout.
>
>
>  --
> You received this message because you are subscribed to the Google Groups
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to sqlalchemy+unsubscr...@googlegroups.com.
> To post to this group, send email to sqlalchemy@googlegroups.com.
> Visit this group at http://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to