From: Mike Bayer Date: Wed, 18 May 2011 16:07:40 +0000 (-0400) Subject: - Fixed bug whereby nesting a label of a select() X-Git-Tag: rel_0_7_0~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=00380cf3c304ea7f1c7397f17b828166364c36ac;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Fixed bug whereby nesting a label of a select() with another label in it would produce incorrect exported columns. Among other things this would break an ORM column_property() mapping against another column_property(). [ticket:2167]. Also in 0.6.8 - _Label() is always against a column or selectable. remove uncovered case of label against something else. - start taking notes to clean up some of this labeling stuff, which will be [ticket:2168] --- diff --git a/CHANGES b/CHANGES index e8e29e3e79..94c17366cc 100644 --- a/CHANGES +++ b/CHANGES @@ -88,6 +88,13 @@ CHANGES an unordered mapping unless you pass an OrderedDict). - sql + - Fixed bug whereby nesting a label of a select() + with another label in it would produce incorrect + exported columns. Among other things this would + break an ORM column_property() mapping against + another column_property(). [ticket:2167]. + Also in 0.6.8 + - Changed the handling in determination of join conditions such that foreign key errors are only considered between the two given tables. diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 45fe5c4bf5..54e864abb1 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -2794,11 +2794,11 @@ class _ColumnEntity(_QueryEntity): "expected - got '%r'" % column ) - # if the Column is unnamed, give it a + # If the Column is unnamed, give it a # label() so that mutable column expressions # can be located in the result even # if the expression's identity has been changed - # due to adaption + # due to adaption. if not column._label: column = column.label(None) diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 1013f9397b..e06eb61bef 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -3814,11 +3814,7 @@ class _Label(ColumnElement): return self.element._from_objects def _make_proxy(self, selectable, name = None): - if isinstance(self.element, (Selectable, ColumnElement)): - e = self.element._make_proxy(selectable, name=self.name) - else: - e = column(self.name)._make_proxy(selectable=selectable) - + e = self.element._make_proxy(selectable, name=name or self.name) e.proxies.append(self) return e @@ -3934,6 +3930,12 @@ class ColumnClause(_Immutable, ColumnElement): return self.name def label(self, name): + # currently, anonymous labels don't occur for + # ColumnClause. The use at the moment + # is that they do not generate nicely for + # is_literal clauses. We would like to change + # this so that label(None) acts as would be expected. + # See [ticket:2168]. if name is None: return self else: diff --git a/test/orm/test_froms.py b/test/orm/test_froms.py index 7959fcea9b..0a2617a6b5 100644 --- a/test/orm/test_froms.py +++ b/test/orm/test_froms.py @@ -2014,6 +2014,31 @@ class ExternalColumnsTest(QueryTest): eq_(o1.address.user.count, 1) self.assert_sql_count(testing.db, go, 1) + def test_external_columns_compound(self): + # see [ticket:2167] for background + users, Address, addresses, User = (self.tables.users, + self.classes.Address, + self.tables.addresses, + self.classes.User) + + mapper(User, users, properties={ + 'fullname':column_property(users.c.name.label('x')) + }) + + mapper(Address, addresses, properties={ + 'username':column_property( + select([User.fullname]).\ + where(User.id==addresses.c.user_id).label('y')) + }) + sess = create_session() + a1 = sess.query(Address).first() + eq_(a1.username, "jack") + + sess = create_session() + a1 = sess.query(Address).from_self().first() + eq_(a1.username, "jack") + + class TestOverlyEagerEquivalentCols(fixtures.MappedTest): @classmethod def define_tables(cls, metadata): diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index a529842bfe..47d81b8793 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -27,7 +27,7 @@ table2 = Table('table2', metadata, ) -class SelectableTest(fixtures.TestBase, AssertsExecutionResults): +class SelectableTest(fixtures.TestBase, AssertsExecutionResults, AssertsCompiledSQL): def test_indirect_correspondence_on_labels(self): # this test depends upon 'distance' to @@ -302,6 +302,56 @@ class SelectableTest(fixtures.TestBase, AssertsExecutionResults): assert_raises(exc.NoReferencedTableError, s.join, t1) + def test_multi_label_chain_naming_col(self): + # See [ticket:2167] for this one. + l1 = table1.c.col1.label('a') + l2 = select([l1]).label('b') + s = select([l2]) + assert s.c.b is not None + self.assert_compile( + s.select(), + "SELECT b FROM (SELECT (SELECT table1.col1 AS a FROM table1) AS b)" + ) + + s2 = select([s.label('c')]) + self.assert_compile( + s2.select(), + "SELECT c FROM (SELECT (SELECT (SELECT table1.col1 AS a FROM table1) AS b) AS c)" + ) + + +class AnonLabelTest(fixtures.TestBase): + """Test behaviors that we hope to change with [ticket:2168].""" + + def test_anon_labels_named_column(self): + c1 = column('x') + + # surprising + assert c1.label(None) is c1 + eq_(str(select([c1.label(None)])), "SELECT x") + + def test_anon_labels_literal_column(self): + c1 = literal_column('x') + assert c1.label(None) is c1 + eq_(str(select([c1.label(None)])), "SELECT x") + + def test_anon_labels_func(self): + c1 = func.count('*') + assert c1.label(None) is not c1 + + eq_(str(select([c1])), "SELECT count(:param_1) AS count_1") + c2 = select([c1]).compile() + + eq_(str(select([c1.label(None)])), "SELECT count(:param_1) AS count_1") + + def test_named_labels_named_column(self): + c1 = column('x') + eq_(str(select([c1.label('y')])), "SELECT x AS y") + + def test_named_labels_literal_column(self): + c1 = literal_column('x') + eq_(str(select([c1.label('y')])), "SELECT x AS y") + class JoinConditionTest(fixtures.TestBase, AssertsExecutionResults): def test_join_condition(self):