Skip to content

adaption of Label breaks further operations because it does not reset the _element attribute #3445

Closed
@sqlalchemy-bot

Description

@sqlalchemy-bot
Collaborator

Migrated issue, originally created by Lukas Siemon (@lukas-gitl)

The test case below generates the following query:

SELECT
   venue.id AS venue_id   
FROM
   venue   
WHERE
   EXISTS (
      SELECT
         1   
      FROM
         label,
         venue AS venue_alias2 
      JOIN
         venue_to_label AS venue_to_label_1 
            ON venue_alias2.id = venue_to_label_1.venue_id 
      JOIN
         label AS label_alias 
            ON label_alias.id = venue_to_label_1.label_id   
      WHERE
         venue.id = venue_alias2.id 
         AND (
            label.id = :param_1
         ) IS 1
   )

However I'd expect the subquery to use the aliased Label model, i.e. instead of "label.id = :param_1" it should say "label_alias.id = :param_1".

The problem seems to be with the column_property.

I've tested the issue with
SQLAlchemy==1.0.5
Flask==0.10.1
Flask-SQLAlchemy==2.0

Minimal test case:

import unittest
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, aliased, column_property
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = ("sqlite://")
db = SQLAlchemy(app)

Base = declarative_base()

venue_to_label = Table(
    'venue_to_label', db.metadata,
    Column('venue_id', Integer, ForeignKey('venue.id'), primary_key=True),
    Column('label_id', Integer, ForeignKey('label.id'), primary_key=True)
)
label_to_label = Table(
    'label_to_label', db.metadata,
    Column('label_id', Integer, ForeignKey('label.id'), primary_key=True),
    Column('label_id', Integer, ForeignKey('label.id'), primary_key=True)
)


class Label(db.Model):
    __tablename__ = 'label'
    id = Column(Integer, primary_key=True, nullable=False)
    is_first = column_property(id == 1)


class Venue(db.Model):
    __tablename__ = 'venue'
    id = Column(Integer, primary_key=True, nullable=False)
    labels = relationship(Label, secondary=venue_to_label)

Base.metadata.drop_all(bind=db.engine)
Base.metadata.create_all(bind=db.engine)


class TestColPropAliasBug(unittest.TestCase):

    def test_column_property_aliased_bug(self):

        query = db.session.query(Venue)

        venue_alias2 = aliased(Venue, name="venue_alias2")
        subquery = db.session.query(venue_alias2)
        label_alias = aliased(venue_alias2.labels, name="label_alias")
        subquery = subquery.join(label_alias, venue_alias2.labels)

        # relate queries
        subquery = subquery.filter(Venue.id == venue_alias2.id)

        # filter_ = label_alias.id.is_(1)  # works as expected
        filter_ = label_alias.is_first.is_(True)  # doesn't work as expected
        subquery = subquery.filter(filter_)
        query = query.filter(subquery.exists())

        print query

Activity

sqlalchemy-bot

sqlalchemy-bot commented on Jun 10, 2015

@sqlalchemy-bot
CollaboratorAuthor

Changes by Lukas Siemon (@lukas-gitl):

  • added labels: sql
sqlalchemy-bot

sqlalchemy-bot commented on Jun 10, 2015

@sqlalchemy-bot
CollaboratorAuthor

Michael Bayer (@zzzeek) wrote:

here's a very short test:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Label(Base):
    __tablename__ = 'label'
    id = Column(Integer, primary_key=True, nullable=False)
    is_first = column_property(id == 1)

s = Session()

label_alias = aliased(Label)
q = s.query(label_alias)
print q.filter(label_alias.is_first)
print "-------"
print q.filter(label_alias.is_first.self_group())

here's a potential patch, needs more specific tests:

diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
index a178ed9..25e8357 100644
--- a/lib/sqlalchemy/sql/elements.py
+++ b/lib/sqlalchemy/sql/elements.py
@@ -3103,7 +3103,8 @@ class Label(ColumnElement):
         return self.element,
 
     def _copy_internals(self, clone=_clone, anonymize_labels=False, **kw):
-        self.element = clone(self.element, **kw)
+        self.__dict__.pop('element', None)
+        self._element = clone(self._element, **kw)
         self.__dict__.pop('_allow_label_resolve', None)
         if anonymize_labels:
             self.name = self._resolve_label = _anonymous_label(

sqlalchemy-bot

sqlalchemy-bot commented on Jun 10, 2015

@sqlalchemy-bot
CollaboratorAuthor

Changes by Michael Bayer (@zzzeek):

  • changed title from "column_property ignores aliased" to "adaption of Label breaks further operations becaus"
sqlalchemy-bot

sqlalchemy-bot commented on Jun 10, 2015

@sqlalchemy-bot
CollaboratorAuthor

Michael Bayer (@zzzeek) wrote:

  • Fixed a bug where clause adaption as applied to a :class:.Label
    object would fail to accommodate the labeled SQL expression
    in all cases, such that any SQL operation that made use of
    :meth:.Label.self_group would use the original unadapted
    expression. One effect of this would be that an ORM :func:.aliased
    construct would not fully accommodate attributes mapped by
    :obj:.column_property, such that the un-aliased table could
    leak out when the property were used in some kinds of SQL
    comparisons.
    fixes adaption of Label breaks further operations because it does not reset the _element attribute #3445

a9030d0

sqlalchemy-bot

sqlalchemy-bot commented on Jun 10, 2015

@sqlalchemy-bot
CollaboratorAuthor

Changes by Michael Bayer (@zzzeek):

  • changed status to closed
sqlalchemy-bot

sqlalchemy-bot commented on Jun 10, 2015

@sqlalchemy-bot
CollaboratorAuthor

Michael Bayer (@zzzeek) wrote:

thanks for reporting!

sqlalchemy-bot

sqlalchemy-bot commented on Jun 10, 2015

@sqlalchemy-bot
CollaboratorAuthor

Lukas Siemon (@lukas-gitl) wrote:

Thanks for the quick fix! Much appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsql

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sqlalchemy-bot

        Issue actions

          adaption of Label breaks further operations because it does not reset the _element attribute · Issue #3445 · sqlalchemy/sqlalchemy