.. changelog::
:version: 1.1.5
+ .. change:: 3882
+ :tags: bug, sql
+ :tikets: 3882
+
+ Fixed bug originally introduced in 0.9 via :ticket:`1068` where
+ order_by(<some Label()>) would order by the label name based on name
+ alone, that is, even if the labeled expression were not at all the same
+ expression otherwise present, implicitly or explicitly, in the
+ selectable. The logic that orders by label now ensures that the
+ labeled expression is related to the one that resolves to that name
+ before ordering by the label name; additionally, the name has to
+ resolve to an actual label explicit in the expression elsewhere, not
+ just a column name. This logic is carefully kept separate from the
+ order by(textual name) feature that has a slightly different purpose.
+
.. change:: try_finally_for_noautoflush
:tags: bug, orm
if self.stack and self.dialect.supports_simple_order_by_label:
selectable = self.stack[-1]['selectable']
- with_cols, only_froms = selectable._label_resolve_dict
+ with_cols, only_froms, only_cols = selectable._label_resolve_dict
if within_columns_clause:
resolve_dict = only_froms
else:
- resolve_dict = with_cols
+ resolve_dict = only_cols
# this can be None in the case that a _label_reference()
# were subject to a replacement operation, in which case
order_by_elem = element.element._order_by_label_element
if order_by_elem is not None and order_by_elem.name in \
- resolve_dict:
-
+ resolve_dict and \
+ order_by_elem.shares_lineage(
+ resolve_dict[order_by_elem.name]):
kwargs['render_label_as_label'] = \
element.element._order_by_label_element
-
return self.process(
element.element, within_columns_clause=within_columns_clause,
**kwargs)
)
selectable = self.stack[-1]['selectable']
- with_cols, only_froms = selectable._label_resolve_dict
+ with_cols, only_froms, only_cols = selectable._label_resolve_dict
try:
if within_columns_clause:
col = only_froms[element.element]
d = dict(
(c.key, c) for c in self.c
)
- return d, d
+ return d, d, d
@classmethod
def _create_union(cls, *selects, **kwargs):
only_froms = dict(
(c.key, c) for c in
_select_iterables(self.froms) if c._allow_label_resolve)
+ only_cols = with_cols.copy()
for key, value in only_froms.items():
with_cols.setdefault(key, value)
- return with_cols, only_froms
+ return with_cols, only_froms, only_cols
def is_derived_from(self, fromclause):
if self in fromclause._cloned_set:
dialect=dialect
)
+ # expression isn't actually the same thing (even though label is)
+ self.assert_compile(
+ select([lab1, lab2]).order_by(
+ table1.c.myid.label('foo'),
+ desc(table1.c.name.label('bar'))
+ ),
+ "SELECT mytable.myid + :myid_1 AS foo, "
+ "somefunc(mytable.name) AS bar FROM mytable "
+ "ORDER BY mytable.myid, mytable.name DESC",
+ dialect=dialect
+ )
+
+ # it's also an exact match, not aliased etc.
+ self.assert_compile(
+ select([lab1, lab2]).order_by(
+ desc(table1.alias().c.name.label('bar'))
+ ),
+ "SELECT mytable.myid + :myid_1 AS foo, "
+ "somefunc(mytable.name) AS bar FROM mytable "
+ "ORDER BY mytable_1.name DESC",
+ dialect=dialect
+ )
+
+ # but! it's based on lineage
+ lab2_lineage = lab2.element._clone()
+ self.assert_compile(
+ select([lab1, lab2]).order_by(
+ desc(lab2_lineage.label('bar'))
+ ),
+ "SELECT mytable.myid + :myid_1 AS foo, "
+ "somefunc(mytable.name) AS bar FROM mytable "
+ "ORDER BY bar DESC",
+ dialect=dialect
+ )
+
+ # here, 'name' is implicitly available, but w/ #3882 we don't
+ # want to render a name that isn't specifically a Label elsewhere
+ # in the query
+ self.assert_compile(
+ select([table1.c.myid]).order_by(table1.c.name.label('name')),
+ "SELECT mytable.myid FROM mytable ORDER BY mytable.name"
+ )
+
+ # as well as if it doesn't match
+ self.assert_compile(
+ select([table1.c.myid]).order_by(
+ func.lower(table1.c.name).label('name')),
+ "SELECT mytable.myid FROM mytable ORDER BY lower(mytable.name)"
+ )
+
def test_order_by_labels_disabled(self):
lab1 = (table1.c.myid + 12).label('foo')
lab2 = func.somefunc(table1.c.name).label('bar')