From 7904ebc62e0a75d1ea31e1a4ae67654c7681a737 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 8 Sep 2014 16:31:11 -0400 Subject: - 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 #3148, match on label keys rather than the objects --- lib/sqlalchemy/sql/elements.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index cf8de936d..8ec0aa700 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -2356,14 +2356,39 @@ class Extract(ColumnElement): class _label_reference(ColumnElement): + """Wrap a column expression as it appears in a 'reference' context. + + This expression is any that inclues an _order_by_label_element, + which is a Label, or a DESC / ASC construct wrapping a Label. + + The production of _label_reference() should occur when an expression + is added to this context; this includes the ORDER BY or GROUP BY of a + SELECT statement, as well as a few other places, such as the ORDER BY + within an OVER clause. + + """ __visit_name__ = 'label_reference' - def __init__(self, text): - self.text = self.key = text + def __init__(self, element): + self.element = element + + def _copy_internals(self, clone=_clone, **kw): + self.element = clone(self.element, **kw) + + @property + def _from_objects(self): + return () + + +class _textual_label_reference(ColumnElement): + __visit_name__ = 'textual_label_reference' + + def __init__(self, element): + self.element = element @util.memoized_property def _text_clause(self): - return TextClause._create_text(self.text) + return TextClause._create_text(self.element) class UnaryExpression(ColumnElement): @@ -3556,6 +3581,13 @@ def _clause_element_as_expr(element): def _literal_as_label_reference(element): if isinstance(element, util.string_types): + return _textual_label_reference(element) + + elif hasattr(element, '__clause_element__'): + element = element.__clause_element__() + + if isinstance(element, ColumnElement) and \ + element._order_by_label_element is not None: return _label_reference(element) else: return _literal_as_text(element) -- cgit v1.2.1 From ad82849bbe4ef329129204d02781f737c0c79fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilja=20Everil=C3=A4?= Date: Wed, 10 Sep 2014 11:34:33 +0300 Subject: implementation for FILTER (WHERE ...) --- lib/sqlalchemy/sql/elements.py | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 8ec0aa700..5562e80d7 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -2888,6 +2888,71 @@ class Over(ColumnElement): )) +class AggregateFilter(ColumnElement): + """Represent an aggregate FILTER clause. + + This is a special operator against aggregate functions, + which controls which rows are passed to it. + It's supported only by certain database backends. + + """ + __visit_name__ = 'aggregatefilter' + + criterion = None + + def __init__(self, func, *criterion): + """Produce an :class:`.AggregateFilter` object against a function. + + Used against aggregate functions, + for database backends that support aggregate "FILTER" clause. + + E.g.:: + + from sqlalchemy import aggregatefilter + aggregatefilter(func.count(1), MyClass.name == 'some name') + + Would produce "COUNT(1) FILTER (WHERE myclass.name = 'some name')". + + This function is also available from the :data:`~.expression.func` + construct itself via the :meth:`.FunctionElement.filter` method. + + """ + self.func = func + self.filter(*criterion) + + def filter(self, *criterion): + for criterion in list(criterion): + criterion = _expression_literal_as_text(criterion) + + if self.criterion is not None: + self.criterion = self.criterion & criterion + else: + self.criterion = criterion + + return self + + @util.memoized_property + def type(self): + return self.func.type + + def get_children(self, **kwargs): + return [c for c in + (self.func, self.criterion) + if c is not None] + + def _copy_internals(self, clone=_clone, **kw): + self.func = clone(self.func, **kw) + if self.criterion is not None: + self.criterion = clone(self.criterion, **kw) + + @property + def _from_objects(self): + return list(itertools.chain( + *[c._from_objects for c in (self.func, self.criterion) + if c is not None] + )) + + class Label(ColumnElement): """Represents a column label (AS). -- cgit v1.2.1 From ab1c25266dd49f087b5fff316b6ba6fb610b1d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilja=20Everil=C3=A4?= Date: Thu, 11 Sep 2014 15:29:33 +0300 Subject: renamed aggregatefilter to funcfilter, since it is that --- lib/sqlalchemy/sql/elements.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 5562e80d7..5ac16ab7a 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -2888,28 +2888,28 @@ class Over(ColumnElement): )) -class AggregateFilter(ColumnElement): - """Represent an aggregate FILTER clause. +class FunctionFilter(ColumnElement): + """Represent a function FILTER clause. - This is a special operator against aggregate functions, + This is a special operator against aggregate and window functions, which controls which rows are passed to it. It's supported only by certain database backends. """ - __visit_name__ = 'aggregatefilter' + __visit_name__ = 'funcfilter' criterion = None def __init__(self, func, *criterion): - """Produce an :class:`.AggregateFilter` object against a function. + """Produce an :class:`.FunctionFilter` object against a function. - Used against aggregate functions, - for database backends that support aggregate "FILTER" clause. + Used against aggregate and window functions, + for database backends that support the "FILTER" clause. E.g.:: - from sqlalchemy import aggregatefilter - aggregatefilter(func.count(1), MyClass.name == 'some name') + from sqlalchemy import funcfilter + funcfilter(func.count(1), MyClass.name == 'some name') Would produce "COUNT(1) FILTER (WHERE myclass.name = 'some name')". -- cgit v1.2.1 From 52a095ba6675f5f5807a1dc655b4ae32b9999f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilja=20Everil=C3=A4?= Date: Thu, 11 Sep 2014 15:39:56 +0300 Subject: allow windowing filtered functions --- lib/sqlalchemy/sql/elements.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 5ac16ab7a..62fe6553a 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -2931,6 +2931,26 @@ class FunctionFilter(ColumnElement): return self + def over(self, partition_by=None, order_by=None): + """Produce an OVER clause against this filtered function. + + Used against aggregate or so-called "window" functions, + for database backends that support window functions. + + The expression:: + + func.rank().filter(MyClass.y > 5).over(order_by='x') + + is shorthand for:: + + from sqlalchemy import over, funcfilter + over(funcfilter(func.rank(), MyClass.y > 5), order_by='x') + + See :func:`~.expression.over` for a full description. + + """ + return Over(self, partition_by=partition_by, order_by=order_by) + @util.memoized_property def type(self): return self.func.type -- cgit v1.2.1 From 89fc7d65b9ac12dd70d48c8d3be04bd50e696ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilja=20Everil=C3=A4?= Date: Thu, 11 Sep 2014 15:47:24 +0300 Subject: documentation indentation fix --- lib/sqlalchemy/sql/elements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 62fe6553a..c1c4fc1e1 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -2908,8 +2908,8 @@ class FunctionFilter(ColumnElement): E.g.:: - from sqlalchemy import funcfilter - funcfilter(func.count(1), MyClass.name == 'some name') + from sqlalchemy import funcfilter + funcfilter(func.count(1), MyClass.name == 'some name') Would produce "COUNT(1) FILTER (WHERE myclass.name = 'some name')". -- cgit v1.2.1 From 76c06aa65345b47af38a0a1d20638dfbc890b640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilja=20Everil=C3=A4?= Date: Thu, 11 Sep 2014 15:49:51 +0300 Subject: method documentation typo fix --- lib/sqlalchemy/sql/elements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index c1c4fc1e1..53838358d 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -2901,7 +2901,7 @@ class FunctionFilter(ColumnElement): criterion = None def __init__(self, func, *criterion): - """Produce an :class:`.FunctionFilter` object against a function. + """Produce a :class:`.FunctionFilter` object against a function. Used against aggregate and window functions, for database backends that support the "FILTER" clause. -- cgit v1.2.1 From ce52dd9e3b71f2074d7821fe62803d4e0eefe512 Mon Sep 17 00:00:00 2001 From: ndparker Date: Tue, 23 Sep 2014 23:28:11 +0200 Subject: improve exception vs. exit handling --- lib/sqlalchemy/sql/elements.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 8ec0aa700..8e18a22fe 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -3491,6 +3491,8 @@ def _string_or_unprintable(element): else: try: return str(element) + except (SystemExit, KeyboardInterrupt): + raise except: return "unprintable element %r" % element -- cgit v1.2.1 From 690532131d8ce8250c62f1d3e27405902df03e70 Mon Sep 17 00:00:00 2001 From: ndparker Date: Thu, 2 Oct 2014 22:00:31 +0200 Subject: cleanup exception handling - use new exception hierarchy (since python 2.5) --- lib/sqlalchemy/sql/elements.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 8e18a22fe..4bc1683dd 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -3491,9 +3491,7 @@ def _string_or_unprintable(element): else: try: return str(element) - except (SystemExit, KeyboardInterrupt): - raise - except: + except Exception: return "unprintable element %r" % element -- cgit v1.2.1 From 49e750a1d788710b89764c4dd9c0ddbf9b1f38ad Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 4 Oct 2014 12:18:20 -0400 Subject: - changelog, migration for pr github:134 --- lib/sqlalchemy/sql/elements.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 53838358d..db14031d2 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -2895,6 +2895,17 @@ class FunctionFilter(ColumnElement): which controls which rows are passed to it. It's supported only by certain database backends. + Invocation of :class:`.FunctionFilter` is via + :meth:`.FunctionElement.filter`:: + + func.count(1).filter(True) + + .. versionadded:: 1.0.0 + + .. seealso:: + + :meth:`.FunctionElement.filter` + """ __visit_name__ = 'funcfilter' @@ -2916,11 +2927,29 @@ class FunctionFilter(ColumnElement): This function is also available from the :data:`~.expression.func` construct itself via the :meth:`.FunctionElement.filter` method. + .. versionadded:: 1.0.0 + + .. seealso:: + + :meth:`.FunctionElement.filter` + + """ self.func = func self.filter(*criterion) def filter(self, *criterion): + """Produce an additional FILTER against the function. + + This method adds additional criteria to the initial criteria + set up by :meth:`.FunctionElement.filter`. + + Multiple criteria are joined together at SQL render time + via ``AND``. + + + """ + for criterion in list(criterion): criterion = _expression_literal_as_text(criterion) -- cgit v1.2.1 From 81d1e0455a406560be468d1aacc37aa63bb4d717 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Thu, 9 Oct 2014 17:20:30 -0400 Subject: - Fixed bug where a fair number of SQL elements within the sql package would fail to ``__repr__()`` successfully, due to a missing ``description`` attribute that would then invoke a recursion overflow when an internal AttributeError would then re-invoke ``__repr__()``. fixes #3195 --- lib/sqlalchemy/sql/elements.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index db14031d2..c38d83106 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -228,6 +228,7 @@ class ClauseElement(Visitable): is_selectable = False is_clause_element = True + description = None _order_by_label_element = None _is_from_container = False @@ -540,7 +541,7 @@ class ClauseElement(Visitable): __nonzero__ = __bool__ def __repr__(self): - friendly = getattr(self, 'description', None) + friendly = self.description if friendly is None: return object.__repr__(self) else: -- cgit v1.2.1 From ade27f35cb4911306404dcc74cce8bbf6f7d37bb Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 19 Oct 2014 18:26:14 -0400 Subject: - Reversing a change that was made in 0.9, the "singleton" nature of the "constants" :func:`.null`, :func:`.true`, and :func:`.false` has been reverted. These functions returning a "singleton" object had the effect that different instances would be treated as the same regardless of lexical use, which in particular would impact the rendering of the columns clause of a SELECT statement. fixes #3170 --- lib/sqlalchemy/sql/elements.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) (limited to 'lib/sqlalchemy/sql/elements.py') diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 444273e67..4d5bb9476 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -1617,10 +1617,10 @@ class Null(ColumnElement): return type_api.NULLTYPE @classmethod - def _singleton(cls): + def _instance(cls): """Return a constant :class:`.Null` construct.""" - return NULL + return Null() def compare(self, other): return isinstance(other, Null) @@ -1641,11 +1641,11 @@ class False_(ColumnElement): return type_api.BOOLEANTYPE def _negate(self): - return TRUE + return True_() @classmethod - def _singleton(cls): - """Return a constant :class:`.False_` construct. + def _instance(cls): + """Return a :class:`.False_` construct. E.g.:: @@ -1679,7 +1679,7 @@ class False_(ColumnElement): """ - return FALSE + return False_() def compare(self, other): return isinstance(other, False_) @@ -1700,17 +1700,17 @@ class True_(ColumnElement): return type_api.BOOLEANTYPE def _negate(self): - return FALSE + return False_() @classmethod def _ifnone(cls, other): if other is None: - return cls._singleton() + return cls._instance() else: return other @classmethod - def _singleton(cls): + def _instance(cls): """Return a constant :class:`.True_` construct. E.g.:: @@ -1745,15 +1745,11 @@ class True_(ColumnElement): """ - return TRUE + return True_() def compare(self, other): return isinstance(other, True_) -NULL = Null() -FALSE = False_() -TRUE = True_() - class ClauseList(ClauseElement): """Describe a list of clauses, separated by an operator. -- cgit v1.2.1