Skip to content

Commit 7904ebc

Browse files
committed
- rework the previous "order by" system in terms of the new one,
unify everything. - create a new layer of separation between the "from order bys" and "column order bys", so that an OVER doesn't ORDER BY a label in the same columns clause - identify another issue with polymorphic for ref sqlalchemy#3148, match on label keys rather than the objects
1 parent e4996d4 commit 7904ebc

File tree

6 files changed

+123
-46
lines changed

6 files changed

+123
-46
lines changed

lib/sqlalchemy/sql/compiler.py

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -503,27 +503,61 @@ def default_from(self):
503503
def visit_grouping(self, grouping, asfrom=False, **kwargs):
504504
return "(" + grouping.element._compiler_dispatch(self, **kwargs) + ")"
505505

506-
def visit_label_reference(self, element, **kwargs):
506+
def visit_label_reference(
507+
self, element, within_columns_clause=False, **kwargs):
508+
if self.stack and self.dialect.supports_simple_order_by_label:
509+
selectable = self.stack[-1]['selectable']
510+
511+
with_cols, only_froms = selectable._label_resolve_dict
512+
if within_columns_clause:
513+
resolve_dict = only_froms
514+
else:
515+
resolve_dict = with_cols
516+
517+
# this can be None in the case that a _label_reference()
518+
# were subject to a replacement operation, in which case
519+
# the replacement of the Label element may have changed
520+
# to something else like a ColumnClause expression.
521+
order_by_elem = element.element._order_by_label_element
522+
523+
if order_by_elem is not None and order_by_elem.name in \
524+
resolve_dict:
525+
526+
kwargs['render_label_as_label'] = \
527+
element.element._order_by_label_element
528+
529+
return self.process(
530+
element.element, within_columns_clause=within_columns_clause,
531+
**kwargs)
532+
533+
def visit_textual_label_reference(
534+
self, element, within_columns_clause=False, **kwargs):
507535
if not self.stack:
508536
# compiling the element outside of the context of a SELECT
509537
return self.process(
510538
element._text_clause
511539
)
512540

513541
selectable = self.stack[-1]['selectable']
542+
with_cols, only_froms = selectable._label_resolve_dict
543+
514544
try:
515-
col = selectable._label_resolve_dict[element.text]
545+
if within_columns_clause:
546+
col = only_froms[element.element]
547+
else:
548+
col = with_cols[element.element]
516549
except KeyError:
517550
# treat it like text()
518551
util.warn_limited(
519552
"Can't resolve label reference %r; converting to text()",
520-
util.ellipses_string(element.text))
553+
util.ellipses_string(element.element))
521554
return self.process(
522555
element._text_clause
523556
)
524557
else:
525558
kwargs['render_label_as_label'] = col
526-
return self.process(col, **kwargs)
559+
return self.process(
560+
col, within_columns_clause=within_columns_clause, **kwargs)
527561

528562
def visit_label(self, label,
529563
add_to_result_map=None,
@@ -678,11 +712,7 @@ def visit_false(self, expr, **kw):
678712
else:
679713
return "0"
680714

681-
def visit_clauselist(self, clauselist, order_by_select=None, **kw):
682-
if order_by_select is not None:
683-
return self._order_by_clauselist(
684-
clauselist, order_by_select, **kw)
685-
715+
def visit_clauselist(self, clauselist, **kw):
686716
sep = clauselist.operator
687717
if sep is None:
688718
sep = " "
@@ -695,26 +725,6 @@ def visit_clauselist(self, clauselist, order_by_select=None, **kw):
695725
for c in clauselist.clauses)
696726
if s)
697727

698-
def _order_by_clauselist(self, clauselist, order_by_select, **kw):
699-
# look through raw columns collection for labels.
700-
# note that its OK we aren't expanding tables and other selectables
701-
# here; we can only add a label in the ORDER BY for an individual
702-
# label expression in the columns clause.
703-
704-
raw_col = set(order_by_select._label_resolve_dict.keys())
705-
706-
return ", ".join(
707-
s for s in
708-
(
709-
c._compiler_dispatch(
710-
self,
711-
render_label_as_label=c._order_by_label_element if
712-
c._order_by_label_element is not None and
713-
c._order_by_label_element._label in raw_col
714-
else None,
715-
**kw)
716-
for c in clauselist.clauses)
717-
if s)
718728

719729
def visit_case(self, clause, **kwargs):
720730
x = "CASE "
@@ -1590,13 +1600,7 @@ def visit_select(self, select, asfrom=False, parens=True,
15901600
text += " \nHAVING " + t
15911601

15921602
if select._order_by_clause.clauses:
1593-
if self.dialect.supports_simple_order_by_label:
1594-
order_by_select = select
1595-
else:
1596-
order_by_select = None
1597-
1598-
text += self.order_by_clause(
1599-
select, order_by_select=order_by_select, **kwargs)
1603+
text += self.order_by_clause(select, **kwargs)
16001604

16011605
if (select._limit_clause is not None or
16021606
select._offset_clause is not None):

lib/sqlalchemy/sql/elements.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,14 +2356,39 @@ def _from_objects(self):
23562356

23572357

23582358
class _label_reference(ColumnElement):
2359+
"""Wrap a column expression as it appears in a 'reference' context.
2360+
2361+
This expression is any that inclues an _order_by_label_element,
2362+
which is a Label, or a DESC / ASC construct wrapping a Label.
2363+
2364+
The production of _label_reference() should occur when an expression
2365+
is added to this context; this includes the ORDER BY or GROUP BY of a
2366+
SELECT statement, as well as a few other places, such as the ORDER BY
2367+
within an OVER clause.
2368+
2369+
"""
23592370
__visit_name__ = 'label_reference'
23602371

2361-
def __init__(self, text):
2362-
self.text = self.key = text
2372+
def __init__(self, element):
2373+
self.element = element
2374+
2375+
def _copy_internals(self, clone=_clone, **kw):
2376+
self.element = clone(self.element, **kw)
2377+
2378+
@property
2379+
def _from_objects(self):
2380+
return ()
2381+
2382+
2383+
class _textual_label_reference(ColumnElement):
2384+
__visit_name__ = 'textual_label_reference'
2385+
2386+
def __init__(self, element):
2387+
self.element = element
23632388

23642389
@util.memoized_property
23652390
def _text_clause(self):
2366-
return TextClause._create_text(self.text)
2391+
return TextClause._create_text(self.element)
23672392

23682393

23692394
class UnaryExpression(ColumnElement):
@@ -3556,6 +3581,13 @@ def _clause_element_as_expr(element):
35563581

35573582
def _literal_as_label_reference(element):
35583583
if isinstance(element, util.string_types):
3584+
return _textual_label_reference(element)
3585+
3586+
elif hasattr(element, '__clause_element__'):
3587+
element = element.__clause_element__()
3588+
3589+
if isinstance(element, ColumnElement) and \
3590+
element._order_by_label_element is not None:
35593591
return _label_reference(element)
35603592
else:
35613593
return _literal_as_text(element)

lib/sqlalchemy/sql/selectable.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,9 +1885,10 @@ def __init__(self, keyword, *selects, **kwargs):
18851885

18861886
@property
18871887
def _label_resolve_dict(self):
1888-
return dict(
1888+
d = dict(
18891889
(c.key, c) for c in self.c
18901890
)
1891+
return d, d
18911892

18921893
@classmethod
18931894
def _create_union(cls, *selects, **kwargs):
@@ -2499,15 +2500,16 @@ def inner_columns(self):
24992500

25002501
@_memoized_property
25012502
def _label_resolve_dict(self):
2502-
d = dict(
2503+
with_cols = dict(
25032504
(c._resolve_label or c._label or c.key, c)
25042505
for c in _select_iterables(self._raw_columns)
25052506
if c._allow_label_resolve)
2506-
d.update(
2507+
only_froms = dict(
25072508
(c.key, c) for c in
25082509
_select_iterables(self.froms) if c._allow_label_resolve)
2510+
with_cols.update(only_froms)
25092511

2510-
return d
2512+
return with_cols, only_froms
25112513

25122514
def is_derived_from(self, fromclause):
25132515
if self in fromclause._cloned_set:

lib/sqlalchemy/sql/util.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from collections import deque
1717

1818
from .elements import BindParameter, ColumnClause, ColumnElement, \
19-
Null, UnaryExpression, literal_column, Label
19+
Null, UnaryExpression, literal_column, Label, _label_reference
2020
from .selectable import ScalarSelect, Join, FromClause, FromGrouping
2121
from .schema import Column
2222

@@ -161,6 +161,8 @@ def unwrap_order_by(clause):
161161
not isinstance(t, UnaryExpression) or
162162
not operators.is_ordering_modifier(t.modifier)
163163
):
164+
if isinstance(t, _label_reference):
165+
t = t.element
164166
cols.add(t)
165167
else:
166168
for c in t.get_children():

test/orm/test_query.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,7 @@ class ColumnPropertyTest(_fixtures.FixtureTest, AssertsCompiledSQL):
12361236
__dialect__ = 'default'
12371237
run_setup_mappers = 'each'
12381238

1239-
def _fixture(self, label=True):
1239+
def _fixture(self, label=True, polymorphic=False):
12401240
User, Address = self.classes("User", "Address")
12411241
users, addresses = self.tables("users", "addresses")
12421242
stmt = select([func.max(addresses.c.email_address)]).\
@@ -1247,7 +1247,7 @@ def _fixture(self, label=True):
12471247

12481248
mapper(User, users, properties={
12491249
"ead": column_property(stmt)
1250-
})
1250+
}, with_polymorphic="*" if polymorphic else None)
12511251
mapper(Address, addresses)
12521252

12531253
def test_order_by_column_prop_string(self):
@@ -1355,6 +1355,22 @@ def test_order_by_column_labeled_prop_attr_aliased_three(self):
13551355
"users AS users_1 ORDER BY email_ad, anon_1"
13561356
)
13571357

1358+
def test_order_by_column_labeled_prop_attr_aliased_four(self):
1359+
User = self.classes.User
1360+
self._fixture(label=True, polymorphic=True)
1361+
1362+
ua = aliased(User)
1363+
s = Session()
1364+
q = s.query(ua, User.id).order_by(ua.ead)
1365+
self.assert_compile(
1366+
q,
1367+
"SELECT (SELECT max(addresses.email_address) AS max_1 FROM "
1368+
"addresses WHERE addresses.user_id = users_1.id) AS anon_1, "
1369+
"users_1.id AS users_1_id, users_1.name AS users_1_name, "
1370+
"users.id AS users_id FROM users AS users_1, users ORDER BY anon_1"
1371+
)
1372+
1373+
13581374
def test_order_by_column_unlabeled_prop_attr_aliased_one(self):
13591375
User = self.classes.User
13601376
self._fixture(label=False)

test/sql/test_compiler.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2169,6 +2169,27 @@ def test_over(self):
21692169
"SELECT x + foo() OVER () AS anon_1"
21702170
)
21712171

2172+
# test a reference to a label that in the referecned selectable;
2173+
# this resolves
2174+
expr = (table1.c.myid + 5).label('sum')
2175+
stmt = select([expr]).alias()
2176+
self.assert_compile(
2177+
select([stmt.c.sum, func.row_number().over(order_by=stmt.c.sum)]),
2178+
"SELECT anon_1.sum, row_number() OVER (ORDER BY anon_1.sum) "
2179+
"AS anon_2 FROM (SELECT mytable.myid + :myid_1 AS sum "
2180+
"FROM mytable) AS anon_1"
2181+
)
2182+
2183+
# test a reference to a label that's at the same level as the OVER
2184+
# in the columns clause; doesn't resolve
2185+
expr = (table1.c.myid + 5).label('sum')
2186+
self.assert_compile(
2187+
select([expr, func.row_number().over(order_by=expr)]),
2188+
"SELECT mytable.myid + :myid_1 AS sum, "
2189+
"row_number() OVER "
2190+
"(ORDER BY mytable.myid + :myid_1) AS anon_1 FROM mytable"
2191+
)
2192+
21722193
def test_date_between(self):
21732194
import datetime
21742195
table = Table('dt', metadata,

0 commit comments

Comments
 (0)