thanks! if they're deep "incorrect query" things in the ORM like this, I fix those really fast. Inconvenient things with Core / typing system etc., not so much :)


On 06/19/2017 12:12 AM, Sherwin Yu wrote:
Mike, thanks for responding and fixing this so quickly (https://bitbucket.org/zzzeek/sqlalchemy/issues/4011/joined-subclass-to-2-level-subquery-load#comment-37650645)!

Just wanted to take a moment to express my gratitude for your work on SQLA -- it's absolutely critical to our company (Benchling). A few of our other engineers have reported issues before and you've always been quick to respond. Thank you!

On Wednesday, June 14, 2017 at 10:23:06 PM UTC-7, Sherwin Yu wrote:



    We found a bug involving filtering a JTI relation and
    subqueryloading. The emitted SQL for the subqueryload does not
    correctly join the child and parent JTI tables, resulting in a cross
    product.

    Info:

      * psql (PostgreSQL) 9.5.7
      * SQLAlchemy 0.9.10

    Suppose we have the following relations (see full repro insructions
    below):

      * Dept
      * Owner
          o dept_id
      * Milestone
          o owner_id
      * Sprint (subclass of Milestone)
          o id references milestone.id <http://milestone.id>


    When doing the following query:

    
Sprint.query.filter(Sprint.id.in_([1])).options(db.subqueryload(Sprint.owner).subqueryload(Owner.dept)).all()

    We see the three following queries, the third of which has an
    incorrect inner SELECT:

    SELECTsprint.id <http://sprint.id>  AS sprint_id,milestone.id 
<http://milestone.id>  AS milestone_id, milestone.milestone_type AS 
milestone_milestone_type, milestone.owner_id AS milestone_owner_id
    FROM milestone JOIN sprint ONmilestone.id <http://milestone.id>  =sprint.id 
<http://sprint.id>
    WHEREsprint.id <http://sprint.id>  IN (%(id_1)s)
    INFO:sqlalchemy.engine.base.Engine:{'id_1': 1}

    SELECTowner.id <http://owner.id>  AS owner_id, owner.dept_id AS 
owner_dept_id, anon_1.milestone_owner_id AS anon_1_milestone_owner_id
    FROM (SELECT DISTINCT milestone.owner_id AS milestone_owner_id
    FROM milestone JOIN sprint ONmilestone.id <http://milestone.id>  =sprint.id 
<http://sprint.id>
    WHEREsprint.id <http://sprint.id>  IN (%(id_1)s)) AS anon_1 JOIN owner ONowner.id 
<http://owner.id>  = anon_1.milestone_owner_id ORDER BY anon_1.milestone_owner_id
    INFO:sqlalchemy.engine.base.Engine:{'id_1': 1}

    SELECTdept.id <http://dept.id>  AS dept_id, owner_1.dept_id AS 
owner_1_dept_id
    FROM (SELECT DISTINCT milestone.owner_id AS milestone_owner_id
    FROM milestone, sprint
    WHEREsprint.id <http://sprint.id>  IN (%(id_1)s)) AS anon_1 JOIN owner AS owner_1 
ONowner_1.id <http://owner_1.id>  = anon_1.milestone_owner_id JOIN dept ONdept.id 
<http://dept.id>  = owner_1.dept_id ORDER BY owner_1.dept_id
    INFO:sqlalchemy.engine.base.Engine:{'id_1': 1}

    In particular, the inner SELECT on the third query, |SELECT DISTINCT
    milestone.owner_id AS milestone_owner_id FROM milestone, sprint
    WHERE sprint.id <http://sprint.id> IN (%(id_1)s)| (corresponding to
    |subqueryload(Owner.dept)|) is selecting from tables milestone and
    sprint without specifying a join condition, which results in the a
    cross product of the two tables (the entire milestone table is
    returned along with sprint.id <http://sprint.id> = 1).

    The temporary work around we found was to replace
    |.filter(Sprint.id.in_([1]))| to |.filter(Milestone.id.in_([1]))| ,
    which then emits this query instead:

    SELECTdept.id <http://dept.id>  AS dept_id, owner_1.dept_id AS 
owner_1_dept_id
    FROM (SELECT DISTINCT milestone.owner_id AS milestone_owner_id
    FROM milestone
    WHEREmilestone.id <http://milestone.id>  IN (%(id_1)s)) AS anon_1 JOIN owner AS owner_1 
ONowner_1.id <http://owner_1.id>  = anon_1.milestone_owner_id JOIN dept ONdept.id 
<http://dept.id>  = owner_1.dept_id ORDER BY owner_1.dept_id

    Which avoids taking the cross product of milestone and sprint.
    (Note, this only works since sprint.id <http://sprint.id> is the
    foreign key to milestone.id <http://milestone.id>, so filtering by
    milestone.id <http://milestone.id> is sufficient)


        Repro

    # Create some tables:
    CREATE TABLE dept (id integer PRIMARY KEY);
    CREATE TABLE owner (id integer PRIMARY KEY, dept_id integer, CONSTRAINT 
owner_dept_id_dept_id_fkey FOREIGN KEY (dept_id) REFERENCES dept (id));
    CREATE TABLE milestone (id integer PRIMARY KEY, milestone_type VARCHAR(64) 
NOT NULL, owner_id integer, CONSTRAINT milestone_owner_id_owner_id_fkey FOREIGN 
KEY (owner_id) REFERENCES dept (id));
    CREATE TABLE sprint (id integer PRIMARY KEY, CONSTRAINT 
sprint_id_milestone_id_fkey FOREIGN KEY (id) REFERENCES milestone (id));

    class Milestone(db.Model):
         __tablename__ = 'milestone'
         id = db.Column(db.Integer, primary_key=True)

         milestone_type = db.Column(db.String(64), nullable=False)
         owner_id = db.Column(
             db.Integer,
             db.ForeignKey('owner.id <http://owner.id>', 
name='milestone_owner_id_owner_id_fkey'))

         owner = db.relationship('Owner', back_populates='milestone')

         __mapper_args__ = {
             'polymorphic_on': milestone_type,
         }


    class Sprint(Milestone):
         __tablename__ = 'sprint'
         id = db.Column(db.Integer,
                        db.ForeignKey('milestone.id <http://milestone.id>', 
name='sprint_id_milestone_id_fkey'),
                        primary_key=True)

         __mapper_args__ = {
             'polymorphic_identity': 'sprint'
         }


    class Owner(db.Model):
         __tablename__ = 'owner'
         id = db.Column(db.Integer, primary_key=True)

         dept_id = db.Column(db.Integer,
                             db.ForeignKey('dept.id <http://dept.id>', 
name='owner_dept_id_dept_id_fkey'))
         dept = db.relationship('Dept', back_populates='owner')
         milestone = db.relationship('Milestone', back_populates='owner')


    class Dept(db.Model):
         __tablename__ = 'dept'
         id = db.Column(db.Integer, primary_key=True)
         owner = db.relationship('Owner', back_populates='dept')


    Then insert some data:

    d = Dept(); o = Owner(); o.dept = d;  s = Sprint(); s.owner = o;
    s.id <http://s.id>  = 1;o.id <http://o.id>  = 1;d.id <http://d.id>  = 1;
    db.session.add(o)
    db.session.commit()


    Then attempt this query:

    
Sprint.query.filter(Sprint.id.in_([1])).options(db.subqueryload(Sprint.owner).subqueryload(Owner.dept)).all()


--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
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 <mailto:sqlalchemy+unsubscr...@googlegroups.com>. To post to this group, send email to sqlalchemy@googlegroups.com <mailto:sqlalchemy@googlegroups.com>.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 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 https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to