]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed bug in new :class:`.TextAsFrom` construct where :class:`.Column`-
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 2 Feb 2014 16:06:08 +0000 (11:06 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 2 Feb 2014 16:06:08 +0000 (11:06 -0500)
oriented row lookups were not matching up to the ad-hoc :class:`.ColumnClause`
objects that :class:`.TextAsFrom` generates, thereby making it not
usable as a target in :meth:`.Query.from_statement`.  Also fixed
:meth:`.Query.from_statement` mechanics to not mistake a :class:`.TextAsFrom`
for a :class:`.Select` construct.  This bug is also an 0.9 regression
as the :meth:`.Text.columns` method is called to accommodate the
:paramref:`.text.typemap` argument. [ticket:2932]

doc/build/changelog/changelog_09.rst
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/selectable.py
test/orm/test_query.py
test/sql/test_query.py

index a218e0f547076a733270de13cf2daf18ba008bdc..573548cfa4276bbb23fa23706ba1a75192a2238c 100644 (file)
 .. changelog::
     :version: 0.9.2
 
+    .. change::
+        :tags: bug, orm
+        :tickets: 2932
+
+        Fixed bug in new :class:`.TextAsFrom` construct where :class:`.Column`-
+        oriented row lookups were not matching up to the ad-hoc :class:`.ColumnClause`
+        objects that :class:`.TextAsFrom` generates, thereby making it not
+        usable as a target in :meth:`.Query.from_statement`.  Also fixed
+        :meth:`.Query.from_statement` mechanics to not mistake a :class:`.TextAsFrom`
+        for a :class:`.Select` construct.  This bug is also an 0.9 regression
+        as the :meth:`.Text.columns` method is called to accommodate the
+        :paramref:`.text.typemap` argument.
+
     .. change::
         :tags: feature, sql
         :tickets: 2923
index 10d635b8d050ed17b3795925dda33ec9ff067982..949c7eff9a15fba5e776a236e1d782e95e5c1570 100644 (file)
@@ -3471,6 +3471,7 @@ class QueryContext(object):
 
         if query._statement is not None:
             if isinstance(query._statement, expression.SelectBase) and \
+                                not query._statement._textual and \
                                 not query._statement.use_labels:
                 self.statement = query._statement.apply_labels()
             else:
index 975cf2d79b997a92c2914641f4ed56ad08abae93..cca03851f8d02c8a53e03eed342d35171dbe47f1 100644 (file)
@@ -2920,9 +2920,12 @@ class ColumnClause(Immutable, ColumnElement):
 
     def _compare_name_for_result(self, other):
         if self.is_literal or \
-            self.table is None or \
+            self.table is None or self.table._textual or \
             not hasattr(other, 'proxy_set') or (
-            isinstance(other, ColumnClause) and other.is_literal
+            isinstance(other, ColumnClause) and
+                (other.is_literal or
+                        other.table is None or
+                        other.table._textual)
         ):
             return super(ColumnClause, self).\
                     _compare_name_for_result(other)
index 01d617259b0f71b664d38315c6c42c85254f7ae3..bda3d655e68458dd84a98100f61eefcf4ffd8427 100644 (file)
@@ -137,6 +137,10 @@ class FromClause(Selectable):
     named_with_column = False
     _hide_froms = []
 
+    _textual = False
+    """a marker that allows us to easily distinguish a :class:`.TextAsFrom`
+    or similar object from other kinds of :class:`.FromClause` objects."""
+
     schema = None
     """Define the 'schema' attribute for this :class:`.FromClause`.
 
@@ -3046,6 +3050,8 @@ class TextAsFrom(SelectBase):
     """
     __visit_name__ = "text_as_from"
 
+    _textual = True
+
     def __init__(self, text, columns):
         self.element = text
         self.column_args = columns
index 64b8cfdc460ba8f874dab60e9c3028d84fd583c5..07cb24cb62d1d8de36b6c9941a9ba9842207128b 100644 (file)
@@ -1,7 +1,7 @@
 from sqlalchemy.sql import operators
 from sqlalchemy import MetaData, null, exists, text, union, literal, \
     literal_column, func, between, Unicode, desc, and_, bindparam, \
-    select, distinct, or_, collate, insert
+    select, distinct, or_, collate, insert, Integer, String
 from sqlalchemy import inspect
 from sqlalchemy import exc as sa_exc, util
 from sqlalchemy.sql import compiler, table, column
@@ -2080,6 +2080,43 @@ class TextTest(QueryTest):
             [User(id=7), User(id=8), User(id=9), User(id=10)]
         )
 
+    def test_via_textasfrom_from_statement(self):
+        User = self.classes.User
+        s = create_session()
+
+        eq_(
+            s.query(User).from_statement(
+                text("select * from users order by id").\
+                        columns(id=Integer, name=String)
+            ).all(),
+            [User(id=7), User(id=8), User(id=9), User(id=10)]
+        )
+
+    def test_via_textasfrom_select_from(self):
+        User = self.classes.User
+        s = create_session()
+
+        eq_(
+            s.query(User).select_from(
+                text("select * from users order by id").\
+                        columns(id=Integer, name=String)
+            ).all(),
+            [User(id=7), User(id=8), User(id=9), User(id=10)]
+        )
+
+    def test_via_textasfrom_use_mapped_columns(self):
+        User = self.classes.User
+        s = create_session()
+
+        eq_(
+            s.query(User).select_from(
+                text("select * from users order by id").\
+                        columns(User.id, User.name)
+            ).all(),
+            [User(id=7), User(id=8), User(id=9), User(id=10)]
+        )
+
+
 class ParentTest(QueryTest, AssertsCompiledSQL):
     __dialect__ = 'default'
 
index 26e2fcf34d2b31cd8ce2eb82618c7bb1a801a621..6e2227650105fe5c2efb55e6dadcb6486cc0f93c 100644 (file)
@@ -1787,6 +1787,51 @@ class KeyTargetingTest(fixtures.TablesTest):
         assert bar.c.content_type not in row
         assert sql.column('content_type') not in row
 
+    def test_columnclause_schema_column_one(self):
+        keyed2 = self.tables.keyed2
+
+        # this is addressed by [ticket:2932]
+        # ColumnClause._compare_name_for_result allows the
+        # columns which the statement is against to be lightweight
+        # cols, which results in a more liberal comparison scheme
+        a, b = sql.column('a'), sql.column('b')
+        stmt = select([a, b]).select_from("keyed2")
+        row = testing.db.execute(stmt).first()
+
+        assert keyed2.c.a in row
+        assert keyed2.c.b in row
+        assert a in row
+        assert b in row
+
+    def test_columnclause_schema_column_two(self):
+        keyed2 = self.tables.keyed2
+
+        a, b = sql.column('a'), sql.column('b')
+        stmt = select([keyed2.c.a, keyed2.c.b])
+        row = testing.db.execute(stmt).first()
+
+        assert keyed2.c.a in row
+        assert keyed2.c.b in row
+        assert a in row
+        assert b in row
+
+    def test_columnclause_schema_column_three(self):
+        keyed2 = self.tables.keyed2
+
+        # this is also addressed by [ticket:2932]
+
+        a, b = sql.column('a'), sql.column('b')
+        stmt = text("select a, b from keyed2").columns(a=CHAR, b=CHAR)
+        row = testing.db.execute(stmt).first()
+
+        assert keyed2.c.a in row
+        assert keyed2.c.b in row
+        assert a in row
+        assert b in row
+        assert stmt.c.a in row
+        assert stmt.c.b in row
+
+
 
 class LimitTest(fixtures.TestBase):